如何检查Windows系统中自带粗体、斜体样式的已安装字体(C++/MFC)
如何在MFC/C++中筛选具备原生粗体/斜体/粗斜体的字体
嘿,这个需求我太熟悉了——毕竟Windows的字体模拟功能确实容易让我们混淆「原生样式」和「系统模拟样式」。既然你已经在用EnumFontFamiliesEx,其实可以通过优化回调里的判断逻辑来精准筛选,甚至还有更权威的方法,咱们一步步说:
核心原理:区分原生样式和系统模拟
Windows里,真正具备粗体/斜体的字体是作为独立字体文件存在的,它们的字体元数据里会明确标记这些样式;而系统模拟的粗体/斜体,只是GDI对基础字体做拉伸、倾斜变形,并没有原生的样式支持。
方法一:基于EnumFontFamiliesEx回调的快速筛选
这是最贴合你现有代码的方案,只需要在枚举回调里判断NEWTEXTMETRICEX的标志位,就能区分原生和模拟样式:
- 定义存储字体信息的结构:
struct FontFamilyInfo { CString faceName; // 字体家族名 bool hasNativeBold; // 是否有原生粗体 bool hasNativeItalic; // 是否有原生斜体 bool hasNativeBoldItalic; // 是否有原生粗斜体 }; CArray<FontFamilyInfo, FontFamilyInfo> m_genuineFonts;
- 实现枚举回调函数:
int CALLBACK EnumGenuineFontProc(const LOGFONT* lplf, const TEXTMETRIC* lptm, DWORD dwType, LPARAM lpData) { auto pFontList = reinterpret_cast<CArray<FontFamilyInfo, FontFamilyInfo>*>(lpData); auto pntm = const_cast<NEWTEXTMETRICEX*>(reinterpret_cast<const NEWTEXTMETRICEX*>(lptm)); // 提取当前字体的样式信息 bool isBold = (pntm->ntmTm.ntmFlags & NTM_BOLD) != 0; bool isItalic = (pntm->ntmTm.ntmFlags & NTM_ITALIC) != 0; bool isBoldItalic = isBold && isItalic; // 按字体家族去重,合并样式信息 bool familyExists = false; for (int i = 0; i < pFontList->GetSize(); ++i) { if ((*pFontList)[i].faceName == lplf->lfFaceName) { familyExists = true; if (isBold) (*pFontList)[i].hasNativeBold = true; if (isItalic) (*pFontList)[i].hasNativeItalic = true; if (isBoldItalic) (*pFontList)[i].hasNativeBoldItalic = true; break; } } // 新字体家族则添加到列表 if (!familyExists) { FontFamilyInfo info; info.faceName = lplf->lfFaceName; info.hasNativeBold = isBold; info.hasNativeItalic = isItalic; info.hasNativeBoldItalic = isBoldItalic; pFontList->Add(info); } return 1; // 继续枚举下一个字体 }
- 在MFC函数中调用枚举:
void CMyApp::CollectGenuineFonts() { m_genuineFonts.RemoveAll(); CDC dc; dc.Attach(::GetDC(NULL)); LOGFONT lf = {0}; lf.lfCharSet = DEFAULT_CHARSET; // 枚举所有字符集的字体 EnumFontFamiliesEx(dc.m_hDC, &lf, EnumGenuineFontProc, reinterpret_cast<LPARAM>(&m_genuineFonts), 0); dc.Detach(); // 输出结果示例 for (int i = 0; i < m_genuineFonts.GetSize(); ++i) { auto& info = m_genuineFonts[i]; TRACE(_T("字体家族:%s\n") _T(" 原生粗体:%s\n") _T(" 原生斜体:%s\n") _T(" 原生粗斜体:%s\n\n"), info.faceName, info.hasNativeBold ? _T("是") : _T("否"), info.hasNativeItalic ? _T("是") : _T("否"), info.hasNativeBoldItalic ? _T("是") : _T("否")); } }
为什么这个方法有效?
系统模拟的粗体/斜体,ntmTm.ntmFlags不会包含NTM_BOLD或NTM_ITALIC标志——只有原生字体的元数据里才会有这些标记,所以能精准过滤。
方法二:解析字体OS/2表的权威判断
如果需要100%准确的结果(比如某些特殊字体的边缘情况),可以直接读取字体文件的OS/2表,其中的fsSelection字段是字体样式的权威标记:
// 判断字体是否具备原生粗体 bool HasNativeBold(HFONT hFont) { HDC hdc = CreateCompatibleDC(NULL); HFONT hOldFont = static_cast<HFONT>(SelectObject(hdc, hFont)); // 获取OS/2表的大小 DWORD tableSize = GetFontData(hdc, MAKEINTRESOURCE('OS/2'), 0, NULL, 0); if (tableSize == GDI_ERROR) { SelectObject(hdc, hOldFont); DeleteDC(hdc); return false; } // 读取OS/2表数据 std::vector<BYTE> os2Table(tableSize); if (GetFontData(hdc, MAKEINTRESOURCE('OS/2'), 0, os2Table.data(), tableSize) == GDI_ERROR) { SelectObject(hdc, hOldFont); DeleteDC(hdc); return false; } // 解析fsSelection字段(偏移0x0A,2字节):位5表示原生粗体 WORD fsSelection = *reinterpret_cast<WORD*>(os2Table.data() + 0x0A); bool isNativeBold = (fsSelection & 0x0020) != 0; SelectObject(hdc, hOldFont); DeleteDC(hdc); return isNativeBold; } // 判断原生斜体:位0表示原生斜体 bool HasNativeItalic(HFONT hFont) { // 逻辑和上面类似,只是判断fsSelection & 0x0001 // 这里省略重复代码,你可以自行封装成通用函数 }
这个方法的优势:直接读取字体文件的元数据,完全不受系统模拟样式的干扰,是最权威的判断方式。
总结
- 如果你追求开发效率和足够的准确性,方法一完全满足需求,贴合你现有的
EnumFontFamiliesEx逻辑,代码改动小。 - 如果需要处理特殊字体或绝对精准的判断,方法二是最优解,虽然代码稍复杂,但结果最可靠。
内容的提问来源于stack exchange,提问作者Penachia
相关产品推荐
相关产品推荐

