摘录于《Windows程序(第5版,珍藏版).CHarles.Petzold 著》P807
字体枚举是从 GDI 获得该设备上所有可用字体列表的过程。程序可以选择其中一个字体或将它们显示在对话框中由用户进行选择。我将首先简要介绍枚举函数,然后介绍如何使用 ChooseFont 函数,它使得字体枚举对于应用程序来说不再那么必要。
17.4.1 枚举函数
在早期的 Windows 中,字体枚举需要使用 EnumFonts 函数:
EnumFonts(hdc, szTypeFace, EnumProc, pData);
程序可以枚举所有的字体(通过设置第二个参数为 NULL)或者只返回某些特定字体。第三个参数是一个枚举回调函数。第四个参数是一个可选参数,表示给那个枚举函数传递的一些额外数据。
对系统中的每种字体,GDI 都会调用一次回调函数,将 LOGFONT 和 TEXTMETRIC 结构传递给它,这两个结构定义了字体和表明字体类型的标志。
EnumFontFamilies 函数的作用是更好的枚举 Windows 3.1 下的 TrueType 字体:
EnumFontFamilies(hdc, szFaceName, EnumProc, pData);
通常第一次调用 EnumFontFamilies 函数时会将第二个参数设定为 NULL。然后,对于每一个字体系列(如 Times New Roman)程序都调用一次 EnumPorc 回调函数。接下来,应用程序会使用字体系列的每个字样名字再次调用 EnumFontFamilies,但每次都会使用一个不同的回调函数。对于该系列中的每个字体(如 Times New Roman Italic),GDI 都会调用第二次传入的回调函数。这个回调函数的参数包括:ENUMLOGFONT 结构(这是一个 LOGFONT 结构加上全名(full name)字段和样式(style)字段,例如,斜体的样式字段就是文本“Italic”,粗体的样式字段就是文本“Bold”)、TEXTMETRIC 结构(对于非 TrueType 字体)和 NEWTEXTMETRIC 结构(对于 TrueType 字体)。NEWTEXTMETRIC 结构比 TEXTMETRIC 结构多了 4 个字段。
如果开发在 32 位版本 Windows 上运行的应用程序,推荐使用 EnumFontFamiliesEx 函数:
EnumFontFamiliesEx(hdc, &logfont, EnumProc, pData, dwFlags);
该函数的第二个参数是一个指向 LOGFONT 结构的指针,它的 lfCharSet 和 lfFaceName 字段说明了需要枚举哪些字体。回调函数会以 ENUMLOGFONTEX 和 NEWTEXTMETRICEX 结构的形式获得有关每个字体的信息。
17.4.2 ChooseFont 对话框
我们在第 11 章介绍过一些关于 ChoosFont 通用对话框的知识。现在我们讲到字体枚举,ChooseFont 函数的内部工作原理就显而易见了。ChooseFont 函数只需要一个参数,即一个指向 CHOOSEFONT 结构的指针, 就是一个可显示出所有字体的对话框。ChooseFont 函数会返回一个 LOGFONT 结构,它是 CHOOSEFONT 结构的一部分,使你可以创建逻辑字体。
图 17-7 所示的 CHOOSEFONT 程序演示了如何使用 ChooseFont 函数,并显示了该函数定义的 LOGFONT 结构的一些字段。该程序显示的文本字符串和 PICKFONT 程序一样。
/*------------------------------------------------
CHOSFONT.C -- ChooseFont Demo
(c) Charles Petzold, 1998
------------------------------------------------*/
#include <Windows.h>
#include "resource.h"
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("ChosFont");
HWND hwnd;
MSG msg;
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = szAppName;
wndclass.lpszClassName = szAppName;
if (!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("This program requires Windows NT!"),
szAppName, MB_ICONERROR);
return 0;
}
hwnd = CreateWindow(szAppName, TEXT("ChooseFont"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static CHOOSEFONT cf;
static int cyChar;
static LOGFONT lf;
static TCHAR szText[] = TEXT("\x41\x42\x43\x44\x45 ")
TEXT("\x61\x62\x63\x64\x65 ")
TEXT("\xC0\xC1\xC2\xC3\xC4\xC5 ")
TEXT("\xE0\xE1\xE2\xE3\xE4\xE5 ")
#ifdef UNICODE
TEXT("\x0390\x0391\x0392\x0393\x0394\x0395 ")
TEXT("\x03B0\x03B1\x03B2\x03B3\x03B4\x03B5 ")
TEXT("\x0410\x0411\x0412\x0413\x0414\x0415 ")
TEXT("\x0430\x0431\x0432\x0433\x0434\x0435 ")
TEXT("\x5000\x5001\x5002\x5003\x5004")
#endif
;
HDC hdc;
int y;
PAINTSTRUCT ps;
TCHAR szBuffer[64];
TEXTMETRIC tm;
switch (message)
{
case WM_CREATE:
// Get text height
cyChar = HIWORD(GetDialogBaseUnits());
// Initialize the CHOOSEFONT structure
cf.lStructSize = sizeof(CHOOSEFONT);
cf.hwndOwner = hwnd;
cf.hDC = NULL;
cf.lpLogFont = &lf;
cf.iPointSize = 0;
cf.Flags = CF_INITTOLOGFONTSTRUCT |
CF_SCREENFONTS | CF_EFFECTS;
cf.rgbColors = 0;
cf.lCustData = 0;
cf.lCustData = NULL;
cf.lpTemplateName = NULL;
cf.hInstance = NULL;
cf.lpszStyle = NULL;
cf.nFontType = 0;
cf.nSizeMin = 0;
cf.nSizeMax = 0;
return 0;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDM_FONT:
if (ChooseFont(&cf))
InvalidateRect(hwnd, NULL, TRUE);
return 0;
}
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
// Display sample text using selected font
SelectObject(hdc, CreateFontIndirect(&lf));
GetTextMetrics(hdc, &tm);
SetTextColor(hdc, cf.rgbColors);
TextOut(hdc, 0, y = tm.tmExternalLeading, szText, lstrlen(szText));
// Display LOGFONT structure fields using system font
DeleteObject(SelectObject(hdc, GetStockObject(SYSTEM_FONT)));
SetTextColor(hdc, 0);
TextOut(hdc, 0, y += tm.tmHeight, szBuffer,
wsprintf(szBuffer, TEXT("lfHeight = %i"), lf.lfHeight));
TextOut(hdc, 0, y += cyChar, szBuffer,
wsprintf(szBuffer, TEXT("lfWidth = %i"), lf.lfWidth));
TextOut(hdc, 0, y += cyChar, szBuffer,
wsprintf(szBuffer, TEXT("lfEscapement = %i"),
lf.lfEscapement));
TextOut(hdc, 0, y += cyChar, szBuffer,
wsprintf(szBuffer, TEXT("LfOrientation = %i"),
lf.lfOrientation));
TextOut(hdc, 0, y += cyChar, szBuffer,
wsprintf(szBuffer, TEXT("lfWeight = %i"), lf.lfWeight));
TextOut(hdc, 0, y += cyChar, szBuffer,
wsprintf(szBuffer, TEXT("lfItalic = %i"), lf.lfItalic));
TextOut(hdc, 0, y += cyChar, szBuffer,
wsprintf(szBuffer, TEXT("lfUnderline = %i"), lf.lfUnderline));
TextOut(hdc, 0, y += cyChar, szBuffer,
wsprintf(szBuffer, TEXT("lfStrikeOut = %i"), lf.lfStrikeOut));
TextOut(hdc, 0, y += cyChar, szBuffer,
wsprintf(szBuffer, TEXT("lfCharSet = %i"), lf.lfCharSet));
TextOut(hdc, 0, y += cyChar, szBuffer,
wsprintf(szBuffer, TEXT("lfOutPrecision = %i"),
lf.lfOutPrecision));
TextOut(hdc, 0, y += cyChar, szBuffer,
wsprintf(szBuffer, TEXT("lfClipPrecision = %i"),
lf.lfClipPrecision));
TextOut(hdc, 0, y += cyChar, szBuffer,
wsprintf(szBuffer, TEXT("lfQuality = %i"), lf.lfQuality));
TextOut(hdc, 0, y += cyChar, szBuffer,
wsprintf(szBuffer, TEXT("lfPitchAndFamily = 0x%02X"),
lf.lfPitchAndFamily));
TextOut(hdc, 0, y += cyChar, szBuffer,
wsprintf(szBuffer, TEXT("lfFaceName = %s"), lf.lfFaceName));
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
CHOSFONT.RC
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
/
//
// Menu
//
CHOSFONT MENU
BEGIN
MENUITEM "&Font!", IDM_FONT
END
RESOURCE.H
// Microsoft Visual C++ 生成的包含文件。
// 供 ChosFont.rc 使用
//
#define IDM_FONT 40001
与多数通用对话框一样,CHOOSEFOT 结构中有一个 Flags(标志)字段,提供许多选项供你选择。CHOSFONT 中的 CF_INITTOLOGFONTSTRUCT 标志会让 Windows 根据 ChooseFont 结构中的 LOGFONT 结构,来初始化对话框的选项。你可以通过设定 Flags 字段来指定:仅适用 TrueType 字体(CF_TTONLY);等宽字体(CF_FIXEDPITCHONLY);或者非符号字体(CF_SCRIPTSONLY)。你还可以显示屏幕字体(CF_SCREENFONTS)、打印机字体(CF_PRINTERFONTS)或者两者兼有(CF_BOTH)。在后两种情况下,CHOOSEFONT 结构的 hDC 字段必须引用一个打印机的设备环境。这里的 CHOSFONT 程序使用了 CF_SCREENFONTS 标志。
CF_EFFECTS 标志 (CHOSFONT 程序使用的第三个标志)使得对话框中包含下划线和删除线复选框,还允许用户选择文本的颜色。在代码中实现改变文本颜色并不十分困难,所以有兴趣的话你可以尝试一下。
现在来看一下 ChoosFont 函数显示的 Font 对话框中的 Script(脚本)字段。它允许用户为某个特定字体的选择一个合适的字符集;LOGFONT 结构会返回被选中字符集的 ID。
ChooseFont 函数根据像素点的大小,使用逻辑英寸为单位来计算 lfHeight 字段。例如,让我们假设你已经从 Windows 的显示属性对话框中安装了 Small Font(小字体)。这意味着,在使用视频显示设备环境,并使用 LOGPIXELSY 参数时,调用 GetDeviceCaps 函数将会返回 96。如果实际需要的是 1 英寸高的字体,则应该使用 ChooseFont 函数并选择 72 点值的 Times Roman 字体。当 ChooseFont 函数返回时,LOGFONT 结构的 lfHeight 字段将等于 -96(注意这里有个负号),即字体的点值大小相当于 96 像素,也就是一个逻辑英寸。
很好,这也许正是我们需要的。但是,要记住以下几点。
- 如果在 Windows NT 下设置使用的是公制映射模式(metric mapping mode),逻辑坐标和字体的物理大小将会不同。例如,如果你在文本旁边画一个基于公制映射模式的标尺,它不会和字体一样高。你应该使用前面介绍过的逻辑单位(Logical Twip)映射模式来绘制与实际字体大小 一致的图形。
- 如果你想要使用的任何非 MM_TEXT 映射模式,请务必保证在将字体选入设备环境和显示文本时,没有设置任何映射模式。否则,GDI 会认为 LOGFONT 结构的 lfHeight 字段使用的是逻辑坐标。
- ChooseFont 函数设置的 LOGFONT 结构的 lfHeight 字段总是使用像素为单位,且它仅适合视频显示。当你为打印机的设备环境创建字体时,必须调整 lfHeight 的值。ChooseFont 函数还通过使用 CHOOSEFONT 结构的 hDC 字段,在对话框中显示可用的打印机字体。这个设备环境句柄不会影响 lfHeight 的值。
幸运的是,CHOOSEFONT 结构包含一个 iPointSize 字段。该字段表示所选择的字体的点值大小,以 1/10 点为单位。无论设备环境和映射模式是什么,你都可以随时将这个字段转换为逻辑尺寸供 lfHeight 字段使用。在 EZFONT.C 文件中可以找到适当的代码,你可以根据需要简化它。
另一个使用 ChooseFont 的程序是 UNICHARS,如图 17-8 所示。这个程序可以帮助你查看一个字体的所有字符,这对于研究 Lucida Sans Unicode 字体(ChooseFont 默认使用该字体)或 Bitstream CyberBit 字体特别有用。UNICHARS 总是调用 TextOutW 函数显示字符,所以你可以在 Windows NT 或 Windows 98 上运行这个程序。
/*------------------------------------------------
UNICHARS.C -- Displays 16-bit character codes
(c) Charles Petzold, 1998
------------------------------------------------*/
#include <Windows.h>
#include "resource.h"
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("UniChars");
HWND hwnd;
MSG msg;
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = szAppName;
wndclass.lpszClassName = szAppName;
if (!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("This program requires Windows NT!"),
szAppName, MB_ICONERROR);
return 0;
}
hwnd = CreateWindow(szAppName, TEXT("Unicode Characters"),
WS_OVERLAPPEDWINDOW | WS_VSCROLL,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static CHOOSEFONT cf;
static int iPage;
static LOGFONT lf;
HDC hdc;
int cxChar, cyChar, x, y, i, cxLabels;
PAINTSTRUCT ps;
SIZE size;
TCHAR szBuffer[64];
TEXTMETRIC tm;
WCHAR ch;
switch (message)
{
case WM_CREATE:
hdc = GetDC(hwnd);
lf.lfHeight = - GetDeviceCaps(hdc, LOGPIXELSY) / 6; // 12 points
lstrcpy(lf.lfFaceName, TEXT("Lucida Sans Unicode"));
ReleaseDC(hwnd, hdc);
cf.lStructSize = sizeof(CHOOSEFONT);
cf.hwndOwner = hwnd;
cf.lpLogFont = &lf;
cf.Flags = CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS;
SetScrollRange(hwnd, SB_VERT, 0, 255, FALSE);
SetScrollPos(hwnd, SB_VERT, iPage, TRUE);
return 0;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDM_FONT:
if (ChooseFont(&cf))
InvalidateRect(hwnd, NULL, TRUE);
return 0;
}
return 0;
case WM_VSCROLL:
switch (LOWORD(wParam))
{
case SB_LINEUP: iPage -= 1; break;
case SB_LINEDOWN: iPage += 1; break;
case SB_PAGEUP: iPage -= 16; break;
case SB_PAGEDOWN: iPage += 16; break;
case SB_THUMBPOSITION: iPage = HIWORD(wParam); break;
default:
return 0;
}
iPage = max(0, min(iPage, 255));
SetScrollPos(hwnd, SB_VERT, iPage, TRUE);
InvalidateRect(hwnd, NULL, TRUE);
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
SelectObject(hdc, CreateFontIndirect(&lf));
GetTextMetrics(hdc, &tm);
cxChar = tm.tmMaxCharWidth;
cyChar = tm.tmHeight + tm.tmExternalLeading;
cxLabels = 0;
for (i = 0; i < 16; i++)
{
wsprintf(szBuffer, TEXT(" 000%1X: "), i);
GetTextExtentPoint(hdc, szBuffer, 7, &size);
cxLabels = max(cxLabels, size.cx);
}
for (y = 0; y < 16; y++)
{
wsprintf(szBuffer, TEXT(" %03X_: "), 16 * iPage + y);
TextOut(hdc, 0, y * cyChar, szBuffer, 7);
for (x = 0; x < 16; x++)
{
ch = (WCHAR)(256 * iPage + 16 * y + x);
TextOutW(hdc, x * cxChar + cxLabels,
y * cyChar, &ch, 1);
}
}
DeleteObject(SelectObject(hdc, GetStockObject(SYSTEM_FONT)));
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
UNICHARS.RC
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
/
//
// Menu
//
UNICHARS MENU
BEGIN
MENUITEM "&Font!", IDM_FONT
END
RESOURCE.H
// Microsoft Visual C++ 生成的包含文件。
// 供 Unichars.rc 使用
//
#define IDM_FONT 40001