0

0

C# 操作TrueType Collection(.ttc) C#如何处理包含多个字体的TTC文件

星降

星降

发布时间:2026-02-15 09:38:57

|

480人浏览过

|

来源于php中文网

原创

gdi+无法直接从ttc中提取单个字体,需手动解析ttc头获取子字体偏移,再用addmemoryfont加载指定ttf子流;wpf可通过fontsource+uri索引(如file:///x.ttc#2)原生支持。

c# 操作truetype collection(.ttc) c#如何处理包含多个字体的ttc文件

怎么用 Gdiplus 读取 TTC 文件里的单个字体

Windows GDI+ 原生不支持直接从 TTC 中提取指定字体实例,GdipCreateFontFromCollection 这类 API 根本不存在。你调用 PrivateFontCollection.AddFontFile 加载一个 .ttc 文件时,它会把整个集合里所有字体都注册进去——但你无法控制“只加载第 2 个字体”或“跳过 Bold 变体”。这是最常被误解的点:不是“能读但要写额外逻辑”,而是“根本没提供按索引/名称选取子字体的公开接口”。

实操建议:

  • 如果只是想让 WPF 或 WinForms 控件能用 TTC 里的字体,直接调用 PrivateFontCollection.AddFontFile("xxx.ttc") 就够了;系统内部会解析并注册全部子字体,之后你可用 new Font("Arial Unicode MS", 12) 这种名字引用(前提是名字匹配)
  • 若需确认 TTC 包含哪些字体名,必须自己解析 TTC 结构——靠 PrivateFontCollection 是拿不到列表的
  • 别在部署时假设用户已安装 TTC 中的字体;AddFontFile 的注册仅对当前进程有效,且不能跨 session 持久化

手动解析 TTC 文件头获取字体数量和偏移

TTC 文件本质是多个 TTF 的拼接,开头有固定结构:ttcf 签名、版本、字体数量,后跟一串偏移数组。你不需要完整实现 OpenType 解析,只需读前 12 字节就能拿到子字体个数和每个 TTF 的起始位置。

常见错误现象:用 FileStream 直接读 byte[4] 判断签名,却忽略字节序(TTC 头部用大端,而 x86 是小端);结果把 0x74746366 当成 "tfct" 判定失败。

实操建议:

  • BinaryReader 配合 BitConverter.IsLittleEndian ? BitConverter.GetBytes(x).Reverse().ToArray() : ... 处理四字节整数,或直接用 IPAddress.HostToNetworkOrder 转换
  • TTC 版本目前只有 0x00010000 和 0x00020000 两种,遇到其他值说明不是合法 TTC
  • 偏移数组起始位置 = 12 + 4 * fontCount,每个偏移是 uint32,指向对应 TTF 的第一个字节(即从该位置开始可当独立 TTF 流处理)

Stream 拆出单个 TTF 子流再喂给 PrivateFontCollection

拿到某个子字体的偏移和下一个偏移(或文件末尾),就能构造一个只包含该 TTF 的 MemoryStreamSubArrayStream。这时候再调用 AddFontFile 就等价于加载单个 TTF——绕过了 TTC 的“全量注册”限制。

Trickle AI
Trickle AI

多功能零代码AI应用开发平台

下载

使用场景:你有一个 TTC 含 Light / Regular / Bold 三版,但 UI 只允许用户选 Regular;或者你想预检某字体是否支持中文(查 cmap 表),但不想把所有变体都注册进进程。

实操建议:

  • 别用 File.OpenRead 后反复 Seek —— 在某些网络路径或只读介质上会失败;优先用 File.ReadAllBytes 加载全量再切片
  • PrivateFontCollection.AddFontFile 接收的是路径,不是流;所以必须把子 TTF 写到临时文件,或改用 AddMemoryFont(注意:后者要求传入完整 TTF 字节数组,且不能释放内存)
  • 调用 AddMemoryFont 后,务必保留该字节数组引用,否则 GC 回收会导致后续 Font 创建失败,错误信息是 "Object is not a valid Font"

WPF 中用 FontSource 加载 TTC 子字体更可控

WinForms 和 GDI+ 被动接受 TTC 全量注册,WPF 却提供了细粒度控制:FontSource 构造函数支持传入 Uri + int index,直接定位 TTC 内第 N 个字体。这是目前唯一无需手动解析、也无需临时文件的原生方案。

性能影响:WPF 内部仍会读取整个 TTC 文件,但只解析指定索引的字体表(如 name、cmap、glyf),比全量注册轻量得多;且不会污染进程级字体缓存。

实操建议:

  • URI 格式必须为 pack://application:,,,/Fonts/#FontNamefile:///C:/xxx.ttc#2(末尾 #2 表示第 3 个字体,索引从 0 开始)
  • 如果 TTC 中字体 name 表缺失或重复,#0 可能加载失败;此时 fallback 到手动解析 + AddMemoryFont
  • WPF 的 TextBlock.FontFamily 支持这种 URI,但 WinForms 的 Label.Font 不支持——别混用上下文

真正麻烦的从来不是“怎么读 TTC”,而是“怎么确定你要的那个字体到底在第几个位置、它的 name 字符串是否带空格或版本号、以及下游控件是否认这个 name”。这些细节不打日志根本看不出问题。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
session失效的原因
session失效的原因

session失效的原因有会话超时、会话数量限制、会话完整性检查、服务器重启、浏览器或设备问题等等。详细介绍:1、会话超时:服务器为Session设置了一个默认的超时时间,当用户在一段时间内没有与服务器交互时,Session将自动失效;2、会话数量限制:服务器为每个用户的Session数量设置了一个限制,当用户创建的Session数量超过这个限制时,最新的会覆盖最早的等等。

325

2023.10.17

session失效解决方法
session失效解决方法

session失效通常是由于 session 的生存时间过期或者服务器关闭导致的。其解决办法:1、延长session的生存时间;2、使用持久化存储;3、使用cookie;4、异步更新session;5、使用会话管理中间件。

772

2023.10.18

cookie与session的区别
cookie与session的区别

本专题整合了cookie与session的区别和使用方法等相关内容,阅读专题下面的文章了解更详细的内容。

96

2025.08.19

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

551

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

214

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1552

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

640

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

905

2024.03.22

pixiv网页版官网登录与阅读指南_pixiv官网直达入口与在线访问方法
pixiv网页版官网登录与阅读指南_pixiv官网直达入口与在线访问方法

本专题系统整理pixiv网页版官网入口及登录访问方式,涵盖官网登录页面直达路径、在线阅读入口及快速进入方法说明,帮助用户高效找到pixiv官方网站,实现便捷、安全的网页端浏览与账号登录体验。

76

2026.02.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C# 教程
C# 教程

共94课时 | 9.5万人学习

C 教程
C 教程

共75课时 | 4.7万人学习

C++教程
C++教程

共115课时 | 17.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号