如何获取通过AddFontMemResourceEx加载的字体名称?
解决方法
1. 问题根源:私有字体不被默认枚举
用AddFontMemResourceEx加载的字体属于进程私有字体,默认情况下EnumFontFamiliesEx只会枚举系统全局字体,不会包含这类仅对当前进程可见的私有字体,因此你两次枚举结果一致。
2. 正确枚举私有字体的方法
调用EnumFontFamiliesEx时,需在dwFlags参数中传入PRIVATE_FONT_ENUM标志,这样就能枚举到当前进程加载的私有字体。
代码示例
#include <windows.h> #include <vector> #include <string> // 枚举回调:收集字体名称 int CALLBACK EnumFontNameProc(ENUMLOGFONTEX* lpelfe, NEWTEXTMETRICEX* lpntme, DWORD FontType, LPARAM lParam) { std::vector<std::string>* fontNames = reinterpret_cast<std::vector<std::string>*>(lParam); fontNames->push_back(lpelfe->elfLogFont.lfFaceName); return 1; // 继续枚举 } // 获取当前进程的私有字体列表 std::vector<std::string> GetPrivateFontNames(HDC hdc) { std::vector<std::string> fontNames; LOGFONT lf = {0}; // 指定PRIVATE_FONT_ENUM标志枚举私有字体 EnumFontFamiliesEx(hdc, &lf, (FONTENUMPROCEX)EnumFontNameProc, (LPARAM)&fontNames, PRIVATE_FONT_ENUM); return fontNames; } // 使用示例 int main() { // 假设已通过AddFontMemResourceEx加载字体,得到资源句柄 HANDLE fontResourceHandle = ...; HDC hdc = GetDC(NULL); std::vector<std::string> privateFonts = GetPrivateFontNames(hdc); ReleaseDC(NULL, hdc); // 取第一个加载的私有字体名称(仅加载单个字体时适用) if (!privateFonts.empty()) { const char* faceName = privateFonts[0].c_str(); // 用获取到的名称创建字体 HFONT hFont = CreateFontA( 12, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, faceName ); // 使用字体... DeleteObject(hFont); } // 释放字体资源 RemoveFontMemResourceEx(fontResourceHandle); return 0; }
3. 更直接的方法:解析内存字体数据获取名称
如果不想依赖系统枚举,可直接解析内存中的TrueType/OTF字体数据,读取字体内置的名称字段,这种方法无需依赖GDI,更灵活。
核心思路
TrueType/OTF字体的name表存储了多语言字体名称,我们可以定位到该表,提取对应语言的名称(比如英文名称),再转换为ANSI格式供CreateFontA使用。
简化代码示例(仅支持TrueType)
#include <windows.h> #include <string> std::string GetTTFFontNameFromMemory(const BYTE* fontData, size_t dataSize) { if (dataSize < 12) return ""; // 验证是否为TrueType格式 uint32_t sfntVersion = *(uint32_t*)fontData; if (sfntVersion != 0x00010000 && sfntVersion != 'OTTO') return ""; uint16_t numTables = *(uint16_t*)(fontData + 4); // 遍历表目录查找name表 for (uint16_t i = 0; i < numTables; ++i) { uint32_t tag = *(uint32_t*)(fontData + 12 + i*16); if (tag == 'name') { uint32_t offset = *(uint32_t*)(fontData + 12 + i*16 + 8); uint32_t length = *(uint32_t*)(fontData + 12 + i*16 + 12); if (offset + length > dataSize) break; uint16_t count = *(uint16_t*)(fontData + offset + 2); uint16_t stringOffset = *(uint16_t*)(fontData + offset + 4); // 查找英文字体名称(nameID=4,对应Windows平台英文) for (uint16_t j = 0; j < count; ++j) { uint16_t nameID = *(uint16_t*)(fontData + offset + 6 + j*12 + 6); uint16_t platformID = *(uint16_t*)(fontData + offset + 6 + j*12); uint16_t languageID = *(uint16_t*)(fontData + offset + 6 + j*12 + 4); if (nameID == 4 && platformID == 3 && languageID == 0x0409) { uint16_t stringLength = *(uint16_t*)(fontData + offset + 6 + j*12 + 8); uint16_t stringLocalOffset = *(uint16_t*)(fontData + offset + 6 + j*12 + 10); const wchar_t* wideName = (const wchar_t*)(fontData + offset + stringOffset + stringLocalOffset); char ansiName[256] = {0}; WideCharToMultiByte(CP_ACP, 0, wideName, stringLength/2, ansiName, sizeof(ansiName), NULL, NULL); return std::string(ansiName); } } break; } } return ""; } // 使用示例 int main() { // fontData为内存中的字体数据,dataSize为数据大小 const BYTE* fontData = ...; size_t dataSize = ...; std::string fontName = GetTTFFontNameFromMemory(fontData, dataSize); if (!fontName.empty()) { HFONT hFont = CreateFontA(12, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, fontName.c_str()); // 使用字体... DeleteObject(hFont); } // 加载和释放字体资源... return 0; }
注意事项
- 调用
AddFontMemResourceEx后必须保存返回的HANDLE,最终用RemoveFontMemResourceEx释放,避免内存泄漏。 - 如果加载了多个字体,可在加载前后分别枚举私有字体,取两次结果的差值来定位新加载的字体。
- 解析字体数据的方法需根据实际需求扩展,以兼容OTF等其他字体格式。
内容的提问来源于stack exchange,提问作者Д Т
相关产品推荐
相关产品推荐

