最可靠方式是使用各平台原生api封装:windows用gdi+的installedfontcollection,linux用fontconfig的fcfontlist,macos用core text的ctfontmanagercopyavailablefontfamilynames(),并统一抽象为跨平台接口。

Windows平台用GDI+枚举字体最可靠
在Windows上,GetFontData 或 EnumFontFamiliesEx 是获取已安装字体的底层方式,但直接调用容易漏掉系统字体(比如微软雅黑、等宽Consolas)或用户安装的OpenType字体。推荐用GDI+的 Gdiplus::InstalledFontCollection,它封装了注册表和字体目录扫描逻辑,兼容TrueType、OpenType、PostScript Type 1(Windows 10+),且不依赖DirectWrite。
实操要点:
- 需链接
Gdiplus.lib,初始化GDI+(GdiplusStartup),否则构造InstalledFontCollection会失败并返回空列表 - 字体名(
family->GetName())是英文名(如"Segoe UI"),不是显示名(如“微软正黑体”),中文显示名需查注册表HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts - 部分字体(如某些PDF嵌入字体或临时加载字体)不会出现在该集合中——它们不属于“系统安装”范畴
Linux下靠fontconfig而非遍历/usr/share/fonts
手动扫描 /usr/share/fonts、~/.local/share/fonts 等路径不仅遗漏缓存字体(如Noto Sans CJK),还会重复计数(同一字体多个变体被当作不同字体)。正确做法是调用fontconfig库的 FcFontList 接口,它读取的是 fonts.conf 解析后的完整字体集,包含别名、合成字体和用户配置。
常见坑:
- 未调用
FcInit()就直接FcFontList,返回nullptr—— 这是初学者最高频错误 -
FcPattern中提取字体名要用FcPatternGetString(pattern, FC_FAMILY, 0, &val),不能硬解FC_FILE路径再parse文件名 - 结果含重复家族(如
"Noto Sans"出现多次,对应Regular/Black/Condensed),需按FC_FAMILY去重,而非按文件路径
macOS必须用Core Text,ATS已废弃
macOS 10.12+ 中 ATS(Apple Type Services)彻底弃用,调用会崩溃或返回空。唯一可靠路径是Core Text框架的 CTFontManagerCopyAvailableFontFamilyNames(),它返回的是字体家族名(CFArrayRef),不是单个字重实例。
注意点:
- 返回数组元素是
CFStringRef,转std::string需用CFStringGetCString+ UTF-8 编码,直接强制cast会乱码 - 该API不返回系统禁用字体(比如用户通过
CTFontManagerDisableFontForURL屏蔽的),也不含仅用于App Bundle内嵌的字体 - 若需获取某家族所有变体(如
"Helvetica"下的"Helvetica Bold"),得对每个家族名调用CTFontCreateFromName再查kCTFontDisplayNameKey,不能只靠家族名列表
跨平台代码别硬写if-else,用抽象层隔离差异
直接在业务逻辑里写三套平台分支,会导致维护成本飙升,尤其当后续要加字体特性检测(如是否支持CJK、是否有可变轴)时更难扩展。建议把字体枚举封装成一个接口类,各平台实现为独立编译单元(.cpp)。
关键设计:
- 接口函数返回
std::vector<:string></:string>字体家族名,不暴露底层句柄(LOGFONT/FcPattern/CTFontRef) - Linux实现里提前调用
FcConfigSubstitute和FcDefaultSubstitute,否则某些合成字体(如"sans-serif")无法解析为真实家族 - macOS实现需在主线程调用(Core Text部分API非线程安全),而Windows/Linux可安全放在线程池里执行
最常被忽略的是:字体列表不是静态快照。Windows上用户可能正在安装字体,macOS可能刚启用/禁用字体册中的字体——除非你监听 CTFontManagerRegisterGraphicsFont 或 WM_FONTCHANGE 消息,否则每次调用都是当前时刻的瞬态结果。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











