不能。system.drawing.text 不提供字形数量等底层 opentype 数据,仅支持渲染;应使用 skiasharp 的 sktypeface.getglyphcount() 获取字形总数,它跨平台且能访问 glyph 表细节。

如何用 System.Drawing.Text 读取 OpenType 字体的字形数量?
不能。C# 原生 System.Drawing.Text(包括 PrivateFontCollection 和 FontFamily)根本不暴露字形(glyph)层面的数据,连字形总数都拿不到,更别说单个字形的轮廓或度量。它只负责“渲染”,不负责“解析”。你调用 GetEmHeight() 或 GetCellAscent() 得到的是字体整体排版参数,不是每个字形的 advanceWidth 或 boundingBox。
实操建议:
- 别在生产环境依赖
System.Drawing解析 OpenType —— 它是 GDI 封装,跨平台(.NET Core/.NET 5+)默认不可用,linux/macos 上会直接抛PlatformNotSupportedException - 如果只是想查字体是否支持某个 Unicode 字符,可用
FontFamily.IsStyleAvailable()+Graphics.MeasureString()间接试探,但无法确认具体映射到哪个 glyph ID - 真正要进 glyph 表(glyf、CFF、loca、maxp 等),必须换底层解析库
推荐用 SkiaSharp 提取字形度量和轮廓路径
SkiaSharp 是目前 C# 生态中最靠谱的 OpenType 解析方案:它自带完整的字体解析器(基于上游 Skia),能访问 glyph ID、advance width、left side bearing、甚至贝塞尔轮廓点序列,且全平台可用(windows/macOS/Linux/ios/android)。
实操建议:
- 安装
SkiaSharp和SkiaSharp.NativeAssets.*对应平台包(缺 native assets 会导致SKTypeface.FromFile()返回 NULL) - 用
SKTypeface.FromFile("<code>font.otf“) 加载,别用 stream —— 某些 OpenType 变体(如 TTC 集合里的子字体)需 mmap 支持,stream 会失败 - 通过
typeface.GetGlyphCount()拿总字形数;用typeface.GetGlyphs("<code>hello“) 转 Unicode 字符串为 glyph IDs;再对每个 ID 调用typeface.GetGlyphMetrics()获取advanceX、bounds等 - 要轮廓数据?用
SKPath path = new(); typeface.GetGlyphPath(glyphId, path)—— 注意 path 是设备无关单位(1em = 1024 单位),需结合typeface.UnitsPerEm换算
OPENTYPE 字体中 hmtx 和 glyf 表的实际访问差异
OpenType 规范里,水平度量(advance width / lsb)存在 hmtx 表,而轮廓数据在 glyf(TrueType 轮廓)或 CFF / CFF2(PostScript 轮廓)表。C# 没有标准 API 暴露这些二进制表,所以你无法像 Python 的 fontTools 那样直接 font['hmtx'].metrics 访问。
实操建议:
-
SkiaSharp内部已处理表解析逻辑,你只需调用高层 API;但要注意:它对 CFF 字体的GetGlyphPath()支持较晚(v2.88+ 才稳定),旧版本可能返回空 path - 若需原始表数据(比如做字体子集化或验证 checksum),得自己解析 SFNT 容器:先读 offset table 找表目录,再按 tag(如 “
hmtx“、”maxp“)定位并解码 —— 推荐用microsoft.Fonts(非官方,github 开源)或手撸二进制 reader,但工作量大、易出错 -
maxp表里的numGlyphs字段才是真实字形总数,比SkTypeface.GetGlyphCount()更底层;某些损坏字体中二者可能不一致,这时以maxp为准
为什么不用 DirectWrite 或 GDI+ P/Invoke?
理论上可通过 P/Invoke 调用 Windows 原生 IDWriteFontFace 或 GetGlyphOutline(),但代价极高:代码侵入性强、完全不可跨平台、需要手动管理 COM 引用和内存布局,且 .NET 6+ 中 unsafe + Marshal 的兼容性越来越难保障。
实操建议:
- 除非你在写 Windows 专用高性能字体编辑器,否则没必要碰这些 ——
SkiaSharp已把 DirectWrite(Windows)/ Core Text(macOS)/ FreeType(Linux)统一抽象掉了 - 特别注意:Windows 上若同时加载了相同字体文件的多个
SKTypeface实例,Skia 内部会复用缓存;但若用 P/Invoke 每次都CreateFontFace,容易触发 GDI 句柄泄漏 - 调试时遇到
SKTypeface.FromFile()返回 null,优先检查文件路径权限(尤其是 Linux 上的 SELinux 上下文)和字体是否被杀毒软件锁定
OpenType 是个分层很厚的格式,从 SFNT 容器到 glyph 描述再到 hinting 指令,每一层都有自己的坑。最常被忽略的是:同一个字体文件在不同解析器里返回的 glyph ID 映射可能不一致(比如是否跳过 .notdef 或保留私有区),实际做字形匹配时,永远用 GetGlyphs() 动态查,别硬编码 ID。