摘录于《Windows程序(第5版,珍藏版).CHarles.Petzold 著》P778
现在我们已经理解了逻辑英寸和逻辑缇的概念,让我们开始讨论逻辑字体。
一个逻辑字体是一个 GDI 对象,它的尺寸控点存储在一个 HFONT 类型的变量中,一个逻辑字体是对一种字体的具体描述。如同逻辑画笔和逻辑画刷一样,它是一个抽象的对象,只有当应用程序调用 SelectObject 将它选入设备环境时,它才成为一个真实的对象。例如,我可以为一个逻辑画笔指定任意的颜色,但是只有我将该画笔选入设备环境时,Windows 才将其转换为当前设备中可以用的纯色。也只有此时,Windows 才知道当前设备环境可以显示哪些颜色。
17.3.1 逻辑字体的创建和选择
调用 CreateFont 函数或 CreateFontIndirect 函数可以创建逻辑字体。CreateFontIndirect 函数接受一个指向 LOGFONT 结构的指针,该结构含有 14 个字段。CreateFont 函数是仅有的两个可以创建逻辑字体的函数(我之所以强调这一点,是因为 Windows 中还有许多其他字体操作函数)。因为 14 个字段很难记,所以一般很少使用 CreateFont 函数。这里,我主要讲解如何使用 CreateFontIndirect 函数。
调用 CreateFontIndirect 函数前,需要先定义 LOGFONT 结构中的字段,有以下三种基本方式。
- 简单地将 LOGFONT 结构的字段设定为所需字体的特征。在这种情况下,在调用 SelectObject 函数时,Windows 会使用一种【字体映射】的算法从当前设备上可用的字体集中选择与这些特征最匹配的字体,然后返回这个字体。由于无法预知显示器或打印机上有哪些可用字体,所以返回结果与最初的要求可能有相当大的差别。
- 先枚举设备上的所有可用字体,然后从中作出选择。还可以把枚举的结构显示在对话框里让用户作决定。我会在本章稍后讨论字体枚举函数。不过,现在已经不流行这种方法,因为使用下面的第三种方法也可以进行字体枚举。
- 我在第 11 章曾讨论过 ChooseFont 函数,简单的调用这个函数会返回一个 LOGFONT 结构,然后使用该结构直接创建逻辑字体。
在本章中,我会展示如何使用第一种和第三种方法来创建逻辑字体。
下面是创建、选择和删除逻辑字体的具体过程。
- 调用 CreateFont 函数或 CreateFontIndirect 函数创建逻辑字体,这些函数会返回一个 HFONT 类型的逻辑字体句柄。
- 调用 SelectObject 函数将逻辑字体选入设备环境。Windows 会选择与逻辑字体最匹配的可用字体。
- 调用 GetTextMetrics 函数(还可能会用到其他一些函数)来获得实际被使用字体的大小和其他特征。当该字体被选入设备环境后,就可以利用这些信息来适当地设定文字输出是的间距。
- 在使用完字体后,调用 DeleteObject 函数删除逻辑字体。如果某个有效的设备环境还选中该字体,则不要删除该字体,也不要删除库存字体。
调用 GetTextFace 函数可以让程序知道目前被选入设备环境的字样名称:
GetTextFace (hdc, sizeof(szFaceName) / sizeof(TCHAR), szFaceName);
详细的字体信息可以通过调用 GetTextMetrics 函数得到:
GetTextMetrics(hdc, &textmetric);
其中,textmetrics 是 TEXTMETRIC 类型的变量,它具有 20 个字段。
稍后我将详细讨论 LOGFONT 和 TEXTMETRIC 结构的一些字段。这两个结构有一些相似的字段,所以很容易混淆。现在只需知道,LOGFONT 是用于定义逻辑字体的,而 TEXTMETRIC 存放着目前被选入设备环境中字体的信息。
17.3.2 PICKFONT 程序
图 17-1 所示的 PICKFONT 范例程序定义了 LOGFONT 结构的许多字段。这个程序创建了一个逻辑字体,并在该逻辑字体被选入设备环境后显示了真实被使用的字体的特征。这是个很有用的程序,通过它我们可以了解 Windows 将逻辑字体映射为真实字体的过程。
/*---------------------------------------------
PICKFONT.CPP -- Create Logical Font
(c) Charles Petzold, 1998
----------------------------------------------*/
#include <Windows.h>
#include "resource.h"
// Structure shared between main window and dialog box
typedef struct
{
int iDevice, iMapMode;
BOOL fMatchAspect;
BOOL fAdvGraphics;
LOGFONT lf;
TEXTMETRIC tm;
TCHAR szFaceName[LF_FULLFACESIZE];
}
DLGPARAMS;
// Formatting for BCHAR fields of TEXTMETRIC structure
#ifdef UNICODE
#define BCHARFORM TEXT ("0x%04X")
#else
#define BCHARFORM TEXT ("0x%02X")
#endif
// Global variables
HWND hdlg;
TCHAR szAppName[] = TEXT("PickFont");
// Forward declarations of functions
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM);
void SetLogFontFromFields(HWND hdlg, DLGPARAMS * pdp);
void SetFieldsFromTextMetric(HWND hdlg, DLGPARAMS * pdp);
void MySetMapMode(HDC hdc, int iMapMode);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
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("PickFont: Create Logical Font"),
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
if (hdlg == 0 || !IsDialogMessage(hdlg, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static DLGPARAMS dp;
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;
PAINTSTRUCT ps;
RECT rect;
switch (message)
{
case WM_CREATE:
dp.iDevice = IDM_DEVICE_SCREEN;
hdlg = CreateDialogParam(((LPCREATESTRUCT)lParam)->hInstance,
szAppName, hwnd, DlgProc, (LPARAM)&dp);
return 0;
case WM_SETFOCUS:
SetFocus(hdlg);
return 0;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDM_DEVICE_SCREEN:
case IDM_DEVICE_PRINTER:
CheckMenuItem(GetMenu(hwnd), dp.iDevice, MF_UNCHECKED);
dp.iDevice = LOWORD(wParam);
CheckMenuItem(GetMenu(hwnd), dp.iDevice, MF_CHECKED);
SendMessage(hwnd, WM_COMMAND, IDOK, 0);
return 0;
}
break;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
// Set graphics mode so escapement works in Windows NT
SetGraphicsMode(hdc, dp.fAdvGraphics ? GM_ADVANCED : GM_COMPATIBLE);
// Set the mapping mode and the mapper flag
MySetMapMode(hdc, dp.iMapMode);
SetMapperFlags(hdc, dp.fMatchAspect);
// Find the point to begin drawing text
GetClientRect(hdlg, &rect);
rect.bottom += 1;
DPtoLP(hdc, (PPOINT)&rect, 2);
// Create and select the font; display the text
SelectObject(hdc, CreateFontIndirect(&dp.lf));
TextOut(hdc, rect.left, rect.bottom, szText, lstrlen(szText));
DeleteObject(SelectObject(hdc, GetStockObject(SYSTEM_FONT)));
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
BOOL CALLBACK DlgProc(HWND hdlg, UINT message, WPARAM wParam, LPARAM lParam)
{
static DLGPARAMS * pdp;
static PRINTDLG pd = { sizeof(PRINTDLG) };
HDC hdcDevice;
HFONT hFont;
switch (message)
{
case WM_INITDIALOG:
// Save pointer to dialog-paramters structure in WndProc
pdp = (DLGPARAMS *)lParam;
SendDlgItemMessage(hdlg, IDC_LF_FACENAME, EM_LIMITTEXT,
LF_FACESIZE - 1, 0);
CheckRadioButton(hdlg, IDC_OUT_DEFAULT, IDC_OUT_OUTLINE,
IDC_OUT_DEFAULT);
CheckRadioButton(hdlg, IDC_DEFAULT_QUALITY, IDC_PROOF_QUALITY,
IDC_DEFAULT_QUALITY);
CheckRadioButton(hdlg, IDC_DEFAULT_PITCH, IDC_VARIABLE_PITCH,
IDC_DEFAULT_PITCH);
CheckRadioButton(hdlg, IDC_FF_DONTCARE, IDC_FF_DECORATIVE,
IDC_FF_DONTCARE);
CheckRadioButton(hdlg, IDC_MM_TEXT, IDC_MM_LOGTWIPS,
IDC_MM_TEXT);
SendMessage(hdlg, WM_COMMAND, IDOK, 0);
// fall through
case WM_SETFOCUS:
SetFocus(GetDlgItem(hdlg, IDC_LF_HEIGHT));
return FALSE;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDC_CHARSET_HELP:
MessageBox(hdlg,
TEXT("0 = Ansi\n")
TEXT("1 = Default\n")
TEXT("2 = Symbol\n")
TEXT("128 = Shift JIS (Japanese)\n")
TEXT("129 = Hangul (Korean)\n")
TEXT("130 = Johab (Korean)\n")
TEXT("134 = GB 2312 (Simplified Chinese)\n")
TEXT("136 = Chinese Big 5 (Traditional Chinese)\n")
TEXT("177 = Hebrew\n")
TEXT("178 = Arabic\n")
TEXT("161 = Greek\n")
TEXT("162 = Turkish\n")
TEXT("163 = Vietnamese\n")
TEXT("204 = Russian\n")
TEXT("222 = Thai\n")
TEXT("238 = East European\n")
TEXT("255 = OEM"),
szAppName, MB_OK | MB_ICONINFORMATION);
return TRUE;
// These radio buttons set the lfOutPrecision field
case IDC_OUT_DEFAULT:
pdp->lf.lfOutPrecision = OUT_DEFAULT_PRECIS;
return TRUE;
case IDC_OUT_STRING:
pdp->lf.lfOutPrecision = OUT_STRING_PRECIS;
return TRUE;
case IDC_OUT_CHARACTER:
pdp->lf.lfOutPrecision = OUT_CHARACTER_PRECIS;
return TRUE;
case IDC_OUT_STROKE:
pdp->lf.lfOutPrecision = OUT_STROKE_PRECIS;
return TRUE;
case IDC_OUT_TT:
pdp->lf.lfOutPrecision = OUT_TT_PRECIS;
return TRUE;
case IDC_OUT_DEVICE:
pdp->lf.lfOutPrecision = OUT_DEVICE_PRECIS;
return TRUE;
case IDC_OUT_RASTER:
pdp->lf.lfOutPrecision = OUT_RASTER_PRECIS;
return TRUE;
case IDC_OUT_TT_ONLY:
pdp->lf.lfOutPrecision = OUT_TT_ONLY_PRECIS;
return TRUE;
case IDC_OUT_OUTLINE:
pdp->lf.lfOutPrecision = OUT_OUTLINE_PRECIS;
return TRUE;
// These three radio buttons set the lfQuality field
case IDC_DEFAULT_QUALITY:
pdp->lf.lfQuality = DEFAULT_QUALITY;
return TRUE;
case IDC_DRAFT_QUALITY:
pdp->lf.lfQuality = DRAFT_QUALITY;
return TRUE;
case IDC_PROOF_QUALITY:
pdp->lf.lfQuality = PROOF_QUALITY;
return TRUE;
// These three radio buttons set the lower nibble
// of the lfPitchAndFamily field
case IDC_DEFAULT_PITCH:
pdp->lf.lfPitchAndFamily = (BYTE)
((0xF0 & pdp->lf.lfPitchAndFamily) | DEFAULT_PITCH) ;
return TRUE ;
case IDC_FIXED_PITCH:
pdp->lf.lfPitchAndFamily = (BYTE)
((0xF0 & pdp->lf.lfPitchAndFamily) | FIXED_PITCH);
return TRUE;
case IDC_VARIABLE_PITCH:
pdp->lf.lfPitchAndFamily = (BYTE)
((0xF0 & pdp->lf.lfPitchAndFamily) | VARIABLE_PITCH);
return TRUE;
// These six radio buttons set the upper nibble
// of the lfPitchAndFamily field
case IDC_FF_DONTCARE:
pdp->lf.lfPitchAndFamily = (BYTE)
((0x0F & pdp->lf.lfPitchAndFamily) | FF_DONTCARE);
return TRUE;
case IDC_FF_ROMAN:
pdp->lf.lfPitchAndFamily = (BYTE)
((0x0F & pdp->lf.lfPitchAndFamily) | FF_ROMAN);
return TRUE;
case IDC_FF_SWISS:
pdp->lf.lfPitchAndFamily = (BYTE)
((0x0F & pdp->lf.lfPitchAndFamily) | FF_SWISS);
return TRUE;
case IDC_FF_MODERN:
pdp->lf.lfPitchAndFamily = (BYTE)
((0x0F & pdp->lf.lfPitchAndFamily) | FF_MODERN);
return TRUE;
case IDC_FF_SCRIPT:
pdp->lf.lfPitchAndFamily = (BYTE)
((0x0F & pdp->lf.lfPitchAndFamily) | FF_SCRIPT);
return TRUE;
case IDC_FF_DECORATIVE:
pdp->lf.lfPitchAndFamily = (BYTE)
((0x0F & pdp->lf.lfPitchAndFamily) | FF_DECORATIVE);
return TRUE;
// Mapping mode:
case IDC_MM_TEXT:
case IDC_MM_LOMETRIC:
case IDC_MM_HIMETRIC:
case IDC_MM_LOENGLISH:
case IDC_MM_HIENGLISH:
case IDC_MM_TWIPS:
case IDC_MM_LOGTWIPS:
pdp->iMapMode = LOWORD(wParam);
return TRUE;
// OK button pressed
// -----------------
case IDOK:
// Get LOGFONT structure
SetLogFontFromFields(hdlg, pdp);
// Set Match-Aspect and Advanced Graphics flags
pdp->fMatchAspect = IsDlgButtonChecked(hdlg, IDC_MATCH_ASPECT);
pdp->fAdvGraphics = IsDlgButtonChecked(hdlg, IDC_ADV_GRAPHICS);
// Get Information Context
if (pdp->iDevice == IDM_DEVICE_SCREEN)
{
hdcDevice = CreateIC(TEXT("DISPLAY"), NULL, NULL, NULL);
}
else
{
pd.hwndOwner = hdlg;
pd.Flags = PD_RETURNDEFAULT | PD_RETURNIC;
pd.hDevNames = NULL;
pd.hDevMode = NULL;
PrintDlg(&pd);
hdcDevice = pd.hDC;
}
// Set the mapping mode and the mapper flag
MySetMapMode(hdcDevice, pdp->iMapMode);
SetMapperFlags(hdcDevice, pdp->fMatchAspect);
// Create font and select it into IC
hFont = CreateFontIndirect(&pdp->lf);
SelectObject(hdcDevice, hFont);
// Get the text metrics and face name
GetTextMetrics(hdcDevice, &pdp->tm);
GetTextFace(hdcDevice, LF_FULLFACESIZE, pdp->szFaceName);
DeleteDC(hdcDevice);
DeleteObject(hFont);
// Update dialog fields and invalidate main window
SetFieldsFromTextMetric(hdlg, pdp);
InvalidateRect(GetParent(hdlg), NULL, TRUE);
return TRUE;
}
break;
}
return FALSE;
}
void SetLogFontFromFields(HWND hdlg, DLGPARAMS * pdp)
{
pdp->lf.lfHeight = GetDlgItemInt (hdlg, IDC_LF_HEIGHT, NULL, TRUE) ;
pdp->lf.lfWidth = GetDlgItemInt (hdlg, IDC_LF_WIDTH, NULL, TRUE) ;
pdp->lf.lfEscapement = GetDlgItemInt (hdlg, IDC_LF_ESCAPE, NULL, TRUE) ;
pdp->lf.lfOrientation = GetDlgItemInt (hdlg, IDC_LF_ORIENT, NULL, TRUE) ;
pdp->lf.lfWeight = GetDlgItemInt (hdlg, IDC_LF_WEIGHT, NULL, TRUE) ;
pdp->lf.lfCharSet = GetDlgItemInt (hdlg, IDC_LF_CHARSET, NULL, FALSE) ;
pdp->lf.lfItalic =
IsDlgButtonChecked(hdlg, IDC_LF_ITALIC) == BST_CHECKED;
pdp->lf.lfUnderline =
IsDlgButtonChecked(hdlg, IDC_LF_UNDER) == BST_CHECKED;
pdp->lf.lfStrikeOut =
IsDlgButtonChecked(hdlg, IDC_LF_STRIKE) == BST_CHECKED;
GetDlgItemText(hdlg, IDC_LF_FACENAME, pdp->lf.lfFaceName, LF_FACESIZE);
}
void SetFieldsFromTextMetric(HWND hdlg, DLGPARAMS * pdp)
{
TCHAR szBuffer[10];
TCHAR * szYes = TEXT("Yes");
TCHAR * szNo = TEXT("No");
TCHAR * szFamily[] = { TEXT("Don't Know"), TEXT("Roman"),
TEXT("Swiss"), TEXT("Modern"),
TEXT("Script"), TEXT("Decorative"),
TEXT("Undefined") };
SetDlgItemInt(hdlg, IDC_TM_HEIGHT, pdp->tm.tmHeight, TRUE);
SetDlgItemInt(hdlg, IDC_TM_ASCENT, pdp->tm.tmAscent, TRUE);
SetDlgItemInt(hdlg, IDC_TM_DESCENT, pdp->tm.tmDescent, TRUE);
SetDlgItemInt(hdlg, IDC_TM_INTLEAD, pdp->tm.tmInternalLeading, TRUE);
SetDlgItemInt(hdlg, IDC_TM_EXTLEAD, pdp->tm.tmExternalLeading, TRUE);
SetDlgItemInt(hdlg, IDC_TM_AVECHAR, pdp->tm.tmAveCharWidth, TRUE);
SetDlgItemInt(hdlg, IDC_TM_MAXCHAR, pdp->tm.tmMaxCharWidth, TRUE);
SetDlgItemInt(hdlg, IDC_TM_WEIGHT, pdp->tm.tmWeight, TRUE);
SetDlgItemInt(hdlg, IDC_TM_OVERHANG, pdp->tm.tmOverhang, TRUE);
SetDlgItemInt(hdlg, IDC_TM_DIGASPX, pdp->tm.tmDigitizedAspectX, TRUE);
SetDlgItemInt(hdlg, IDC_TM_DIGASPY, pdp->tm.tmDigitizedAspectY, TRUE);
wsprintf(szBuffer, BCHARFORM, pdp->tm.tmFirstChar);
SetDlgItemText(hdlg, IDC_TM_FIRSTCHAR, szBuffer);
wsprintf(szBuffer, BCHARFORM, pdp->tm.tmLastChar);
SetDlgItemText(hdlg, IDC_TM_LASTCHAR, szBuffer);
wsprintf(szBuffer, BCHARFORM, pdp->tm.tmDefaultChar);
SetDlgItemText(hdlg, IDC_TM_DEFCHAR, szBuffer);
wsprintf(szBuffer, BCHARFORM, pdp->tm.tmBreakChar);
SetDlgItemText(hdlg, IDC_TM_BREAKCHAR, szBuffer);
SetDlgItemText(hdlg, IDC_TM_ITALIC, pdp->tm.tmItalic ? szYes : szNo);
SetDlgItemText(hdlg, IDC_TM_UNDER, pdp->tm.tmUnderlined ? szYes : szNo);
SetDlgItemText(hdlg, IDC_TM_STRUCK, pdp->tm.tmStruckOut ? szYes : szNo);
SetDlgItemText(hdlg, IDC_TM_VARIABLE,
TMPF_FIXED_PITCH & pdp->tm.tmPitchAndFamily ? szYes : szNo);
SetDlgItemText(hdlg, IDC_TM_VECTOR,
TMPF_VECTOR & pdp->tm.tmPitchAndFamily ? szYes : szNo);
SetDlgItemText(hdlg, IDC_TM_TRUETYPE,
TMPF_TRUETYPE & pdp->tm.tmPitchAndFamily ? szYes : szNo);
SetDlgItemText(hdlg, IDC_TM_DEVICE,
TMPF_DEVICE & pdp->tm.tmPitchAndFamily ? szYes : szNo);
SetDlgItemText(hdlg, IDC_TM_FAMILY,
szFamily[min(6, pdp->tm.tmPitchAndFamily >> 4)]);
SetDlgItemInt(hdlg, IDC_TM_CHARSET, pdp->tm.tmCharSet, FALSE);
SetDlgItemText(hdlg, IDC_TM_FACENAME, pdp->szFaceName);
}
void MySetMapMode(HDC hdc, int iMapMode)
{
switch (iMapMode)
{
case IDC_MM_TEXT: SetMapMode(hdc, MM_TEXT); break;
case IDC_MM_LOMETRIC: SetMapMode(hdc, MM_LOMETRIC); break;
case IDC_MM_HIMETRIC: SetMapMode(hdc, MM_HIMETRIC); break;
case IDC_MM_LOENGLISH: SetMapMode(hdc, MM_LOENGLISH); break;
case IDC_MM_HIENGLISH: SetMapMode(hdc, MM_HIENGLISH); break;
case IDC_MM_TWIPS: SetMapMode(hdc, MM_TWIPS); break;
case IDC_MM_LOGTWIPS:
SetMapMode(hdc, MM_ANISOTROPIC);
SetWindowExtEx(hdc, 1440, 1440, NULL);
SetViewportExtEx(hdc, GetDeviceCaps(hdc, LOGPIXELSX),
GetDeviceCaps(hdc, LOGPIXELSY), NULL);
break;
}
}
PickFont.RC (excerpts)
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
/
//
// Dialog
//
PICKFONT DIALOG 0, 0, 348, 308
STYLE DS_SETFONT | WS_CHILD | WS_VISIBLE | WS_BORDER
FONT 8, "MS Sans Serif"
BEGIN
LTEXT "&Height:",IDC_STATIC,8,10,44,8
EDITTEXT IDC_LF_HEIGHT,64,8,24,12,ES_AUTOHSCROLL
LTEXT "&Width",IDC_STATIC,8,26,44,8
EDITTEXT IDC_LF_WIDTH,64,24,24,12,ES_AUTOHSCROLL
LTEXT "Escapement:",IDC_STATIC,8,42,44,8
EDITTEXT IDC_LF_ESCAPE,64,40,24,12,ES_AUTOHSCROLL
LTEXT "Orientation:",IDC_STATIC,8,58,44,8
EDITTEXT IDC_LF_ORIENT,64,56,24,12,ES_AUTOHSCROLL
LTEXT "Weight:",IDC_STATIC,8,74,44,8
EDITTEXT IDC_LF_WEIGHT,64,74,24,12,ES_AUTOHSCROLL
GROUPBOX "Mapping Mode",IDC_STATIC,97,3,96,90,WS_GROUP
CONTROL "Text",IDC_MM_TEXT,"Button",BS_AUTORADIOBUTTON,104,13,56,8
CONTROL "Low Metric",IDC_MM_LOMETRIC,"Button",BS_AUTORADIOBUTTON,104,24,56,8
CONTROL "High Metric",IDC_MM_HIMETRIC,"Button",BS_AUTORADIOBUTTON,104,35,56,8
CONTROL "Low English",IDC_MM_LOENGLISH,"Button",BS_AUTORADIOBUTTON,104,46,56,8
CONTROL "High English",IDC_MM_HIENGLISH,"Button",BS_AUTORADIOBUTTON,104,57,56,8
CONTROL "Twips",IDC_MM_TWIPS,"Button",BS_AUTORADIOBUTTON,104,68,56,8
CONTROL "Logical Twips",IDC_MM_LOGTWIPS,"Button",BS_AUTORADIOBUTTON,104,79,64,8
CONTROL "Italic",IDC_LF_ITALIC,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,90,48,12
CONTROL "Underline",IDC_LF_UNDER,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,104,48,12
CONTROL "Strike Out",IDC_LF_STRIKE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,118,48,12
CONTROL "Match Aspect",IDC_MATCH_ASPECT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,60,104,62,8
CONTROL "Adv Grfx Mode",IDC_ADV_GRAPHICS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,60,118,62,8
LTEXT "Character Set:",IDC_STATIC,8,137,46,8
EDITTEXT IDC_LF_CHARSET,58,135,24,12,ES_AUTOHSCROLL
PUSHBUTTON "?",IDC_CHARSET_HELP,90,135,14,14
GROUPBOX "Quality",IDC_STATIC,132,98,62,48,WS_GROUP
CONTROL "Default",IDC_DEFAULT_QUALITY,"Button",BS_AUTORADIOBUTTON,136,110,40,8
CONTROL "Draft",IDC_DRAFT_QUALITY,"Button",BS_AUTORADIOBUTTON,136,122,40,8
CONTROL "Proof",IDC_PROOF_QUALITY,"Button",BS_AUTORADIOBUTTON,136,134,40,8
LTEXT "Face Name:",IDC_STATIC,8,154,44,8
EDITTEXT IDC_LF_FACENAME,58,152,136,12,ES_AUTOHSCROLL
GROUPBOX "Output Precision",IDC_STATIC,8,166,118,133,WS_GROUP
CONTROL "OUT_DEFAULT_PRECIS",IDC_OUT_DEFAULT,"Button",BS_AUTORADIOBUTTON,12,178,112,8
CONTROL "OUT_STRING_PRECIS",IDC_OUT_STRING,"Button",BS_AUTORADIOBUTTON,12,191,112,8
CONTROL "OUT_CHARACTER_PRECIS",IDC_OUT_CHARACTER,"Button",BS_AUTORADIOBUTTON,12,204,112,8
CONTROL "OUT_STROKE_PRECIS",IDC_OUT_STROKE,"Button",BS_AUTORADIOBUTTON,12,217,112,8
CONTROL "OUT_TT_PRECIS",IDC_OUT_TT,"Button",BS_AUTORADIOBUTTON,12,230,112,8
CONTROL "OUT_DEVICE_PRECIS",IDC_OUT_DEVICE,"Button",BS_AUTORADIOBUTTON,12,243,112,8
CONTROL "OUT_RASTER_PRECIS",IDC_OUT_RASTER,"Button",BS_AUTORADIOBUTTON,12,256,112,8
CONTROL "OUT_TT_ONLY_PRECIS",IDC_OUT_TT_ONLY,"Button",BS_AUTORADIOBUTTON,12,269,112,8
CONTROL "OUT_OUTLINE_PRECIS",IDC_OUT_OUTLINE,"Button",BS_AUTORADIOBUTTON,12,282,112,8
GROUPBOX "Pitch",IDC_STATIC,132,166,62,50,WS_GROUP
CONTROL "Default",IDC_DEFAULT_PITCH,"Button",BS_AUTORADIOBUTTON,137,176,52,8
CONTROL "Fixed",IDC_FIXED_PITCH,"Button",BS_AUTORADIOBUTTON,137,189,52,8
CONTROL "Variable",IDC_VARIABLE_PITCH,"Button",BS_AUTORADIOBUTTON,137,203,52,8
GROUPBOX "Family",IDC_STATIC,132,218,62,82,WS_GROUP
CONTROL "Don't Care",IDC_FF_DONTCARE,"Button",BS_AUTORADIOBUTTON,137,229,52,8
CONTROL "Roman",IDC_FF_ROMAN,"Button",BS_AUTORADIOBUTTON,137,241,52,8
CONTROL "Swiss",IDC_FF_SWISS,"Button",BS_AUTORADIOBUTTON,137,253,52,8
CONTROL "Modern",IDC_FF_MODERN,"Button",BS_AUTORADIOBUTTON,137,265,52,8
CONTROL "Script",IDC_FF_SCRIPT,"Button",BS_AUTORADIOBUTTON,137,277,52,8
CONTROL "Decorative",IDC_FF_DECORATIVE,"Button",BS_AUTORADIOBUTTON,137,289,52,8
DEFPUSHBUTTON "OK",IDOK,247,286,50,14
GROUPBOX "Text Metrics",IDC_STATIC,201,2,140,272,WS_GROUP
LTEXT "Height:",IDC_STATIC,207,12,64,8
LTEXT "0",IDC_TM_HEIGHT,281,12,44,8
LTEXT "Ascent:",IDC_STATIC,207,22,64,8
LTEXT "0",IDC_TM_ASCENT,281,22,44,8
LTEXT "Descent:",IDC_STATIC,207,32,64,8
LTEXT "0",IDC_TM_DESCENT,281,32,44,8
LTEXT "Internal Leading:",IDC_STATIC,207,42,64,8
LTEXT "0",IDC_TM_INTLEAD,281,42,44,8
LTEXT "External Leading:",IDC_STATIC,207,52,64,8
LTEXT "0",IDC_TM_EXTLEAD,281,52,44,8
LTEXT "Ave Char Width:",IDC_STATIC,207,62,64,8
LTEXT "0",IDC_TM_AVECHAR,281,62,44,8
LTEXT "Max Char Width:",IDC_STATIC,207,72,64,8
LTEXT "0",IDC_TM_MAXCHAR,281,72,44,8
LTEXT "Weight:",IDC_STATIC,207,82,64,8
LTEXT "0",IDC_TM_WEIGHT,281,82,44,8
LTEXT "Overhang:",IDC_STATIC,207,92,64,8
LTEXT "0",IDC_TM_OVERHANG,281,92,44,8
LTEXT "Digitized Aspect X:",IDC_STATIC,207,102,64,8
LTEXT "0",IDC_TM_DIGASPX,281,102,44,8
LTEXT "Digitized Aspect Y:",IDC_STATIC,207,112,64,8
LTEXT "0",IDC_TM_DIGASPY,281,112,44,8
LTEXT "First Char:",IDC_STATIC,207,122,64,8
LTEXT "0",IDC_TM_FIRSTCHAR,281,122,44,8
LTEXT "Last Char:",IDC_STATIC,207,132,64,8
LTEXT "0",IDC_TM_LASTCHAR,281,132,44,8
LTEXT "Default Char:",IDC_STATIC,207,142,64,8
LTEXT "0",IDC_TM_DEFCHAR,281,142,44,8
LTEXT "Break Char:",IDC_STATIC,207,152,64,8
LTEXT "0",IDC_TM_BREAKCHAR,281,152,44,8
LTEXT "Italic?",IDC_STATIC,207,162,64,8
LTEXT "0",IDC_TM_ITALIC,281,162,44,8
LTEXT "Underlined?",IDC_STATIC,207,172,64,8
LTEXT "0",IDC_TM_UNDER,281,172,44,8
LTEXT "Struck Out?",IDC_STATIC,207,182,64,8
LTEXT "0",IDC_TM_STRUCK,281,182,44,8
LTEXT "Variable Pitch?",IDC_STATIC,207,192,64,8
LTEXT "0",IDC_TM_VARIABLE,281,192,44,8
LTEXT "Vector Font?",IDC_STATIC,207,202,64,8
LTEXT "0",IDC_TM_VECTOR,281,202,44,8
LTEXT "TrueType Font?",IDC_STATIC,207,212,64,8
LTEXT "0",IDC_TM_TRUETYPE,281,212,44,8
LTEXT "Device Font?",IDC_STATIC,207,222,64,8
LTEXT "0",IDC_TM_DEVICE,281,222,44,8
LTEXT "Family:",IDC_STATIC,207,232,64,8
LTEXT "0",IDC_TM_FAMILY,281,232,44,8
LTEXT "Character Set:",IDC_STATIC,207,242,64,8
LTEXT "0",IDC_TM_CHARSET,281,242,44,8
LTEXT "0",IDC_TM_FACENAME,207,262,128,8
END
/
//
// Menu
//
PICKFONT MENU
BEGIN
POPUP "&Device"
BEGIN
MENUITEM "&Screen", IDM_DEVICE_SCREEN, CHECKED
MENUITEM "&Printer", IDM_DEVICE_PRINTER
END
END
RESOURCE.H (excerpts)
// Microsoft Visual C++ 生成的包含文件。
// 供 PickFont.rc 使用
//
#define IDR_MENU1 101
#define IDC_LF_HEIGHT 1000
#define IDC_LF_WIDTH 1001
#define IDC_LF_ESCAPE 1002
#define IDC_LF_ORIENT 1003
#define IDC_LF_WEIGHT 1004
#define IDC_MM_TEXT 1005
#define IDC_MM_LOMETRIC 1006
#define IDC_MM_HIMETRIC 1007
#define IDC_MM_LOENGLISH 1008
#define IDC_MM_HIENGLISH 1009
#define IDC_MM_TWIPS 1010
#define IDC_MM_LOGTWIPS 1011
#define IDC_LF_ITALIC 1012
#define IDC_LF_UNDER 1013
#define IDC_LF_STRIKE 1014
#define IDC_MATCH_ASPECT 1015
#define IDC_ADV_GRAPHICS 1016
#define IDC_LF_CHARSET 1017
#define IDC_CHARSET_HELP 1018
#define IDC_DEFAULT_QUALITY 1019
#define IDC_DRAFT_QUALITY 1020
#define IDC_PROOF_QUALITY 1021
#define IDC_LF_FACENAME 1022
#define IDC_OUT_DEFAULT 1023
#define IDC_OUT_STRING 1024
#define IDC_OUT_CHARACTER 1025
#define IDC_OUT_STROKE 1026
#define IDC_OUT_TT 1027
#define IDC_OUT_DEVICE 1028
#define IDC_OUT_RASTER 1029
#define IDC_OUT_TT_ONLY 1030
#define IDC_OUT_OUTLINE 1031
#define IDC_DEFAULT_PITCH 1032
#define IDC_FIXED_PITCH 1033
#define IDC_VARIABLE_PITCH 1034
#define IDC_FF_DONTCARE 1035
#define IDC_FF_ROMAN 1036
#define IDC_FF_SWISS 1037
#define IDC_FF_MODERN 1038
#define IDC_FF_SCRIPT 1039
#define IDC_FF_DECORATIVE 1040
#define IDC_TM_HEIGHT 1041
#define IDC_TM_ASCENT 1042
#define IDC_TM_DESCENT 1043
#define IDC_TM_INTLEAD 1044
#define IDC_TM_EXTLEAD 1045
#define IDC_TM_AVECHAR 1046
#define IDC_TM_MAXCHAR 1047
#define IDC_TM_WEIGHT 1048
#define IDC_TM_OVERHANG 1049
#define IDC_TM_DIGASPX 1050
#define IDC_TM_DIGASPY 1051
#define IDC_TM_FIRSTCHAR 1052
#define IDC_TM_LASTCHAR 1053
#define IDC_TM_DEFCHAR 1054
#define IDC_TM_BREAKCHAR 1055
#define IDC_TM_ITALIC 1056
#define IDC_TM_UNDER 1057
#define IDC_TM_STRUCK 1058
#define IDC_TM_VARIABLE 1059
#define IDC_TM_VECTOR 1060
#define IDC_TM_TRUETYPE 1061
#define IDC_TM_DEVICE 1062
#define IDC_TM_FAMILY 1063
#define IDC_TM_CHARSET 1064
#define IDC_TM_FACENAME 1065
#define IDM_DEVICE_SCREEN 40001
#define IDM_DEVICE_PRINTER 40002
图 17-2 显示的是 PICKFONT 程序的截屏。左边是一个无模式对话框,从中可以选择逻辑字体结构中定义的绝大多数字段,右边输出的是调用 GetTextMetrics 后得到的结果。对话框之下有一个样本字符串,那显示了使用该字体后的实际效果图。由于无模式对话框较大,所以该程序至少应该在 1024 * 768 显示分辨率下运行。
图 17-2 PICKFONT 程序(运行于 Win7 下的 Unicode 版本)
此模式对话框有上几千个选项并不属于逻辑字体结构。比如映射模式(包括逻辑缇(Login Twip) 模式)、Match Aspect 选项(Windows 使用它来匹配逻辑字体和实际使用的字体)、Adv Grfx 模式(表示在 Windows NT 下使用高级图像模式),随后我会详细讲解他们。
从 Device 菜单选择使用打印机作为输出设备时,PICKFONT 把逻辑字体选入打印机的设备环境,然后显示打印机中的 TEXTMETRIC 结构。程序接着把逻辑字体选入设备环境来显示样本字符串。此时,样本字符串的字体(屏幕字体)和TEXTMETRIC 结构表述的字体(打印机的字体)可能并不一致。
因为 PICKFONT 程序的很大一部分代码都是用于建立对话框,我就不一一讲解了。这里我要解释的是如何创建和使用逻辑字体。
17.3.3 逻辑字体结构
使用 CreateFont 函数可以创建一个逻辑字体。但是 CreateFont 有 14 个参数,从易用性来说,不如先定义一个 LOGFONT 结构
LOGFONT lf;
然后设定该结构的一些字段,再获得一个指向该结构的指针,最后将指针传入 CreateFontIndirect 函数来创建逻辑字体。
hFont = CreateFontIndirect (&lf);
不是 LOGFONT 的所有字段都需要被设定。如果程序中的逻辑字体结构是一个静态变量,那该结构的所有字段都会被初始化为 0(0 一般都被定义为默认值)。如果不做任何改动直接使用该结构,CreateFontIndirect 也会返回一个字体句柄。当该字体句柄被选入设备环境时,得到的就是一个默认字体。无论你怎么使用 LOGFONT 结构,一一指定每个字段也好,留一些字段不设定也好,Windows 总是会尽量为你找到一个类似你需求的真实字体。
以下,我要开始详细介绍 LOGFONT 结构的每个字段了。使用 PICKFONT 程序可以验证这些设定。但是要记得在 PICKFONT 程序界面上每次修改设定后,要单击 OK 按钮或按下 ENTRY 键设定才会生效。
LOGFONT 结构的前两个字段用逻辑单位表示,所以它们与映射模式的当前设置有关。
- lfHeight:这是以逻辑单位表示的希望得到的字符高度。设置 lfHeight 为 0 表示使用默认的字符高度。它的值可以为正也可以为负。当 lfHeight 为正数时,它表示该高度包含内部间隔(但不包含外部间隔),lfHeight 实际上就是最终字体的行间距。当它为负数时,Windows 会将其绝对值作为字体的高度。这是一个很重要的区别:如果想要某一特定字号的字体,应该先把该字号转化成逻辑单位,然后把 lfHeight 设定为该值的相反数,Windows 就会反向查找到你想要的字号。当 lfHeight 是正数时,它和 TEXTMETRIC 结构的 tmHeight 字段值近似相等(有时会有微小的不同,那是由于四舍五入带来的误差)。如果 lfHeight 是负数,则它大致等于 TEXTMETRIC 结构的 tmHeight 字段减去 tmInternalLeading。
- lfWidth:这是以逻辑单位表示的希望得到的字符高度。多数情况下,应该把它设置为 0,这就让 Windows 根据字符的高度来选择合适的字体。当该值不为 0 时,它对点阵字体基本不起作用,对 TrueType 字体则可以返回一个比正常字符稍宽或稍窄的字体。该字段对应于 TEXTMETRIC 结构的 tmAveCharWidth 字段。使用 lfWidth 字段的正确方式是首先把 LOGFONT 结构的 lfWidth 字段设置为 0,创建一个逻辑字体,将它选入设备环境,然后调用 GetTextMetrics 函数获得 tmAveCharWidth 字段的值,按比例调节该值的大小,然后使用被调节过的 tmAveCharWidth 值作为 lfWidth 字段值来创建一个新的字体。
PICKFONT 例程里接下来的两个输入选项是文本预先定义的位移数(Escapement)和方向(Orientation)。lfEscapement 参数表示位移角度,理论上来说,位移角度表示字符串会以该角度斜向输出,但是每个字符的基准线和水平轴是平行的,也就是每个字符还是垂直的。lfEscapement 参数表示字符方向,它控制每个字符的倾斜方向。但实际情况并不一定如此。要让这两个字段起到以上所描述的效果,必须满足以下条件:使用 TrueType 字体、在 Windows NT 上运行以及在先前调用 SetGraphicsMode 函数时设定了 CM_ADVANCED 标志。在 PICKFONT 例程里,选中 Adv Grfx Mode(Adv Grfx 模式)复选框,可以获得这个效果。
在使用 PICKFONT 中的这些输入选项时,要注意其单位是十分之一度,从逆时针方向度量。很多输入值都可以使样本字符串消失,因此,请使用 0 到 -600 或 3000 到 3600 之间的值。
- lfEscapement:字符串的方向,是从水平方向往逆时针方向测量的角度值,以十分之一度为计量单位。它指定在输出字符串的相邻字符之间的位移大小。下表显示了几个实例。
值 | 字符的放置 |
---|---|
0 | 从左向右(默认) |
900 | 垂直向上 |
1800 | 从右向左 |
2700 | 垂直向下 |
在 Windows 98 中,这个值除了设定 TrueType 文本的位移角度,同时还指定了方向(Orientation)。在 Windows NT 中,也会发生这种情况,唯一的例外是已使用了 GM_ADVANCED 标志调用 SetGraphicsMode 函数,那时,它的效果才会和前面文档描述的结果相同。
- lfOrientation:单个字符的倾斜方向,是从水平方向往逆时针方向测量的角度值,以十分之一度为计量单位。下表显示了几个实例:
值 | 字符外观 |
---|---|
0 | 正常(默认) |
900 | 向右旋转 90 度 |
1800 | 颠倒 |
2700 | 向左旋转 90 度 |
一般情况下这个字段会被忽略,唯一的例外是在 Windows NT 下使用 TrueType 字体,并已使用了 GM_ADVANCED 标志调用 SetGraphicsMode 函数,那时,它的效果才会和前面文档描述的结果相同。
其余 10 个字段如下。
- lfWeight:这个字段指明是否使用指定粗体字。WINGDI.H 头文件定义了这个字段的可用值(参见下表)。
值 | 标 识 符 |
---|---|
0 | FW_DONTCARE |
100 | FW_THIN |
200 | FW_EXTRALIGHT 或 FW_ULTRALIGHT |
300 | FW_LIGHT |
400 | FW_NORMAL 或 FW_REGULAR |
500 | FW_MEDIUM |
600 | FW_SEMIBOLD 或 FW_DEMIBOLD |
700 | FW_BOLD |
800 | FW_EXTRABOLD 或 FW_ULTRABOLD |
900 | FW_HEAVY 或 FW_BALCK |
实际上,很多字体并没有完全实现上表的所有效果。一般使用 0 或 400 来表示正常字体,使用 700 表示粗体。
- lfItalic:当其值为非零时,表示使用斜体。对 GDI 点阵字体,Windows 可以直接合成斜体效果。也就是说,Windows 通过移动字符的位图来模拟斜体输出。对于 TrueType 字体,Windows 会使用真正的单独的斜体字体,或者是使用该字体的倾斜属性版本。
- lfUnderline:当其值为非零时,表示字符要添加下划线。对所有的 GDI 字体,该效果都是通过使用合成方式实现的。也就是说,Windows 在每个字符(包括空格字符)下面添加一条底线。
- lfStrikeOut:当其值为非零时,表示字符上面有一条删除线,就是一条穿越字符的直线。对所有 GDI 字体,该效果也都是合成的。
- lfCharSet:该字段长度为一个字节,表示该字体的字符集列表。我会在下一节“字符集和 Unicode”中更详细地讨论这个字段。在 PICKFONT 例程中,单击问号按钮获得当前可用的字符集列表。值得注意的是,lfCharSet 是唯一一个不以 0 为默认值的字段,它的默认值 DEFAULT_CHARSET 相对应的数值是 1,表示使用当前计算机默认字符集。当指定 lfCharSet 为 0 时,表示 ANSI_CHARSET,也就是使用美国和西欧地区使用的 ANSI 字符集。
- lfOutPrecision:该字段指定了 Windows 通过字体的大小特征来匹配真实字体的方式。该字段的设定相当复杂,一般不会使用。如果需要了解详细的信息,可以查阅 LOGFONT 结构的文档。但需要知道的是,如果使用 OUT_TT_ONLY_PRECIS 标志,那可以确保返回的一定是一个 TrueType 字体。
- lfClipPrecision:该字段指定了剪裁方式,也就是当字符在显示区域以外时,如何只显示部分字符。该字段并不常用,在 PICKFONT 例程中没有使用到。
- lfQuality:该字段指示 Windows 如何进行匹配希望得到的字体和实际获得的字体。它只影响点阵字体,对 TrueType 字体没有影响。DRAFT_QUALITY 标志表示希望 GDI 缩放点阵字体来获得期望的字体大小;PROOF_QUALITY 标志表示不要缩放字体。PROOF_QUALITY 标志可以产生比较漂亮的字体,但是字体的尺寸可能期望的要小。也可以使用 DEFAULT_QUALITY(值为 0),表示不在乎真实字体的美观程度。
- lfPitchAndFamily:该字段是一个字节,由两部分组成。这两部分通过 C 语言的 OR 操作符(位或运算)来产生最终字节。最低两个比特位表示该字体是否是一个等宽字体(所有字符的宽度都相同)或是一个变宽字体。
值 | 标 识 符 |
---|---|
0 | DEFAULT_PITCH |
1 | FIXED_PITCH |
2 | VARIABLE_PITCH |
字节的高六位表示字体的系列如下表所示。
值 标 识 符 0x00 FW_DONTCARE 0x10 FF_ROMAN(变宽,衬线型字体) 0x20 FF_SWISS(变宽,非衬线型字体) 0x30 FF_MODERN(等宽) 0x40 FF_SCRIPT(模拟手写体) 0x50 FF_DECORATIVE
- lfFacename:是实际字体的字样名字(如 Courier,Arial 或者是 Times New Roman)。该字段是长度为 LF_FACESIZE(32 个字符)的字节数组。如果想指定一个粗体或斜体的 TrueType 字体,既可以在 lfFaceName 字段里直接指定完整的字样名字(如 Times New Roman Italic),也可以只使用基本的字样名字(就是 Times New Roman)同时设定 lfItalic 字段来达到相同的效果。
17.3.4 字体匹配算法
将 LOGFONT 结构的各个字段设定好后,调用 CreateFontIndirect 函数可以获得一个指向逻辑字体的句柄。在调用 SelectObject 函数可以将逻辑字体选入设备环境,然后 Windows 会找到一个和所需字体最接近的实际字体,该查找过程使用了一个“字体匹配算法”。在此算法中,LOGFONT 的某些字段被认为相对比较重要,还有一些字段被认为较为次要。
要理解字体匹配的过程,最好花些时间试验 PICKFONT。以下是该算法一些基本点:
- lfCharSet(字符集)字段非常重要。从前当该字段的值为 OEM_CHARSET(相应的数值为 255)时,最终获得的或者是一个笔画字体或者是一个 Terminal 字体,因为只有这些字体才会使用 OEM 字符集。然而,自出现 TrueType 大字体(Big Font)(在前面第 6 章讨论过)后,一个单独的 TrueType 字体就可以映射到不同的字符集(包括 OEM 字符集)。该字段为 SYMBOL_CHARSET(相应的数值为 2)时,可以获得一个 Symbol 字体或 Wingdings 字体。
- 当 lfPitchAndFamily 字段包含 FIFXED_PITCH 值,该值非常重要。因为这说明用户不希望最后获得一个变宽字体。
- lfFaceName 也很重要,因为当用户指定字体的字样名称时,它明确表明了用户的意图。如果 lfFaceName 设置为 NULL,但是在 lfPitchAndFamily 里指定了某一字体系列(如果名称是 FF_DONTCARE 则除外),那 lfPitchAndFamily 字段的重要性就相应被提高了。
- 对点阵字体,Windows 会尝试去匹配 lfHeight 的值,即使那可能需要放大一个较小的字体。实际获得的字体大小不会超过期望字体的尺寸,除非用户请求的字体太小以至所有的实际字体都比期望字体大。如果是笔画字体或 TrueType 字体,windows 就会对它们进行简单的缩放操作来得到期望的字体高度。
- 设定 lfQuality 为 PROOF_QUALITY 可以防止 Windows 对点阵字体进行缩放。当使用 PROOF_QUALITY 时,表明用户认为字体的美观程度更重要,字符的高度则是比较次要的。
- 如果 lfHeight 和 lfWidth 值的比值和显示器的纵横比不符合,Windows 可能会去找一个为不同纵横比显示器或打印设备设计的特殊点阵字体。这曾经是一个用来获得较窄或较宽字体的小窍门。(对于 TrueType 字体,当然就没有必要这么做了)。总之,尽量避免使用一个为其他设备设计的字体。如果实在想这么做,在 PICKFONT 中,可以单击 Match Aspect(纵横比匹配)选项。该复选框被选中时,PICKFONT 会使用 TRUE 参数调用 SetMapperFlags 函数。
17.3.5 获取字体信息
PICKFONT 范例程序的右部显示的是该字体被选入设备环境之后调用 GetTextMetrics 函数返回的结果。(使用 PICKFONT 程序上的 Device(设备)菜单可以切换设备环境,它可以是显示器或者是默认打印机。根据不同的设备环境,显示效果可能不同,这是因为打印机上自带的字体和 Windows 上安装的字体并不一致。)在 PICKFONT 左下部显示的是从 GetTextFace 函数返回的可用的字体的字样名称。
TEXTMETRIC 结构中除了纵横比外,其他字段的数值都是以逻辑单位来表示的。TEXTMETRIC 结构的字段如下。
- tmHeight:逻辑单位表示的字符高度。如果 LOGFONT 结构中 lfHeight 字段值为正数,tmHeight 字段的值和 lfHeight 值就大致相等,都指的是行间距大小而不是点值。如果 lfHeight 字段为负数,那 tmHeight 字段值减去 tmInternalLeading,就是 lfHeight 的绝对值。
- tmAscent:字符在基准线之上的垂直长度,以逻辑单位表示。
- tmDescent:字符在基准线之下的垂直长度,以逻辑单位表示。
- tmInternalLeading:内部间隔值,对应的是大写字母变音符号的区域。tmHeight 字段值已经包含了该区域的长度。再重复一次,从 tmHeight 减去 tmInternalLeading 可以得到字体的点值。
- tmExternalLeading:外部间隔值,字体的设计者会认为某些字体相邻两行之间可能需要一些额外空间,所以就在该值内允许用户设定那些额外空间的大小。tmHeight 并不包含该部分的尺寸。
- tmAveCharWidth:小写字母的平均宽度。
- tmMaxCharWidth:该字体中最宽字符的宽度,以逻辑单位表示。对等宽字体而言,该字段值和 tmAveCharWidth 相同。
- tmWeight:字符粗细程序,取值范围为 0 到 999。400 表示正常字符,700 表示粗体字。
- tmOverhang:当 Windows 对点阵字体进行斜体或粗体合成时,该字符会能变得略宽。此字段就代表那额外的宽度。当把点阵字体转化成斜体时,tmAveCharWidth 值不变,这是因为由斜体字组成的字符串宽度和原有宽度是一样的。如果是粗体字,Windows 必须把每个字符稍稍变宽。对一个粗体字而言,它的 tmAveCharWidth 的值减去 tmOverHang 就是其对应非粗体字体的 tmAveCharWidth 值。
- tmDigitizedAspectX 和 tmDigitizedAspectY:适合该字体的纵横比例。它们的值和使用 LOGPIXELSX、LOGPIXELSY 标志调用 GetDeviceCaps 函数后返回的值相等。
- tmFirstChar:该字体中第一个字符的编码。
- tmLastChar:该字体中最后一个字符的编码。如果此 TEXTMETRIC 结构是通过对 GetTextMetricsW 函数(GetTextMetrics 的 Unicode 版本)调用获得的,此字段值可能大于 255。
- tmDefaultChar:当希望显示的字符在改字体中并不存在时,Windows 会使用此字段定义的字符来代替显示。通常是这是一个方块字符。
- tmBreakChar:Windows 认为在该字符处是可以进行断行的。一般该字段值为 32,就是空格字符,除非用户使用一些特殊的字体(比如 EBCDIC 字体)。
- tmItalic:表示是否是斜体字。非零表示一个斜体字体。
- tmUnderlined:表示是否带有下划线。非零表示带有下划线。
- tmStruckOut:表示是否带有删除线。非零表示带有删除线。
- tmPitchAndFamily:该字段的低四位表示字体的一些特性。在 WINGDI.H 定义了以下标识符。
值 | 标 识 符 |
---|---|
0x01 | TMPF_FIXED_PITCH |
0x02 | TMPF_VECTOR |
0x04 | TMPF_TRUETYPE |
0x08 | TMPF_DEVICE |
尽管 TMPF_FIXED_PITCH 名字含有等宽的意思,但其实是当字体中有一个变宽字符,该字段的最低位就会是 1。如果是 TrueType 字体或者是任何可缩放的轮廓字体(比如 PostScript 字体),那该字段的次低位(TMPF_VECTOR)就是 1。TMPF_DEVICE 标志表示一个设备字体(比如,打印机的内置字体),而不是 GDI 类型的字体。
字段的高四位表示字体的系列。它们的含义和 LOGFONT 结构的 lfPitchAndFamily 字段是一样的。
- tmCharSet:表示字符集。
17.3.6 字符集和 Unicode
第 6 章我们讨论了 Windows 字符集的概念,解决了一个关于键盘国际化的问题。在 LOGFONT 和 TEXTMETRIC 结构中期望字体(或实际获得的字体)的字符集是由一个位于 0 和 255 之间的单字节整数表示的。WINGDI.H 中定义了字符集的标识符:
#define ANSI_CHARSET 0
#define DEFAULT_CHARSET 1
#define SYMBOL_CHARSET 2
#define MAC_CHARSET 77
#define SHIFTJIS_CHARSET 128
#define HANGEUL_CHARSET 129
#define HANGUL_CHARSET 129
#define JOHAB_CHARSET 130
#define GB2312_CHARSET 134
#define CHINESEBIG5_CHARSET 136
#define GREEK_CHARSET 161
#define TURKISH_CHARSET 162
#define VIETNAMESE_CHARSET 163
#define HEBREW_CHARSET 177
#define ARABIC_CHARSET 178
#define BALTIC_CHARSET 186
#define RUSSIAN_CHARSET 204
#define THAI_CHARSET 222
#define EASTEUROPE_CHARSET 238
#define OEM_CHARSET 255
字符集和代码页的概念有些类似,但是字符集的概念仅限于 Windows,而且其值不会大于 255。
如同本书中的其他例程一样,PICKFONT 也可以使用 UNICODE 来编译(通过定义一个 UNICODE 标识符)。运行 UNICODE 版本的 PICKFONT 时,在窗口底部输出的字符串会比非 UNICODE 版本的长。
在这两个版本里,输出字符串的起始 10 个字符的代码都是从 0x40 到 0x45 和从 0x60 到 0x65。无论选择了哪个字符集(除非是 SYMBOL_CHARSET 字符集),这些码位都表示了拉丁字母表的起始五个大小写字母(也就是 A 到 E 和 a 到 e)。
如果运行非 UNICODE 的 PICKFONT 程序,随后 12 个字符(字符编码为 0xC0 到 0xC5 和 0xE0 到 0xE5)的具体显示将取决于使用了哪个字符集。对于 ANSI_CHARSET 字符集,这些字符编码代表的是带有重音符号的字母 A 和 a。对于 GREEK_CHARSET 字符集,这些编码代表的是一些希腊字母。如果是 RUSSIAN_CHARSET,它们表示的是一些西里尔字符(Cyrillic)。值得注意的是,使用这里提到的字符集可能会导致程序最后使用不同的字体。这是因为一个点阵字体不一定含有所有这些字符,而往往一个 TrueType 字体则可能会包含它们。大多 TrueType 字体都是 Big Fonts,也就是说它们会含有多个字符集。在远东版本的 Windows 上,这些字符都被认为是双字节的字符,并且作为图像而非字母来显示。
在 Windows NT 上运行 Unicode 版本的 PICKFONT 时,如果不使用 SYMBOL_CHARSET 字符集,编码 0xC0 到 0xC5 和 0xE0 到 0xE5 永远都表示带有重音符号的字母 A 和 a,因为它们在 Unicode 就是这么定义的。程序还会显示编码为 0x0410 到 0x0415 和 0x0430 到 0x0435 的字符,这些在 Unicode 里都是西里尔字符。但是,这些字符可能在默认字体里根本不存在。必须要指定使用 GREEK_CHARSET 或 RUSSIAN_CHARSET,才能看到这些字符。这种情况下,实际使用的字符集永远都是 Unicode,在 LOGFONT 结构里面的指定的字符集 ID 并没有改变这个事实,只是表示希望使用该 ID 代表的字符集里的那个字符。
如果指定使用 HEBREW_CHARSET(数值为 117),因为 Windows 的 Big Fonts 并不包含希伯来字母,所以操作系统使用 Lucida Sans Unicode 字体,在 PICKFONT 例程的右下角中可以验证这一结果。
PICKFONT 范例程序还显示了几个中日韩文字,编码分别为 0x5000 到 0x5004。如果你使用的是远东版 Windows 或者系统上安装了一些比 Lucida Sans Unicode 还要全面的扩展字体(如 Bitstream CyberBit),你才可以看见这些字符(Lucida sans Unicode 字体文件大约是 300K,二 Bitstream CyberBit 是 13 MB 左右)。如果安装了这些额外的字体,在使用一种 Lucida Sans Unicode 不支持的字符时,例如 SHIFTJIS_CHARSET(日文),HANGUL_CHARSET(韩文),JOHAB_CHARSET(韩文),GB2312_CHARSET(简体中文)或 CHINESEBIG5_CHARSET(繁体中文),Windows 就会选用那些额外的字体。
本章稍后的一个范例程序将展示如何列出 Unicode 字体中的所有字符。
17.3.7 EZFONT 系统
基于传统印刷术的 TrueType 字体的引入,为 Windows 以不同的方式显示文字提供了坚实的基础。然而,有些 Windows 的字体选择函数还是基于较陈旧的技术,也就是在显示器上使用点阵字体来模拟打印设备的字体。在下一章,我会介绍如何枚举字体,这样程序就可以获得在显示器上或打印机上所有可用字体的列表。不过,使用 ChooseFont 对话框(后文有详述)在很大程度上消除了程序枚举字体的必要性。
先枚举所有的字体再从中选择其一,可以确保程序想使用的字体确实是存在并可用的。因为在每个系统上都安装有标准的 TrueType 字体,而且这种字体既可以在显示器上也可以在打印机上使用,所以枚举字体不再是必需要做的了。程序知道系统上预安装有特定的 TrueType 字体(除非用户故意删除了它们),所以就可以直接使用这些字体。使用方法非常简单,就是直接使用字样名字和字号来选择特定字体。我把这种方法称之为 EZFONT(Easy Font,简单字体),图 17-3 显示了实现该方法的两个文件。
/*---------------------------
EZFONT.H header file
----------------------------*/
HFONT EzCreateFont(HDC hdc, TCHAR * szFaceName, int iDeciPtHeight,
int iDeciPtWidth, int iAttributes, BOOL fLogRes);
#define EZ_ATTR_BOLD 1
#define EZ_ATTR_ITALIC 2
#define EZ_ATTR_UNDERLINE 4
#define EZ_ATTR_STRIKEOUT 8
/*---------------------------------------------------
EZFONT.C -- Easy Font Creation
(c) Charles Petzold, 1998
---------------------------------------------------*/
#include <Windows.h>
#include <math.h>
#include "EZFont.h"
HFONT EzCreateFont(HDC hdc, TCHAR * szFaceName, int iDeciPtHeight,
int iDeciPtWidth, int iAttributes, BOOL fLogRes)
{
FLOAT cxDpi, cyDpi;
HFONT hFont;
LOGFONT lf;
POINT pt;
TEXTMETRIC tm;
SaveDC(hdc);
SetGraphicsMode(hdc, GM_ADVANCED);
ModifyWorldTransform(hdc, NULL, MWT_IDENTITY);
SetViewportOrgEx(hdc, 0, 0, NULL);
SetWindowOrgEx(hdc, 0, 0, NULL);
if (fLogRes)
{
cxDpi = (FLOAT)GetDeviceCaps(hdc, LOGPIXELSX);
cyDpi = (FLOAT)GetDeviceCaps(hdc, LOGPIXELSY);
}
else
{
cxDpi = (FLOAT)(25.4 * GetDeviceCaps(hdc, HORZRES) /
GetDeviceCaps(hdc, HORZSIZE));
cyDpi = (FLOAT)(25.4 * GetDeviceCaps(hdc, VERTRES) /
GetDeviceCaps(hdc, VERTSIZE));
}
pt.x = (int)(iDeciPtWidth * cxDpi / 72);
pt.y = (int)(iDeciPtHeight * cyDpi / 72);
DPtoLP(hdc, &pt, 1);
lf.lfHeight = -(int)(abs(pt.y) / 10.0 + 0.5);
lf.lfWidth = 0;
lf.lfEscapement = 0;
lf.lfOrientation = 0;
lf.lfWeight = iAttributes & EZ_ATTR_BOLD ? 700 : 0;
lf.lfItalic = iAttributes & EZ_ATTR_ITALIC ? 1 : 0;
lf.lfUnderline = iAttributes & EZ_ATTR_UNDERLINE ? 1 : 0;
lf.lfStrikeOut = iAttributes & EZ_ATTR_STRIKEOUT ? 1 : 0;
lf.lfCharSet = DEFAULT_CHARSET;
lf.lfOutPrecision = 0;
lf.lfClipPrecision = 0;
lf.lfQuality = 0;
lf.lfPitchAndFamily = 0;
lstrcpy(lf.lfFaceName, szFaceName);
hFont = CreateFontIndirect(&lf);
if (iDeciPtWidth != 0)
{
hFont = (HFONT)SelectObject(hdc, hFont);
GetTextMetrics(hdc, &tm);
DeleteObject(SelectObject(hdc, hFont));
lf.lfWidth = (int)(tm.tmAveCharWidth *
(double)abs(pt.x) / abs(pt.y) + 0.5);
hFont = CreateFontIndirect(&lf);
}
RestoreDC(hdc, -1);
return hFont;
}
EZFONT.C 只有一个函数,那就是 EzCreateFont。你可以通过以下方式调用它:
hFont = EzCreateFont (hdc, szFaceName, iDeciPtHeight, iDeciPtWidth,
iAttribuutes, fLogRes);
该函数返回一个指向字体的句柄。使用 SelectObject 函数可以把该字体选入设备环境。随后应该调用 GetTextMetrics 和 GetOutlineTextMetrics 函数获得该字体在逻辑坐标中的实际尺寸。在程序结束前,应该调用 DeleteObject 函数来删除被创建的所有字体。
szFaceName 参数可以是任何 TrueType 字体的字样名字。系统一般都安装了标准字体,如果你选择的是标准字体,通常情况下都可以获得想要的字体。
第三个参数表示字体大小,单位为十分之一点。如果你要一个 12.5 点值的字体,使用数值 125。
通常,第四个参数应该是 0 或者和第三个参数相等,但如果要得到一个较宽或较窄的字体,就需要把该参数设置成其他值。该参数有时也称为字体的“EM 宽度”。早期的印刷术中,字母 M 大写时的高度和宽度是一样的,所以就产生了 EM 度量的概念,大写 M 所占用的方格被称为“EM 正方形”。所以当一个字体的 EM 宽度和 EM 高度相同时,该字体的长宽比是最佳的,也就是最初字体设计人员所设计的那样。使用一个较小的 EM 宽度或较大的 EM 宽度可以获得相应较窄或较宽的字符。
iAttributes 参数可以设置为以下值,文件 EZFONT.H 定义了这些值。
EZ_ATTR_BOLD
EZ_ATTR_ITALIC
EZ_ATTR_UNDERLINE
EZ_ATTR_STRIKEOUT
如果要指定斜体或粗体,既可以使用 EZ_ATTR_BOLD 和 EZ_ATTR_ITALIC 标志,也可以在 TrueType 字体的字样名字中特别指定。
最后一个参数设置被设置为 TRUE,这样字体的尺寸就是以“逻辑分辨率”为单位的,这里的“逻辑分辨率”说的是使用 LOGPIXELSX 和 LOGPIXELSY 标志调用 GetDeviceCaps 函数后的返回值。如果参数是 FALSE,字体的尺寸是会基于由 HORZRES,HORZSIZE,VERTRES 和 VERTSIZE 计算出的分辨率。在 Windows NT 系统下,这会产生不同的结果。
EzCreateFont 函数开始先针对 Windows NT 平台的特性做了些改动。要知道调用 SetGraphicsMode 函数和 ModifyWorldTransform 函数在 Windows98 系统下不起作用。因为在 Windows NT 下的全局转换(World Transform)可能会改变字体可见部分的大小,所以在计算字体大小之前,先要将全球转换设定为默认值(MWT_IDENTITY)也就是不进行转换。
EzCreateFont 函数设置完 LOGFONT 结构的字段后调用 CreateFontIndirect 函数会获得一个指向字体的句柄。EzCreateFont 函数大部分的代码都在处理将字体的点值转换为 LOGFONT 结构中 lfHeight 字段要求的逻辑单位。字体的点值先被转换为设备的像素点,调用 GetDeviceCaps 函数可以做到这一点。然后再把像素点转换为逻辑单位,这里似乎只需要简单地调用 DPtoLP(“从设备点到逻辑点”的缩写)函数。但是为了让 DPtoLP 函数进行正确的转换,在以后使用新创建字体显示文本时,程序必须使用相同的映射模式。这就意味着应该在调用 EzCreateFont 函数之前就应该先设定好映射模式。在大多数情况下,程序在绘制窗口时都只会使用一种映射模式,所以这种要求不是什么大的限制。
图 17-4 的 EZTEST 程序可以用来测试 EZFONT 程序,虽然它并没有穷尽测试所有的情况。此程序使用了以下 EZTEST 文件和本书后面要谈到的 FONTDEMO 文件。
/*---------------------------------------------------
EZTEST.C -- Test of EZFONT
(c) Charles Petzold, 1998
---------------------------------------------------*/
#include <Windows.h>
#include "EZFont.h"
TCHAR szAppName[] = TEXT("EZTest");
TCHAR szTitle[] = TEXT("EZTest: Test of EZFONT");
void PaintRoutine(HWND hwnd, HDC hdc, int cxArea, int cyArea)
{
HFONT hFont;
int y, iPointSize;
LOGFONT lf;
TCHAR szBuffer[100];
TEXTMETRIC tm;
// Set Logical Twips mapping mode
SetMapMode(hdc, MM_ANISOTROPIC);
SetWindowExtEx(hdc, 1440, 1440, NULL);
SetViewportExtEx(hdc, GetDeviceCaps(hdc, LOGPIXELSX),
GetDeviceCaps(hdc, LOGPIXELSY), NULL);
// Try some fonts
y = 0;
for (iPointSize = 80; iPointSize <= 120; iPointSize++)
{
hFont = EzCreateFont(hdc, TEXT("Times New Roman"),
iPointSize, 0, 0, TRUE);
GetObject(hFont, sizeof(LOGFONT), &lf);
SelectObject(hdc, hFont);
GetTextMetrics(hdc, &tm);
TextOut(hdc, 0, y, szBuffer,
wsprintf(szBuffer,
TEXT("Times New Roman font of %i.%i points, ")
TEXT("lf.lfHeight = %i, tm.tmHeight = %i"),
iPointSize / 10, iPointSize % 10,
lf.lfHeight, tm.tmHeight));
DeleteObject(SelectObject(hdc, GetStockObject(SYSTEM_FONT)));
y += tm.tmHeight;
}
}
/*---------------------------------------------------
FONTDEMO.C -- Font Demonstration Shell Program
(c) Charles Petzold, 1998
---------------------------------------------------*/
#include <Windows.h>
#include "EZFont.h"
#include "resource.h"
extern void PaintRoutine(HWND, HDC, int, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE hInst;
extern TCHAR szAppName[];
extern TCHAR szTitle[];
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
TCHAR szResource[] = TEXT("FontDemo");
HWND hwnd;
MSG msg;
WNDCLASS wndclass;
hInst = hInstance;
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 = szResource;
wndclass.lpszClassName = szAppName;
if (!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("This program requires Windows NT!"),
szAppName, MB_ICONERROR);
return 0;
}
hwnd = CreateWindow(szAppName, szTitle,
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 DOCINFO di = { sizeof(DOCINFO), TEXT("Font Demo: Printing") };
static int cxClient, cyClient;
static PRINTDLG pd = { sizeof(PRINTDLG) };
BOOL fSuccess;
HDC hdc, hdcPrn;
int cxPage, cyPage;
PAINTSTRUCT ps;
switch (message)
{
case WM_COMMAND:
switch (wParam)
{
case IDM_PRINT:
// Get printer DC
pd.hwndOwner = hwnd;
pd.Flags = PD_RETURNDC | PD_NOPAGENUMS | PD_NOSELECTION;
if (!PrintDlg(&pd))
return 0;
if (NULL == (hdcPrn = pd.hDC))
{
MessageBox(hwnd, TEXT("Cannot obtain Printer DC"),
szAppName, MB_ICONEXCLAMATION | MB_OK);
return 0;
}
// Get size of printable area of page
cxPage = GetDeviceCaps(hdcPrn, HORZRES);
cyPage = GetDeviceCaps(hdcPrn, VERTRES);
fSuccess = FALSE;
// Do the printer page
SetCursor(LoadCursor(NULL, IDC_WAIT));
ShowCursor(TRUE);
if ((StartDoc(hdcPrn, &di) > 0) && (StartPage(hdcPrn) > 0))
{
PaintRoutine(hwnd, hdcPrn, cxPage, cyPage);
if (EndPage(hdcPrn) > 0)
{
fSuccess = TRUE;
EndDoc(hdcPrn);
}
}
DeleteDC(hdcPrn);
ShowCursor(FALSE);
SetCursor(LoadCursor(NULL, IDC_ARROW));
if (!fSuccess)
MessageBox(hwnd, TEXT("Error encountered during printing"),
szAppName, MB_ICONEXCLAMATION | MB_OK);
return 0;
case IDM_ABOUT:
MessageBox(hwnd, TEXT("Font Demostration Program\n")
TEXT(" (c) Charles Petzold, 1998"),
szAppName, MB_ICONINFORMATION | MB_OK);
return 0;
}
break;
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
PaintRoutine(hwnd, hdc, cxClient, cyClient);
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
EZTEST.RC
// Microsoft Visual C++ 生成的资源脚本。
//
#include "resource.h"
/
//
// Menu
//
FONTDEMO MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "&Print...", IDM_PRINT
END
POPUP "&Help"
BEGIN
MENUITEM "&About...", IDM_ABOUT
END
END
RESOURCE.H
// Microsoft Visual C++ generated include file.
// Used by EZTest.rc
#define IDM_PRINT 40001
#define IDM_ABOUT 40002
EZTEST.C 中的 PaintRoutine 函数将映射模式设置为逻辑缇(logical twip),然后从 8 点到 12 点字体,每隔 0.1 个点就创建一个 Times New Roman 字体。第一次运行该程序时,输出的结果可能看上去不是很容易理解。有几行文本显然使用的是相同字号的字体,而且从 TEXTMETRIC 函数返回的 tmHeight 也证实了这一点,所有的字体的高度都一样。这是因为 Windows 对输出的结果进行了一个点阵化的过程。显示器上的像素点时一个个不连续的圆点,它不可能显示所有字号,但是,FONTDEMO 程序显示了子啊打印机上的输出效果,在那里可以比较容易地分辨字体大小。
17.3.8 字体旋转
在使用 PICKFONT 程序时,你可能已经注意到 LOGFONT 结构的 lfOrientation 字段和 lfEscapement 字段可以用来旋转 TrueType 文本。细想一下就可以知道,在 GDI 实现这个效果并不困难,因为对坐标点进行旋转变换的公式是大家熟知的。
虽然 EzCreateFont 函数不能指定字体的旋转角度,但是如同 FONTROT 程序展示的那样,在调用该函数后,很容易进行角度调整。图 17-5 显示了 FONTROT.C 文件,该程序也需要用到前面的 EZFONT 文件和 FONTDEMO 文件。
/*---------------------------------------------------
FONTROT.C -- Rotated Fonts
(c) Charles Petzold, 1998
---------------------------------------------------*/
#include <Windows.h>
#include "EZFont.h"
TCHAR szAppName[] = TEXT("FontRot");
TCHAR szTitle[] = TEXT("FontRot: Rotated Fonts");
void PaintRoutine(HWND hwnd, HDC hdc, int cxArea, int cyArea)
{
static TCHAR szString[] = TEXT("Rotation");
HFONT hFont;
int i;
LOGFONT lf;
hFont = EzCreateFont(hdc, TEXT("Times New Roman"), 540, 0, 0, TRUE);
GetObject(hFont, sizeof(LOGFONT), &lf);
DeleteObject(hFont);
SetBkMode(hdc, TRANSPARENT);
SetTextAlign(hdc, TA_BASELINE);
SetViewportOrgEx(hdc, cxArea / 2, cyArea / 2, NULL);
for (i = 0; i < 12; i++)
{
lf.lfEscapement = lf.lfOrientation = i * 300;
SelectObject(hdc, CreateFontIndirect(&lf));
TextOut(hdc, 0, 0, szString, lstrlen(szString));
DeleteObject(SelectObject(hdc, GetStockObject(SYSTEM_FONT)));
}
}
FONTROT 程序调用 EzCreateFont 函数只是为了得到一个 54 点 Times New Roman 字体的 LOGFONT 结构。得到该结构后,该字体被删除。在 for 循环里,旋转角度每增长 30 度,程序就创建一个新的字体,然后用它来显示文本。结果参见图 17-6。
图 17-6 FONTROT 显示结果
如果想得到一个更通用的图像旋转算法并且有兴趣了解一些线性变换的知识,而且如果你的程序只需要在 Windows NT(或 Windows NT 以后的)平台上运行,你可以使用 XFROM 矩阵和世界转换函数。