最近工作中遇到一个需求,需要统计当前系统中包含的所有字体。在网上逛了一圈后发现了EnumFontFamiliesEx这个API好像就可以实现这个功能。这里将自己对这个API的理解做一个记录,算是对这块知识的一个总结吧。
API介绍
这里主要介绍的API就是EnumFontFamiliesEx以及它的回调函数EnumFontFamExProc。从MSDN的官方文档中可以看出,EnumFontFamiliesEx可以枚举出当前系统中符合特定字符集的所有字体。
EnumFontFamiliesEx的函数的原型如下:
int EnumFontFamiliesEx( _In_ HDC hdc, _In_ LPLOGFONT lpLogfont, _In_ FONTENUMPROC lpEnumFontFamExProc, _In_ LPARAM lParam, DWORD dwFlags );
在上面的参数中,hdc对应了设备上下文,可以直接根据GetDC得到。LPLOGFONT代表指向字体逻辑的指针,在进行字体遍历时,可以将该参数设置为NULL。lpEnumFontFamExProc回调函数地址,也就是EnumFontFamExProc这个函数的地址。lParam作为附加的参数用与字体信息一起传递给回调函数,本文中并未用到这个附加参数,因此直接设为0,。最后dwFlags未被使用,需要强制设置0。因此,在调用该函数时,其实就是将它所遍历到的字体信息传递给回调函数EnumFontFamExProc进行处理。既然这样,如果要统计当前系统中包含的所有字体,那么这个统计的操作就可以放在回调函数EnumFontFamExProc中进行(因为每进入一次回调函数EnumFontFamExProc,说明EnumFontFamiliesEx给它传入了一种系统字体)。既然需要在EnumFontFamExProc中处理统计逻辑,因此需要了解EnumFontFamExProc这个函数的原型。MSDN中给出了其标准的原型定义:
int CALLBACK EnumFontFamExProc( const LOGFONT *lpelfe, const TEXTMETRIC *lpntme, DWORD FontType, LPARAM lParam );
其中,lpelfe就对应了当前处理的字体信息,但是LOGFONT这个结构体中并不包含需要的字体名称信息。为了解决这个问题,需要进行一次转型操作。如MSDN中所说,
To obtain additional information about the font, you can cast the result as an ENUMLOGFONTEX or ENUMLOGFONTEXDV structure.
将LOGFONT* 转型为ENUMLOGFONTEX *。 而根据MSDN中对ENUMLOGFONTEX这个结构体的描述
typedef struct tagENUMLOGFONTEX { LOGFONT elfLogFont; TCHAR elfFullName[LF_FULLFACESIZE]; TCHAR elfStyle[LF_FACESIZE]; TCHAR elfScript[LF_FACESIZE]; } ENUMLOGFONTEX, *LPENUMLOGFONTEX;
可以由elfFullName这个成员得到当前字体的名称。因此,统计字体名称就可以直接从这个结构体中的elfFullName获取。到这里,实现这样一个功能来统计当前系统中所有的字体对应的步骤就变得十分清晰的了。
1. 调用EnumFontFamiliesEx依次遍历当前系统上的每一种字体 2. 在EnumFontFamiliesEx遍历到其中一种字体时,会将字体的信息传递给回调函EnumFontFamExProc。此时,回调函数负责处理传过来的字体,并将这部分信息解析到ENUMLOGFONTEX中。 3. 在EnumFontFamiliesEx函数中,添加字体统计逻辑(其实很简单,就是将elfFullName这个字符串放入一个容器中保存)
在了解了本文的实现机制后,就可以实际动手来验证这个方案了。
验证实例
首先定义一个通用类来进行Windows系统下字体统计功能,它的定义如下
/************************************************************************/ /* file : 设计单独的类来进行统计本机字体操作 * author : Huagang Li * date : 2014-8-25 21:40:49 * tips : 调用EnumFontFamiliesExProc回调函数遍历当前系统的所有字体 * blogs : http://www.cnblogs.com/lhglihuagang/ */ /************************************************************************/ #ifndef _SYS_FONT_H #define _SYS_FONT_H #include <windows.h> #include <string> #include <set> // // CWindowHelper 提供遍历统计Windows系统中字体的接口 class CWindowHelper { public: static std::set<std::wstring> sysFonts; // 用于在回调函数中保存当前遍历到的字体 static void GetSystemFonts(); static void ShowSysFonts(); private: static enum emFindFont { STOP_FIND = 0, CONTINUE_FIND = 1 }; static int CALLBACK EnumFontFamiliesExProc( ENUMLOGFONTEX *lpelfe, NEWTEXTMETRICEX *lpntme, int FontType, LPARAM lParam ); }; #endif
对应的接口实现如下:
#include "SysFonts.h" #include <tchar.h> std::set<std::wstring> CWindowHelper::sysFonts; int CALLBACK CWindowHelper::EnumFontFamiliesExProc( ENUMLOGFONTEX *lpelfe, NEWTEXTMETRICEX *lpntme, int FontType, LPARAM lParam ) { if (NULL == lpelfe) { return emFindFont::STOP_FIND; // 返回0停止遍历 } sysFonts.insert( lpelfe->elfFullName ); // 将遍历中得到的字体存入容器保存 return emFindFont::CONTINUE_FIND; // 返回1 代表继续进行下一轮遍历 } void CWindowHelper::GetSystemFonts() { HDC hdc = GetDC(NULL); LOGFONT logFont = { 0, 0, 0, 0, 0, 0, 0, 0, DEFAULT_CHARSET, 0, 0, 0, 0, L"" }; ::EnumFontFamiliesEx(hdc, &logFont, (FONTENUMPROC)EnumFontFamiliesExProc, 0, 0); ::ReleaseDC(NULL, hdc); } void CWindowHelper::ShowSysFonts() { std::wstring strRes; std::set<std::wstring>::const_iterator iter = sysFonts.begin(); while (iter != sysFonts.end()) { strRes = strRes + (*iter) + L" "; ++iter; } ::MessageBox(NULL, strRes.c_str(), _T("当前系统所有字体"), MB_OK); }
这个类的核心接口为GetSystemFonts。按照前文的实现逻辑:首先调用EnumFontFamiliesEx来遍历每一种字体,在定义响应的回调函数EnumFontFamiliesExProc来处理传入的每一种字体信息,同时在回调函数的实现体中添加字体统计逻辑。这里需要注意的是:回调函数返回值必须非0.如果返回值为0,那么遍历将会结束。因此为了能够遍历本系统中所有字体,应该返回一个非0值。
最后,测试程序的入口如下:
/************************************************************************/ /* file : 测试程序的主入口 * author : Huagang Li * date : 2014-8-25 21:25:24 * blogs : http://www.cnblogs.com/lhglihuagang/ */ /************************************************************************/ #include <windows.h> #include "SysFonts.h" int WINAPI WinMain( __in HINSTANCE hInstance, __in_opt HINSTANCE hPrevInstance, __in_opt LPSTR lpCmdLine, __in int nShowCmd ) { CWindowHelper::GetSystemFonts(); CWindowHelper::ShowSysFonts(); return EXIT_SUCCESS; }
这里,为了方便直接将所有的字体用MessageBox 显示出来了,并没有做可读性方面的优化。运行结果如下图1所示:
图1 运行后显示的所有字体
从图中可以看出,该程序统计了当前系统的字体。有一个奇怪的点是:统计的一部分字体前面带有@符号,如宋体和@宋体。至于这个@出现的原因,好像是和windows 7隐藏字体相关,具体可以参见http://stackoverflow.com/questions/11253827/too-many-fonts-when-enumerating-with-enumfontfamiliesex-function
结论
1. 通过EnumFontFamiliesEx和EnumFontFamExProc配合可以遍历系统中所有字体。
2. 上述方法得到的字体中会有重复,实现程序了为了显示单一的字体所以用Set进行了保存
3. 上述方法得到的字体部分存在@,对应了windows 7中的隐藏字体
参考链接
[1] http://msdn.microsoft.com/ZH-CN/library/windows/desktop/dd162620(v=vs.85).aspx
[2] http://msdn.microsoft.com/ZH-CN/library/windows/desktop/dd162618(v=vs.85).aspx
版权声明