当前位置: 首页 > 文档资料 > Windows 程序设计 >

文字和字体

优质
小牛编辑
133浏览
2023-12-01

显示文字是本书所要解决的首要问题,现在我们来研究Microsoft Windows中各种有效字体和字体大小的使用方法以及调整文字的方式。

Windows3.1发表的TrueType使程序写作者和使用者以灵活的方式处理文字的能力大幅增强。TrueType是轮廓字体技术,由AppleComputer公司和Microsoft公司开发,并被许多字体制造商支持。由于TrueType字体能够连续缩放,并能应用于视讯显示器和打印机,现在能够在Windows下实作真的WYSIWYG(whatyou see is what youget:所见即所得)。TrueType也便于制作「奇妙」字体,例如旋转的字母、内部填充图案的字母或将它们用于剪裁区域,在本章我将展示它们。

简单的文字输出

让我们先来看看Windows为文字输出、影响文字的设备内容属性以及备用字体提供的各种函数。

文字输出函数

我已经在许多范例程序中使用过最常用的文字输出函数:

TextOut (hdc, xStart, yStart, pString, iCount) ;  

参数xStart和yStart是逻辑坐标上字符串的起始点。通常,这是Windows开始绘制的第一个字母的左上角。TextOut需要指向字符串的指针和字符串的长度,这个函数不能识别以NULL终止的字符串。

TextOut函数的xStart和yStart参数的含义可由SetTextAlign函数改变。TA_LEFT、TA_RIGHT和TA_CENTER旗标影响使用xStart在水平方向上定位字符串的方式。默认值是TA_LEFT。如果在SetTextAlign函数中指定了TA_RIGHT,则后面的TextOut呼叫会将字符串的最后一个字符定位于xStart,如果指定了TA_CENTER,则字符串的中心位于xStart。

类似地,TA_TOP、TA_BOTTOM和TA_BASELINE旗标影响字符串的垂直位置。TA_TOP是默认值,它意味着字符串的字母顶端位于yStart,使用TA_BOTTOM意味着字符串位于yStart之上。可以使用TA_BASELINE定位字符串,使基准线位于yStart。基准线是如小写字母p、q、y等字母下部的线。

如果您使用TA_UPDATECP旗标呼叫SetTextAlign,Windows就会忽略TextOut的xStart和yStart参数,而使用由MoveToEx、LineTo或更改目前位置的另一个函数设定的位置。TA_UPDATECP旗标也使TextOut函数将目前位置更新为字符串的结尾(TA_LEFT)或字符串的开头(TA_RIGHT)。这在使用多个TextOut呼叫显示一行文字时非常有用。当水平位置是TA_CENTER时,在TextOut呼叫后,目前位置不变。

您应该还记得,第四章的一系列SYSMETS程序显示几列文字时,对每一列都需要呼叫一个TextOut,其替代函数是TabbedTextOut函数:

TabbedTextOut (hdc, xStart, yStart, pString, iCount, iNumTabs, piTabStops, xTabOrigin) ;  

如果文字字符串中含有嵌入的制表符(‘\t’或0x09),则TabbedTextOut会根据传递给它的整数数组将制表符扩展为空格。

TabbedTextOut的前五个参数与TextOut相同,第六个参数是跳位间隔数,第七个是以图素为单位的跳位间隔数组。例如,如果平均字符宽度是8个图素,而您希望每5个字符加一个跳位间隔,则这个数组将包含40、80、120,按递增顺序依此类推。

如果第六个和第七个参数是0或NULL,则跳位间隔按每八个平均字符宽度设定。如果第六个参数是1,则第七个参数指向一个整数,表示跳位间隔重复增大的倍数(例如,如果第六个参数是1,并且第七个参数指向值为30的变量,则跳位间隔设定在30、60、90…图素处)。最后一个参数给出了从跳位间隔开始测量的逻辑x坐标,它与字符串的起始位置可能相同也可能不同。

另一个进阶的文字输出函数是ExtTextOut(前缀Ext表示它是扩展的):

ExtTextOut (hdc, xStart, yStart, iOptions, &rect, pString, iCount, pxDistance) ;  

第五个参数是指向矩形结构的指针,在iOptions设定为ETO_CLIPPED时,该结构为剪裁矩形,在iOptions设定为ETO_OPAQUE时,该结构为用目前背景色填充的背景矩形。这两种选择您可以都采用,也可以都不采用。

最后一个参数是整数数组,它指定了字符串中连续字符的间隔。程序可以使用它使字符间距变窄或变宽,因为有时需要在较窄的列中调整单个文字。该参数可以设定为NULL来使用内定的字符间距。

用于写文字的高级函数是DrawText,我们第一次遇到它是在第三章讨论HELLOWIN程序时,它不指定坐标的起始位置,而是通过RECT结构型态定义希望显示文字的区域:

DrawText (hdc, pString, iCount, &rect, iFormat) ;  

和其它文字输出函数一样,DrawText需要指向字符串的指针和字符串的长度。然而,如果在DrawText中使用以NULL结尾的字符串,就可以将iCount设定为-1,Windows会自动计算字符串的长度。

当iFormat设定为0时,Windows会将文字解释为一系列由carriagereturn字符(‘\r’或0x0D)或linefeed字符(‘\n’或0x0A)分隔的行。文字从矩形的左上角开始,carriagereturn字符或linefeed字符被解释为换行字符,因此Windows会结束目前行而开始新的一行。新的一行从矩形的左侧开始,在上一行的下面空开一个字符的高度(没有外部间隔)。包含字母的任何文字都应该显示在所剪裁矩形底部的右边或下边。

您可以使用iFormat参数更改DrawText的内定操作,iFormat由一个或多个旗标组成。DT_LEFT旗标(默认值)指定了左对齐的行,DT_RIGHT指定了向右对齐的行,而DT_CENTER指定了位于矩形左边和右边中间的行。因为DT_LEFT的值是0,所以如果只需要左对齐,就不需要包含标识符。

如果您不希望将carriagereturn字符或linefeed字符解释为换行字符,则可以包括标识符DT_SINGLELINE。然后,Windows会把carriagereturn字符和linefeed字符解释为可显示的字符,而不是控制字符。在使用DT_SINGLELINE时,还可以将行指定为位于矩形的顶端(DT_TOP)、底端(DT_BOTTOM)或者中间(DT_VCETER,V表示垂直)。

在显示多行文字时,Windows通常只在carriagereturn字符或linefeed字符处换行。然而,如果行的长度超出了矩形的宽度,则可以使用DT_WORDBREAK旗标,它使Windows在行内字的末尾换行。对于单行或多行文字的显示,Windows会把超出矩形的文字部分截去,可以使用DT_NOCLIP跳过这个操作,这个旗标还加快了函数的速度。当Windows确定多行文字的行距时,它通常使用不带外部间距的字符高度,如果您想在行距中加入外部间距,就可以使用旗标DT_EXTERNALLEADING。

如果文字中包含制表符(‘\t’或0x09),则您需要包括旗标DT_EXPANDTABS。在内定情况下,跳位间隔设定于每八个字符的位置。通过使用旗标DT_TABSTOP,您可以指定不同的跳位间隔,在这种情况下,iFormat的高字节包含了每个新跳位间隔的字符位置数值。不过我建议您避免使用DT_TABSTOP,因为iFormat的高字节也用于其它旗标。

DT_TABSTOP旗标存在的问题,可以由新的函数DrawTextEx来解决,它含有一个额外的参数:

DrawTextEx (hdc, pString, iCount, &rect, iFormat, &drawtextparams) ;  

最后一个参数是指向DRAWTEXTPARAMS结构的指针,它的定义如下:

typedef struct tagDRAWTEXTPARAMS  
{  
  UINT   cbSize ; // size of structure  
   intiTabLength ;  // size of each tab stop  
   intiLeftMargin ; // left margin  
   intiRightMargin ;// right margin  
   UINT   uiLengthDrawn ;   // receives number of characters processed  
} DRAWTEXTPARAMS, * LPDRAWTEXTPARAMS ;  

中间的三个字段是以平均字符的增量为单位的。

文字的设备内容属性

除了上面讨论的SerTextAlign外,其它几个设备内容属性也对文字产生了影响。在内定的设备内容下,文字颜色是黑色,但您可以用下面的叙述进行更改:

SetTextColor (hdc, rgbColor) ;  

使用画笔的颜色和画刷的颜色,Windows把rgbColor的值转换为纯色,您可以通过呼叫GetTextColor取得目前文字的颜色。

Windows在矩形的背景区域中显示文字,它可能根据背景模式的设定进行着色,也可能不这样做。您可以使用

SetBkMode (hdc, iMode) ;  

更改背景模式,其中iMode的值为OPAQUE或TRANSPARENT。内定的背景模式为OPAQUE,它表示Windows使用背景颜色来填充矩形的背景。您可以使用

SetBkColor (hdc, rgbColor) ;  

来改变背景颜色。rgbColor的值是转换为纯色的值。内定背景色是白色。

如果两行文字靠得太近,其中一个的背景矩形就会遮盖另一个的文字。由于这种原因,我通常希望内定的背景模式是TRANSPARENT。在背景模式为TRANSPARENT的情况下,Windows会忽略背景色,也不对矩形背景区域着色。Windows也使用背景模式和背景色对点和虚线之间的空隙及阴影刷中阴影间的区域着色,就像第五章所讨论的那样。

许多Windows程序将WHITE_BRUSH指定为Windows用于擦出窗口背景的画刷,画刷在窗口类别结构中指定。然而,您可能希望您程序的窗口背景与使用者在「控制台」中设定的系统颜色保持一致,在这种情况下,可以在WNDCLASS结构中指定背景颜色的这种方式:

wndclass.hbrBackground = COLOR_WINDOW + 1 ;  

当您想要在显示区域书写文字时,可以使用目前系统颜色设定文字色和背景色:

SetTextColor (hdc, GetSysColor (COLOR_WINDOWTEXT)) ;  
SetBkColor (hdc, GetSysColor (COLOR_WINDOW)) ;  

完成这些以后,就可以使您的程序随系统颜色的更改而变化:

caseWM_SYSCOLORCHANGE :   InvalidateRect (hwnd, NULL, TRUE) ;   break ;  

另一个影响文字的设备内容属性是字符间距。它的默认值是0,表示Windows不在字符之间添加任何空间,但您可以使用以下函数插入空间:

SetTextCharacterExtra (hdc, iExtra) ;  

参数iExtra是逻辑单位,Windows将其转换为最接近的图素,它可以是0。如果您将iExtra取为负值(希望将字符紧紧压在一起),Windows会接受这个数值的绝对值─也就是说,您不能使iExtra的值小于0。您可以通过呼叫GetTextCharacterExtra取得目前的字符间距,Windows在传回该值前会将图素间距转换为逻辑单位。

使用备用字体

当您呼叫TextOut、TabbedTextOut、ExtTextOut、DrawText或DrawTextEx书写文字时,Windows使用设备内容中目前选择的字体。字体定义了特定的字样和大小。以不同字体显示文字的最简单方法是使用Windows提供的备用字体,然而,它的范围是很有限的。

您可以呼叫下面的函数取得某种备用字体的句柄:

hFont = GetStockObject (iFont) ;  

其中,iFont是几个标识符之一。然后,您就可以将该字体选入设备内容:

SelectObject (hdc, hFont) ;  

这些您也可以只用一步完成:

SelectObject (hdc, GetStockObject (iFont)) ;  

在内定的设备内容中选择的字体称为系统字体,能够由GetStockObject的SYSTEM_FONT参数识别。这是调和的ANSI字符集字体。在GetStockObject中指定SYSTEM_FIXED_FONT(我在本书的前面几个程序中应用过),可以获得等宽字体的句柄,这一字体与Windows3.0以前的系统字体兼容。在您希望所有的字体都具有相同宽度时,这是很方便的。

备用字体OEM_FIXED_FONT也称为终端机字体,是Windows在MS-DOS命令提示窗口中使用的字体,它包括与原始IBM-PC扩展字符集兼容的字符集。Windows在窗口标题列、菜单和对话框的文字中使用DEFULT_GUI_FONT。

当您将新字体选入设备内容时,必须使用GetTextMetrics计算字符的高度和平均宽度。如果选择了调和字体,那么一定要注意,字符的平均宽度只是个平均值,某些字符会比它宽或比它窄。在本章的后面,您会了解到确定由不同宽度字符所组成的字符串总宽度的方法。

尽管GetStockObject确实提供了存取不同字体的最简单方式,但是您还不能充分控件Windows所提供的字体。不久,您会看到指定字体字样和大小的方法。

字体的背景

本章剩余的部分致力于处理不同的字体。但是在您接触这些特定程序代码前,对Windows使用字体的基本知识有一个深入的了解是很有好处的。

字体型态

Windows支持两大类字体,即所谓的「GDI字体」和「设备字体」。GDI字体储存在硬盘的文件中,而设备字体是输出设备本来就有的。例如,通常打印机都具有内建的设备字体集。

GDI字体有三种样式:点阵字体,笔划字体和TrueType字体。

点阵字体的每个字符都以位图图素图案的形式储存,每种点阵字体都有特定的纵横比和字符大小。Windows通过简单地复制图素的行或列就可以由GDI点阵字体产生更大的字符。然而,只能以整数倍放大字体,并且不能超过一定的限度。由于这种原因,GDI点阵字体又称为「不可缩放的」字体。它们不能随意地放大或缩小。点阵字体的主要优点是显示性能(显示速度很快)和可读性(因为是手工设计的,所以尽可能清晰)。

字体是通过字体名称识别的,点阵字体的字体名称为:

System (用于SYSTEM_FONT)

FixedSys (用于SYSTEM_FIXED_FONT)

Terminal (用于OEM_FIXED_FONT)

Courier

MS Serif

MS Sans Serif(用于DEFAULT_GUI_FONT)

Small Fonts

每个点阵字体只有几种大小(不超过6种)。Courier字体是定宽字体,外形与用打字机打出的字体相似。「Serif」指字体字母笔划在结束时拐个小弯。「sansserif」字体不是serif类的字体。在Windows的早期版本中,MS(Microsoft)Serif和MSSans Serif字体被称为Tms Rmn(指它与TimesRoman相似)和Helv(与Helvetica相似)。SmallFonts是专为显示小字设计的。

在Windows3.1以前,除了GDI字体外,Windows所提供的字体只有笔划字体。笔划字体是以「连结点」的方式定义的一系列线段,笔划字体可以连续地缩放,这意味着同样的字体可以用于具有任何分辨率的图形输出设备,并且字体可以放大或缩小到任意尺寸。不过,它的性能不好,小字体的可读性也很糟,而大字体由于笔划是单根直线而显得很单薄。笔划字体有时也称为绘图机字体,因为它们特别适合于绘图机,但是不适合于别的场合。笔划字体的字样有:Modern、Roman和Script。

对于GDI点阵字体和GDI笔划字体,Windows都可以「合成」粗体、斜体、加底线和加删除线,而不需要为每种属性另外储存字体。例如,对于斜体,Windows只需要将字符的上部向右移动就可以了。

接下来是Truetype,我将在本章的剩部分主要讨论它。

TrueType 字体

TrueType字体的单个字符是通过填充的直线和曲线的轮廓来定义的。Windows可以通过改变定义轮廓的坐标对TrueType字体进行缩放。

当程序开始使用特定大小的TrueType字体时,Windows「点阵化」字体。这就是说Windows使用TrueType字体文件中包括的「提示」对每个字符的连结直线和曲线的坐标进行缩放。这些提示可以补偿误差,避免合成的字符变得很难看(例如,在某些字体中,大写H的两竖应该一样宽,但盲目地缩放字体可能会导致其中一竖的图素比另一竖宽。有了提示就可以避免这些现象发生)。然后,每个字符的合成轮廓用于建立字符的位图,这些位图储存在内存以备将来使用。

最初,Windows使用了13种TrueType字体,它们的字体名称如下:

Courier New

Courier New Bold

Courier New Italic

Courier New Bold Italic

Times New Roman

Times New Roman Bold

Times New Roman Italic

Times New Roman Bold Italic

Arial

Arial Bold

Arial Italic

Arial Bold Italic

Symbol

在新的Windows版本中,这个列表更长了。在此特别指出,我将使用LucidaSans Unicode字体,它包括了一些在世界其它地方使用的字母表。

三个主要字体系列与点阵字体相似,CourierNew是定宽字体。它看起来就像是打字机输出的字体。Times NewRoman是Times字体的复制品,该字体最初为《Times ofLondon》设计,并用在许多印刷材料上,它具有很好的可读性。Arial是Helvetica字体的复制品,是一种sansserif字体。Symbol字体包含了手写符号集。

属性或样式

在上面的TrueType字体列表中,您会注意到,Courier、Times NewRoman和Arial的粗体和斜体是带有自己字体名称的单独字体,这一命名与传统的板式一致。然而,计算机使用者认为粗体和斜体只是已有字体的特殊「属性」。Windows在定义点阵字体命名、列举和选择的方式时,采用了属性的方法。但对于TrueType字体,更倾向于使用传统的命名方式。

这种冲突在Windows中还没有完全解决,简而言之,您可以完全通过命名或特定属性来选择字体。然而在处理字体列举时,应用程序需要系统中的字体列表,正如您所预料,这种双重处理使问题复杂化了。

点值

在传统的版式中,您可以用字体名称和大小来指定字体,字体的大小以点的单位来表示。一点与1/72英…己芙咏ぉに欠浅=咏虼嗽诩扑慊兴ǔ6ㄒ逦?/72英…肌5阒低ǔC枋鑫帜付ザ耍ú话ǚ⒁舴牛┑阶帜傅锥说母叨龋纾帜浮竍q」的总高度。这是一个考虑字体大小的简单方式,但它通常不是很精确。

字体的点值实际上是排版设计的概念而不是度量概念。特定字体中字符的大小可能会大于或小于其点值所表示的大小。在传统的排版中,您使用点值来指定字体的大小,在计算机排版中,还有其它方法来确定字符的实际大小。

间隔和间距

在第四章我们曾提到,可以通过呼叫GetTextMetrics取得设备内容中目前选择的字体信息,我们也多次使用过这个函数。图4-3显示了FONTMETRIC结构中字体的垂直大小。

TEXTMETRIC结构的另一个字段是tmExternalLeading,词「间隔(leading)」来自排字工人在金属字块间插入的铅,它用于在两行文字之间产生空白。tmInternalLeading值与为发音符号保留的空间有关,tmExternalLeading表示字符的连续行之间所留的附加空间。程序写作者可以使用或忽略外部的间隔值。

当我们说一个字体是8点或12点时,指的是不带内部间隔的高度。某种大写字母上的发音符号占据了分隔行的间距。这样,TEXTMETRIC结构的tmHeight值实际指行间距而不是字体的点值。字体的点值可由tmHeight减tmInternalLeading得到。

逻辑英寸问题

正如我们在第五章〈设备的大小〉一节中所讨论的,Windows 98将系统字体定义为带有12点行距的10点字体。根据在「显示属性」对话框中选择的是「小字体」还是「大字体」,该字体的tmHeight值为16或20图素,tmHeight减去tmInternalLeading的值为13或16图素。这样,字体的选择就暗指以每英…嫉牡闶ノ坏纳璞阜直媛剩≡瘛感∽痔濉辜次?6dpi,选择「大字体」即为120dpi。

您可以用LOGPIXELSX或LOGPIXELSY参数呼叫GetDeviceCaps来取得该设备分辨率。因此,96或120图素在屏幕上占有的度量距离可以称为「逻辑英…肌埂H绻贸卟饬科聊徊⒓扑阃妓兀涂赡芊⑾致呒?#8230;家仁导实挠?#8230;即笠恍裁椿嵴庋兀?/p>

在纸张上,每英…挤派?4个8点的字符很方便阅读。如果您在作文书处理或写作应用程序时,可能希望在显示器上显示清晰的8点字型,但如果使用视讯显示器的实际尺寸,就没有足够的图素清晰地显示字符。即使显示器具有足够的分辨率,在屏幕上阅读8点字体仍然会有问题。当人们阅读纸上的印刷物时,眼睛与文字的距离通常为一英…眨褂檬友断允酒魇保飧鼍嗬胪ǔN接?#8230;铡?/p>

逻辑英…加行У囟云聊唤辛朔糯螅芄幌允拘≈?点的清晰字体。而且,每英寸96点使640图素的最小显示大小等于大约6.5英…肌U馇∏∈窃谝潮呔辔?英…嫉?.5英…伎淼闹缴洗蛴〉奈淖值目矶取R蚨呒?#8230;家怖昧似聊豢矶龋】赡艽蟮叵允疚淖帧?/p>

您可能还记得在第五章,Windows NT的做法有些不同。在Windows NT中,从GetDeviceCaps中得到的LOGPIXELSX(每英…嫉耐妓厥┲挡坏扔赎ORZRES值(图素数)除以HORZSIZE值(毫米数)再乘以25.4的值。以此类似,LOGPIXELSY、VERTRES和VERTSIZE也不一致。Windows在为不同映像方式计算窗口和偏移范围时,使用HORZRES、HORZSIZE、VERTRES和VERTSIZE值。然而,显示文字的程序最好不要使用根据LOGPIXELSX和LOGPIXELSY使用假定的显示分辨率,这一点与Windows 98更为一致。

所以,在WindowsNT下,当程序以特定的点值显示文字时,它可能不使用Windows提供的映像方式,程序根据与Windows98一样的每英…嫉穆呒妓厥炊ㄒ遄约旱挠诚穹绞健N医庵钟糜谖淖值挠诚穹绞匠莆窵ogicalTwips」映像方式。您可以设定如下:

SetMapMode (hdc, MM_ANISOTROPIC) ;  
SetWindowExtEx (hdc, 1440, 1440, NULL) ;  
SetViewportExt (hdc, GetDeviceCaps (hdc, LOGPIXELSX),  GetDeviceCaps (hdc, LOGPIXELSY), NULL) ;  

使用这种映像方式设定,您能够以点值的20倍来指定字体大小,例如,为12点字取240。注意,与MM_TWIPS映像方式不同,y值在屏幕中向下增长,这在显示文字的连续行时很方便。

请记住,逻辑英寸与实际英寸间的差异仅对显示器存在。在打印设备上,GDI和尺是完全一致的。

逻辑字体

既然我们已经明确了逻辑英寸和逻辑单位的概念,那么现在我们就来讨论逻辑字体

逻辑字体是一个GDI对象,它的句柄储存在HFONT型态的变量中,逻辑字体是字体的描述。和逻辑画笔及逻辑画刷一样,它是抽象的对象,只有当应用程序呼叫SelectObject将它选入设备内容时,它才成为真实的对象。例如,对于逻辑画笔,您可以为画笔指定任意的颜色,但是在您将画笔选入设备内容时,Windows才将其转换为设备中有效的颜色。只有此时,Windows才知道设备的色彩能力。

逻辑字体的建立和选择

您可以透过呼叫CreateFont或CreateFontIndirect来建立逻辑字体。CreateFontIndirect函数接受一个指向LOGFONT结构的指针,该结构有14个字段。CreateFont函数接受14个参数,它们与LOGFONT结构的14个字段形式相同。它们是仅有的两个建立逻辑字体的函数(我提到这一点,是因为Windows中有许多用于其它字体操作的函数)。因为很难记住14个字段,所以很少使用CreateFont。因此,我主要讨论CreateFontIndirect。

有三种基本的方式用于定义LOGFONT结构中的字段,以便呼叫CreateFontIndirect:

  • 您可以简单地将LOGFONT结构的字段设定为所需的字体特征。在这种情况下,在呼叫SelectObject时,Windows使用「字体映像」算法从设备上有效的字体中选择与这些特征最匹配的字体。由于这依赖于视讯显示器和打印机上的有效字体,所以其结果可能与您的要求有相当大的差别。
  • 您可以列举设备上的所有字体并从中选择,甚至用对话框把它们显示给使用者。我将在本章后面讨论字体列举函数。不过,它们现在已经不常用了,因为第三种方法也可以进行列举。
  • 您可以采用简单的方法并呼叫ChooseFont函数,我在第十一章曾讨论过这个函数,能够使用LOGFONT结构直接建立字体。

在本章,我使用第一种和第三种方法。

下面是建立、选择和删除逻辑字体的程序:

  1. 通过呼叫CreateFont或CreateFontIndirect建立逻辑字体,这些函数传回HFONT型态的逻辑字体句柄。
  2. 使用SelectObject将逻辑字体选入设备内容,Windows会选择与逻辑字体最匹配的真实字体。
  3. 使用GetTextMetrics(及可能用到的其它函数)确定真实字体的大小和特征。在该字体选入设备内容后,可以使用这些信息来适当地设定文字的间距。
  4. 在使用完逻辑字体后,呼叫DeleteObject删除逻辑字体,当字体选入有效的设备内容时,不要删除字体,也不要删除备用字体。

GetTextFace函数使程序能够确定目前选入设备内容的字体名称:

GetTextFace (hdc, sizeof (szFaceName) / sizeof (TCHAR), szFaceName) ;  

详细的字体信息可以从GetTextMetrics中得到:

GetTextMetrics (hdc, &textmetric) ;  

其中,textmetric是TEXTMETRIC型态的变量,它具有20个字段。

稍后我将详细讨论LOGFONT和TEXTMETRIC结构的字段,这两个结构有一些相似的字段,所以它们容易混淆。现在您只需记住,LOGFONT用于定义逻辑字体,而TEXTMETRIC用于取得目前选入设备内容中的字体信息。

PICKFONT程序

使用程序17-1所示的PICKFONT,可以定义LOGFONT结构的许多字段。这个程序建立逻辑字体,并在逻辑字体选入设备内容后显示真实字体的特征。这是个方便的程序,通过它我们可以了解逻辑字体映像为真实字体的方式。

程序17-1 PICKFONT
PICKFONT.C  
/*----------------------------------------------------------------------------  
  PICKFONT.C -- 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 ;   TEXTMETRICtm ;  TCHARszFaceName [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 ;   RECTrect ;  
  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-parameters 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 =  (0xF0 & pdp->lf.lfPitchAndFamily) | DEFAULT_PITCH ; return TRUE ;  
case   IDC_FIXED_PITCH: pdp->lf.lfPitchAndFamily =   (0xF0 & pdp->lf.lfPitchAndFamily) | FIXED_PITCH ;   return TRUE ;  
case   IDC_VARIABLE_PITCH:pdp->lf.lfPitchAndFamily =   (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 =(0x0F & pdp->lf.lfPitchAndFamily) | FF_DONTCARE ;   return TRUE ;  
case   IDC_FF_ROMAN: pdp->lf.lfPitchAndFamily =(0x0F & pdp->lf.lfPitchAndFamily) | FF_ROMAN ; return TRUE ;  
case   IDC_FF_SWISS:pdp->lf.lfPitchAndFamily =   (0x0F & pdp->lf.lfPitchAndFamily) | FF_SWISS ;return TRUE ;  
case   IDC_FF_MODERN: pdp->lf.lfPitchAndFamily =   (0x0F & pdp->lf.lfPitchAndFamily) | FF_MODERN ; return TRUE ;  
case   IDC_FF_SCRIPT:pdp->lf.lfPitchAndFamily =   (0x0F & pdp->lf.lfPitchAndFamily) | FF_SCRIPT ; return TRUE ;  
case   IDC_FF_DECORATIVE: pdp->lf.lfPitchAndFamily =   (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  
//Microsoft Developer Studio generated resource script.  
#include "resource.h"  
#include "afxres.h"  
/////////////////////////////////////////////////////////////////////////////  
// Dialog  
PICKFONT DIALOG DISCARDABLE  0, 0, 348, 308  
STYLE 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 DISCARDABLE  
BEGIN  
  POPUP "&Device"  
   BEGIN   MENUITEM "&Screen",   IDM_DEVICE_SCREEN, CHECKED   MENUITEM "&Printer",  IDM_DEVICE_PRINTER  
   END  
END  
RESOURCE.H  
// Microsoft Developer Studio generated include file.  
// Used by PickFont.rc  
#defineIDC_LF_HEIGHT  1000  
#defineIDC_LF_WIDTH   1001  
#defineIDC_LF_ESCAPE  1002  
#defineIDC_LF_ORIENT  1003  
#defineIDC_LF_WEIGHT  1004  
#defineIDC_MM_TEXT1005  
#defineIDC_MM_LOMETRIC1006  
#defineIDC_MM_HIMETRIC1007  
#defineIDC_MM_LOENGLISH   1008  
#defineIDC_MM_HIENGLISH   1009  
#defineIDC_MM_TWIPS   1010  
#defineIDC_MM_LOGTWIPS1011  
#defineIDC_LF_ITALIC  1012  
#defineIDC_LF_UNDER  1013  
#defineIDC_LF_STRIKE  1014  
#defineIDC_MATCH_ASPECT   1015  
#defineIDC_ADV_GRAPHICS   1016  
#defineIDC_LF_CHARSET 1017  
#defineIDC_CHARSET_HELP   1018  
#defineIDC_DEFAULT_QUALITY 1019  
#defineIDC_DRAFT_QUALITY   1020  
#defineIDC_PROOF_QUALITY   1021  
#defineIDC_LF_FACENAME 1022  
#defineIDC_OUT_DEFAULT 1023  
#defineIDC_OUT_STRING  1024  
#defineIDC_OUT_CHARACTER   1025  
#defineIDC_OUT_STROKE  1026  
#defineIDC_OUT_TT 1027  
#defineIDC_OUT_DEVICE  1028  
#defineIDC_OUT_RASTER  1029  
#defineIDC_OUT_TT_ONLY 1030  
#defineIDC_OUT_OUTLINE 1031  
#defineIDC_DEFAULT_PITCH   1032  
#defineIDC_FIXED_PITCH 1033  
#defineIDC_VARIABLE_PITCH  1034  
#defineIDC_FF_DONTCARE 1035  
#defineIDC_FF_ROMAN   1036  
#defineIDC_FF_SWISS1037  
#defineIDC_FF_MODERN   1038  
#defineIDC_FF_SCRIPT   1039  
#defineIDC_FF_DECORATIVE  1040  
#defineIDC_TM_HEIGHT   1041  
#defineIDC_TM_ASCENT   1042  
#defineIDC_TM_DESCENT  1043  
#defineIDC_TM_INTLEAD  1044  
#defineIDC_TM_EXTLEAD  1045  
#defineIDC_TM_AVECHAR  1046  
#defineIDC_TM_MAXCHAR  1047  
#defineIDC_TM_WEIGHT   1048  
#defineIDC_TM_OVERHANG 1049  
#defineIDC_TM_DIGASPX  1050  
#defineIDC_TM_DIGASPY  1051  
#defineIDC_TM_FIRSTCHAR1052  
#defineIDC_TM_LASTCHAR 1053  
#defineIDC_TM_DEFCHAR  1054  
#defineIDC_TM_BREAKCHAR1055  
#defineIDC_TM_ITALIC   1056  
#defineIDC_TM_UNDER1057  
#defineIDC_TM_STRUCK   1058  
#defineIDC_TM_VARIABLE 1059  
#defineIDC_TM_VECTOR   1060  
#defineIDC_TM_TRUETYPE 1061  
#defineIDC_TM_DEVICE   1062  
#defineIDC_TM_FAMILY  1063  
#defineIDC_TM_CHARSET  1064  
#defineIDC_TM_FACENAME 1065  
#defineIDM_DEVICE_SCREEN  40001  
#defineIDM_DEVICE_PRINTER  40002  

图17-1显示了典型的PICKFONT屏幕显示。PICKFONT左半部分显示了一个非模态对话框,透过它,您可以选择逻辑字体结构的大部分字段。对话框的右半部分显示了字体选入设备内容后GetTextMetrics的结果。对话框的下部,程序使用这种字体显示一个字符串。因为非模态对话框非常大,所以最好在1024×768或更大的显示大小下执行这个程序。

图17-1 典型的PICKFONT屏幕显示(Windows NT下的Unicode版本)

非模态对话框还包含一些非逻辑字体结构的选项,它们是包括「LogicalTwips」方式的映像方式、「MatchAspect」选项(更改Windows将逻辑字体与真实字体匹配的方式)和「AdvGrtx Mode」(设定WindowsNT中的高级图形模式)。稍后我将对这些作详细讨论。

从「Device」菜单中,可以选择内定打印机而不是视讯显示器。在这种情况下,PICKFONT将逻辑字体选入打印机设备内容中,并从打印机显示TEXTMETRIC结构。然后,程序将逻辑字体选入窗口设备内容中,以显示样本字符串。因此,程序显示的文字可能会使用与TEXTMETRIC字段所描述的字体(打印机字体)不同的字体(屏幕字体)。

PICKFONT程序的大部分逻辑都在处理对话框的必要动作,因此我不会详细讨论该程序的工作方式,只解释建立和选择逻辑字体的原理。

逻辑字体结构

您可以呼叫CreateFont来建立逻辑字体,它是具有14个参数的函数。一般,定义一个LOGFONT型态的结构

LOGFONT lf ;  

然后再定义该结构的字段会更容易一些。完成后,可以使用指向该结构的指针呼叫CreateFontIndirect:

hFont = CreatFontIndirect (&lf) ;  

您不必设定LOGFONT结构的每个字段。如果逻辑字体结构定义为静态变量,那么所有的字段都会初始化为0,0一般是默认值。然后,可以不用更改而直接使用这个结构,CreateFontIndirect会传回字体的句柄。当您将该字体选入设备内容时,会得到一个合理的内定字体。您可以根据自己的需要,明确或模糊地填充LOGFONT结构,Windows会用一种真实字体与您的要求相匹配。

在我讨论LOGFONT结构中每个字段时,您可能想用PICKFONT程序来测试它们。当您希望程序使用您输入的任何字段时,别忘了按下Enter或「OK」按钮。

LOGFONT结构的前两个字段是逻辑单位,因此它们依赖于映像方式的目前设定:

  • lfHeight这是以逻辑单位表示的希望的字符高度。您可以将lfHeight设定0,以使用内定大小,或者根据字段代表的含义将其设定为正数或负数。如果将lfHeight设定为正数,就表示您希望该值表示含有内部间隔(不是外部间隔)的高度。实际上,所要求的字体行距为lfHeight。如果将lfHeight设定为负值,则Windows会将其绝对值作为与点值一致的字体高度。这是一个很重要的区别:如果想要特定点值的字体,可将点值转换为逻辑单位,并将lfHeight字段设定为该值的负数。如果lfHeight是正值,则TEXTMETRIC结构的tmHeight字段近似为该值(有时有微小的偏差,可能由于舍入误差所引起)。如果lfHeight是负值,则它粗略地与不包括tmInternalLeading字段的TEXTMETRIC结构的tmHeight字段相匹配。
  • lfWidth是逻辑单位的字符期望宽度。在多数情况下,可以将此值设定为0,让Windows仅根据高度选择字体。使用非零值对点阵字体并不会起太大作用,但对于TrueType字体,您能轻松地用它来获得比正常字符更宽或更窄的字体。这个字段对应于TEXTMETRIC结构的tmAveCharWidth字段。要正确使用lfWidth字段,首先把带有lfWidth字段的LOGFONT结构设定为0,建立逻辑字体,将它选入设备内容,然后呼叫GetTextMetrics。得到tmAveCharWidth字段,可按比例调节其值的大小,然后使用所调节的lfWidth的tmAveCharWidth值建立第二种字体。

下两个字段指定文字的「移位角度」和「方向」。理论上,lfEscapement使字符串能够以一定的角度书写(但每个字符的基准线仍与水平轴平行),而lfOrientation使单个字符倾斜。但是这两个字段并不是那么有效,即使现在它们只有在下面的情况下才能很好地起作用:使用TureType字体、执行WindowsNT以及首先用CM_ADVANCED旗标设定呼叫SetGraphicsMode。通过选中「AdvGrfx Mode」复选框,您能够完成PICKFONT中的最终需要。

在验证PICKFONT中的这些字段时,要注意单位是十分之一度,逆时针方向旋转。它很容易输入一个值使范例字符串消失!因此,请使用0到-600或3000到3600之间的值。

  • lfEscapement这是从水平方向上逆时针测量的十分之几的角度。它指定在书写文字时字符串的连续字符放置的方式。表17-1提供了几个例子:

表17-1

字符的放置

0

从左向右(内定)

900

向上

1800

从右向左

2700

向下

  • 在Windows 98中,这个值设定了TrueType文字的移位角度和方向。在Windows NT中,这个值通常也是这样设定,除了用GM_ADVANCED参数呼叫SetGraphicsMode时,它按文件中说明的那样工作。
  • lfOrientation这是从水平方向逆时针测量的十分之几的角度,它影响单个字符的外观。表17-2提供了几个例子:

表17-2

字符外观

0

正常(内定)

900

向右倾斜90度

1800

颠倒

2700

向左倾斜90度

  • 这个字段一般不起作用,除非在Windows NT下使用TrueType字体,并把图像模式设定为GM_ADVANCED, 在这种情况下它按文件中说明的那样工作。
  • 其余10个字段如下:
  • lfWeight这个字段使您能够指定粗体。WINGDI.H表头文件定义了可用于这个字段的一组值(参见表17-3)。

表17-3

标识符

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_BLACK

  • 事实上,它比以前用过的任何一组值都完善。您可以对标准字使用0或400,对粗体使用700。
  • lfItalic在非零值时,它指定斜体。Windows能在GDI点阵字体上合成斜体。亦即,Windows仅仅移动若干行字符位图来模仿斜体。对于TrueType字体,Windows使用真正的斜体或字体的倾斜版本。
  • lfUnderline在非零值时,它指定底线,这项属性在GDI字体上都是用合成的。也就是说,Windows GDI只是在包括空格的每个字符底线。
  • lfStrikeOut在非零值时,它指定字体上应该有一条线穿过。这也是由GDI字体合成的。
  • lfCharSet这是指定字体字符集的一个字节的值。我会在下一节「字符集和Unicode」中更详细地讨论这个字段。在PICKFONT中,您可以按下带有问号的按钮来取得能够使用的字符集列表。

注意lfCharSet字段是唯一不用零表示默认值的字段。零值相当于ANSI_CHARSET,ANSI字符在美国和西欧使用。DEFAULT_CHARSET代码等于1,表示程序执行的机器上内定的字符集。

  • lfOutPrecision它指定了Windows用实际的字体匹配期望的字体大小和特征的方式。这是一个复杂的字段,一般很少使用。请查看关于LOGFONT结构的文件以得到更详细的信息。注意,可以使用OUT_TT_ONLY_PRECIS旗标来确保得到的是TrueType字体。
  • lfClipPrecision这个字段指定了当字符的一部分位于剪裁区以外时,剪裁字符的方式。这个字段不经常使用,PICKFONT程序也没有使用它。
  • lfQuality这是一个给Windows的指令,有关于期望字体与实际字体相匹配的指令。它实际只对点阵字体有意义,并不影响TrueType字体。DRAFT_QUALITY旗标指出需要GDI缩放点阵字体以得到想要的大小;PROOF_QUALITY旗标指出不需缩放。PROOF_QUALITY字体最漂亮,但它们可能比所希望的要小一些。这个字段中也可以使用DEFAULT_QUALITY(或0)。
  • lfPitchAndFamily这个字节由两部分组成。您可以使用位或运算符号结合用于此字段的两个标识符。最低的两位指定字体是定宽(即所有字符的宽度相等)还是变宽(参见表17-4)。

表17-4

标识符

0

DEFAULT_PITCH

1

FIXED_PITCH

2

VARIABLE_PITCH

  • 字节的上半部分指定字体系列(参见表17-5)。

表17-5

标识符

0x00

FW_DONTCARE

0x10

FF_ROMAN(变宽,serifs)

0x20

FF_SWISS(变宽,非serifs)

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字段。

字体映像算法

在设定了逻辑字体结构后,呼叫CreateFontIndirect来得到逻辑字体句柄。当呼叫SelectObject把逻辑字体选入设备内容时,Windows寻找与所需字体最接近匹配的实际字体。它使用「字体映像算法」。结构的某些字段要比其它字段更重要一些。

了解字体映像的最好方式是花一些时间试验PICKFONT。以下是几条指南:

  • lfCharSet(字符集)字段是非常重要的。如果您指定了OEM_CHARSET(255),会得到某种笔划字体或终端机字体,因为它们是唯一使用OEM字符集的字体。然而,随着TrueType「Big Fonts」的出现(在第六章〈TrueType和大字体〉一节讨论过),单一的TrueType字体能映像到包括OEM字符集等不同的字符集。您需要使用SYMBOL_CHARSET(2) 来得到Symbol字体或Wingdings字体。
  • lfPitchAndFamily字段的FIXED_PITCH间距值很重要,因为您实际上告诉Windows不想处理变宽字体。
  • lfFaceName字段很重要,因为您指定了所需字体的字样。如果让lfFaceName设定为NULL,并在lfPitchAndFamily字段中将组值设定为FF_DONTCARE以外的值,因为指定了字体系列,所以该字段也很重要。
  • 对于点阵字体,Windows会试图配合lfHeight值,即使需要增加较小字体的大小。实际字体的高度总是小于或等于所需的字体,除非没有更小的字体满足您的要求。对于笔划或TrueType字体,Windows仅简单地将字体缩放到需要的高度。
  • 可以通过将lfQuality设定为PROOF_QUALITY来防止Windows缩放点阵字体。这么做可以告诉Windows所需的字体高度没有字体外观重要。
  • 如果指明了对于显示器的特定纵横比不协调的lfHeight和lfWeight值,Windows能映射到为显示器或其它不同纵横比的设备设计的点阵字体。这是得到细或粗字体的技巧(当然,对于TrueType字体是不必要的)。一般而言,您可能想避免为另一种设备挑配字体。您可以通过单击标有「Match Aspect」的复选框,在PICKFONT中完成。如果选中了复选框,PICKFONT会使用TRUE参数呼叫SetMapperFlags。

取得字体信息

在PICKFONT中非模态对话框的右侧是字体选入设备内容后从GetTextMetrics函数中获得的信息(注意,可以使用PICKFONT的「Device」菜单指出设备内容是屏幕还是内定打印机。因为在打印机上有效的字体可能不同,所以结果也可能不同)。在PICKFONT中列表的底部是从GetTextFace得到的有效字体名称。

除了数值化的纵横比以外,Windows复制到TEXTMETRIC结构的所有大小值都以逻辑单位表示。TEXTMETRIC结构的字段如下:

  • tmHeight逻辑单位的字符高度。它近似等于LOGFONT结构中指定的lfHeight字段的值,如果该值为正,它就代表行距,而非点值。如果LOGFONT结构的lfHeight字段为负,则tmHeight字段减tmInternalLeading字段应近似等于lfHeight字段的绝对值。
  • tmAscent逻辑单位的基准线以上的字符垂直大小。
  • tmDescent逻辑单位的基准线以下的字符垂直大小。
  • tmInternalLeading包含在tmHeight值内的垂直大小,通常被一些大写字母上注音符号占据。同样,可以用tmHeight值减tmInternalLeading值来计算字体的点值。
  • tmExternalLeading tmHeight以外的行距附加量,字体的设计者推荐用于隔开文字的连续行。
  • tmAveCharWidth字体中小写字母的平均宽度。
  • tmMaxCharWidth逻辑单位的字符最大宽度。对于定宽字体,这个值与tmAveCharWidth相同。
  • tmWeight字体重量,范围从0到999。实际上,这个字段为400时是标准字体,700时是粗体。
  • tmOverhangWindows在合成斜体或粗体时添加到点阵字体字符的额外宽度量(逻辑单位)。当点阵字体斜体化时,tmAveCharWidth值保持不变,因为斜体化的字符串与相同的正常字符串的总宽度相等。要为字体加粗,Windows必须稍微增加每个字符的宽度。对于粗体,tmAveCharWidth值小于tmOverhang值,等于没有加粗的相同字体的tmAveCharWidth值。
  • tmDigitizedAspectX和tmDigitizedAspectY字体合适的纵横比。它们与使用LOGPIXELSX和LOGPIXELSY标识符从GetDeviceCaps得到的值相同。
  • tmFirstChar字体中第一个字符的字符代码。
  • tmLastChar字体中最后一个字符的字符代码。如果TEXTMETRIC结构通过呼叫GetTextMetricsW(函数的宽字符版本)获得,那么这个值可能大于255。
  • tmDefaultCharWindows用于显示不在字体中的字符的字符代码,通常是矩形。
  • tmBreakChar在调整文字时,Windows和您的程序用于确定单字断开的字符。如果您不用一些奇怪的东西(例如EBCDIC字体),它就是32-空格符。
  • tmItalic对于斜体字为非零值。
  • tmUnderlined对于底线字体为非零值。
  • tmStruckOut对于删除线字体为非零值。
  • tmPitchAndFamily低四位是表示字体某些特征的旗标,由在WINGDI.H中定义的标识符指出(参见表17-6)。

表17-6

标识符

0x01

TMPF_FIXED_PITCH

0x02

TMPF_VECTOR

0x04

TMPF_TRUETYPE

0x08

TMPF_DEVICE

  • 不管TMPF_FIXED_PITCH旗标的名称是什么,如果字体字符是变宽的,则最低位为1。第二最低位(TMPF_VECTOR)对于TrueType字体和使用其它可缩放的轮廓技术的字体(如PostScript的字体)为1。TMPF_DEVICE旗标表示设备字体(即打印机内置的字体),而不是依据GDI的字体。
  • 这个字段的第四高的位表示字体系列,并且与LOGFONT的lfPitchAndFamily字段中所用的值相同。
  • tmCharSet字符集标识符。

字符集和Unicode

我在第六章讨论了Windows字符集的概念,在那里我们必须处理涉及键盘的国际化问题。在LOGFONT和TEXTMETRIC结构中,所需字体(或实际字体)的字符集由0至255之间的单个字节的数值表示。定义在WINGDI.H中的字符集标识符如下所示:

#defineANSI_CHARSET 0  
#defineDEFAULT_CHARSET  1  
#defineSYMBOL_CHARSET   2  
#defineMAC_CHARSET  77  
#defineSHIFTJIS_CHARSET 128  
#defineHANGEUL_CHARSET  129  
#defineHANGUL_CHARSET   129 
#defineJOHAB_CHARSET130  
#defineGB2312_CHARSET   134  
#defineCHINESEBIG5_CHARSET  136  
#defineGREEK_CHARSET161  
#defineTURKISH_CHARSET  162  
#defineVIETNAMESE_CHARSET   163  
#defineHEBREW_CHARSET   177  
#defineARABIC_CHARSET   178  
#defineBALTIC_CHARSET  186  
#defineRUSSIAN_CHARSET  204  
#defineTHAI_CHARSET 222  
#defineEASTEUROPE_CHARSET   238  
#defineOEM_CHARSET  255  

字符集与页码表的概念类似,但是字符集特定于Windows,且通常小于或等于255。

与本书的所有程序一样,您可以带有定义的UNICODE标识符编译PICKFONT,也可以不带UNICODE标识符编译它。和往常一样,本书内附光盘上的程序的两个版本分别位于DEBUG和RELEASE目录中。

注意,在程序的Unicode版本中PICKFONT在其窗口底部显示的字符串要更长一些。在两个版本中,字符串的字符代码由0x40到0x45、0x60到0x65。不管您选择了哪种字符集(除了SYMBOL_CHARSET),这些字符代码都显示拉丁字母表的前五个大写和小写字母(即A到E和a到e)。

当执行PICKFONT程序的非Unicode版本时,接下来的12个字符-字符代码0xC0到0xC5以及0xE0到0xE5-将依赖于所选择的字符集。对于ANSI_CHARSET,这个字符代码对应于大写和小写字母A的加重音版本。对于GREEK_CHARSET,这些代码对应于希腊字母表的字母。对于RUSSIAN_CHARSET,对应于斯拉夫字母表的字母。注意,当您选择一种字符集时,字体可能会改变,这是因为点阵字体可能没有这些字符,但TrueType字体可能有。您可能回忆起大多数TrueType字体是「Bigfonts」并且包含几种不同字符集的字母。如果您执行Windows的远东版本,这些字符会被解释为双字节字符,并且会按方块字显示,而不是按字母显示。

在WindowsNT下执行PICKFONT的Unicode版本时,代码0xC0到0xC5以及0xE0到0xE5通常是大写和小写字母A的加重音版本(除了SYMBOL_CHARSET),因为Unicode中定义了这些代码。程序也显示0x0390到0x0395以及0x03B0到0x03B5的字符代码。由于它们在Unicode中有定义,这些代码总是对应于希腊字母表的字母。同样地,程序显示0x0410到0x0415以及0x0430到0x0435的字符代码,它们对应于斯拉夫字母表的字母。然而,这些字符不可能存在于内定字体中,您必须选择GREEK_CHARSET或RUSSIAN_CHARSET来得到它们。在这种情况下,LOGFONT结构中的字符集ID不更改实际的字符集;字符集总是Unicode。而字符集ID指出来自所需字符集的字符。

现在选择HEBREW_CHARSET(代码177)。希伯来字母表不包括在Windows通常的BigFonts中,因此操作系统选择Lucida SansUnicode,这一点您可以在非模态对话框的右下角中验证。

PICKFONT也显示0x5000到0x5004的字符代码,它们对应于汉语、日语和朝鲜语象形文字的一部分。如果您执行Windows的远东版本,或者下载了比LucidaSans Unicode范围更广的免费Unicode字体,就可以看到这些。BitstreamCyberBit字体就是这样的一种字体,您可以从http://www.bitstream.com/products/world/cyberbits中找到。(LucidaSans Unicode大约有300K,而BitstreamCyberBit大约有13M)。如果您安装了这种字体,当需要一种Lucida SansUnicode不支持的字体时,Windows会选择它,这些字体如:SHIFTJIS_CHARSET(日语)、HANGUL_CHARSET(朝鲜语)、JOHAB_CHARSET(朝鲜语)、GB2312_CHARSET(简体中文)或CHINESEBIG5_CHARSET(繁体中文)。

本章的后面有一个程序可让您查看Unicode字体的所有字母。

EZFONT系统

TrueType字体系统(以传统的排版为基础)为Windows以不同的方式显示文字提供了牢固的基础。但是一些Windows的字体选择函数依据较旧技术,使得画面上的点阵字体必须趋近打印机设备字体的样子。下一节将讲到列举字体的做法,它能够使程序获得显示器或打印机上全部有效字体的列表。不过,「ChooseFont」对话框(稍后讨论)确实大幅度消除了程序行举字体的必要性。

因为标准TrueType字体可以在任何系统上使用,且这些字体可以用于显示器以及打印机,如此一来,程序在选择TrueType字体或在缺乏信息的情况下取得某种相似的字体时,就没有必要列举字体了。程序只需简单并明确地选择系统中存在的TrueType字体(当然,除非使用者故意删除它们)。这种方法与指定字体名称(可能是第十七章中〈TrueType字体〉一节中列出的13种字体中的一种)和字体大小一样简单。我把这种方法称做EZFONT(「简便字体」),程序17-2列出了它的两个文件。

程序17-2 EZFONT
EZFONT.H  
/*---------------------------------------------------------------------------  
  EZFONT.H header file  
----------------------------------------------------------------------------*/  
HFONT EzCreateFont (HDC hdc, TCHAR * szFaceName, int iDeciPtHeight,  int iDeciPtWidth, int iAttributes, BOOL fLogRes) ;  

#defineEZ_ATTR_BOLD1  
#defineEZ_ATTR_ITALIC 2  
#defineEZ_ATTR_UNDERLINE   4  
#defineEZ_ATTR_STRIKEOUT   8  
EZFONT.C  
/*----------------------------------------------------------------------------  
  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)  
{   FLOATcxDpi, cyDpi ;   HFONThFont ;   LOGFONT   lf ;   POINTpt ;   TEXTMETRICtm ;  
 
  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) (fabs (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 *fabs (pt.x) / fabs (pt.y) + 0.5) ;  hFont = CreateFontIndirect (&lf) ;  }  
 RestoreDC (hdc, -1) ;  return hFont ;  
}  

EZFONT.C只有一个函数,称为EzCreateFont,如下所示:

hFont = EzCreateFont ( hdc, szFaceName, iDeciPtHeight, iDeciPtWidth,   iAttributes, fLogRes) ;  

函数传回字体句柄。可通过呼叫SelectObject将该字体选入设备内容,然后呼叫GetTextMetrics或GetOutlineTextMetrics以确定字体尺寸在逻辑坐标中的实际大小。在程序终止前,应该呼叫DeleteObject删除任何建立的字体。

szFaceName参数可以是任何TrueType字体名称。您选择的字体越接近标准字体,则该字体在系统中存在的机率就越大。

第三个参数指出所需的点值,但是它的单位是十分之一点。因而,如果所需要的点值为十二又二分之一,则值应为125。

第四个参数通常应设定为零或与第三个参数相同。然而,通过将此字段设定为不同值可以建立更宽或更窄的TrueType字体。它以点为单位描述了字体的宽度,有时称之为字体的「全宽(em-width)」。不要将它与字体字符的平均宽度或其它类似的东西相混淆。在过去的排版技术中,大写字母M的宽度与高度是相等的。于是,「完全正方形(em-square)」的概念产生了,这是全宽测量的起源。当字体的全宽等于字体的全高(字体的点值)时,字符宽度是字体设计者设定的宽度。宽或窄的全宽值可以产生更细或更宽的字符。

您可以将iAttributes参数设定为以下定义在EZFONT.H中的值:

EZ_ATTR_BOLD  
EZ_ATTR_ITALIC  
EZ_ATTR_UNDERLINE  
EZ_ATTR_STRIKEOUT  

可以使用EZ_ATTR_BOLD或EZ_ATTR_ITALIC或者将样式作为完整TrueType字体名称的一部分。

最后,我们将参数fLogRes设定为逻辑值TRUE,以表示字体点值与设备的「逻辑分辨率」相吻合,其中「逻辑分辨率」是GetDeviceCaps函数使用LOGPIXELSX和LOGPIXELSY参数的传回值。另外,依据分辨率的字体大小是从HORZRES、HORZSIZE、VERTRES和VERTSIZE计算出来的。这仅对于WindowsNT下的视讯显示器才有所不同。

EzCreateFont函数开始只进行一些用于WindowsNT的调整。即呼叫SetGraphicsMode和ModifyWorldTransform函数,它们在Windows98下不起作用。因为WindowsNT的全球转换应该有修改字体可视大小的作用,因此在计算字体大小之前,全球转换设定为默认值-无转换。

EzCreateFont基本上设定LOGFONT结构的字段并呼叫CreateFontIndirect,CreateFontIndirect传回字体的句柄。EzCreateFont函数的主要任务是将字体的点值转换为LOGFONT结构的lfHeight字段所要求的逻辑单位。其实是首先将点值转换为设备单位(图素),然后再转换为逻辑单位。为完成第一步,函数使用GetDeviceCaps。从图素到逻辑单位的转换似乎只需简单地呼叫DPtoLP(「从设备点到逻辑点」)函数。但是为了使DPtoLP转换正常工作,在以后使用建立的字体显示文字时,相同的映像方式必须有效。这就意味着应该在呼叫EzCreateFont函数前设定映像方式。在大多数情况下,只使用一种映像方式在窗口的特定区域绘制,因此这种要求不是什么问题。

程序17-3所示的EZTEST程序不很严格地考验了EZFONT文件。此程序使用上面的EZTEST文件,还包括了本书后面程序要使用的FONTDEMO文件。

程序17-3 EZTEST
EZTEST.C  
/*--------------------------------------------------------------------------  
  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)  
{   HFONThFont ;   int  y, iPointSize ;   LOGFONT   lf ;   TCHARszBuffer [100] ;   TEXTMETRICtm ;  
// 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  
/*---------------------------------------------------------------------------  
  FONTDEMO.C --   Font Demonstration Shell Program(c) Charles Petzold, 1998  
-----------------------------------------------------------------------------*/  
#include <windows.h>  
#include "..\\EZTest\\EzFont.h"  
#include "..\\EZTest\\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)  
{   TCHARszResource [] = 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 DOCINFOdi = { 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 Demonstration 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) ;  
}  
FONTDEMO.RC  
//Microsoft Developer Studio generated resource script.  
#include "resource.h"  
#include "afxres.h"  
/////////////////////////////////////////////////////////////////////////////  
// Menu  
FONTDEMO MENU DISCARDABLE  
BEGIN  
   POPUP "&File"  
   BEGIN  MENUITEM "&Print...",   IDM_PRINT  
   END  
   POPUP "&Help"  
   BEGIN  MENUITEM "&About...",  IDM_ABOUT  
   END  
END  
RESOURCE.H  
// Microsoft Developer Studio generated include file.  
// Used by FontDemo.rc  
#define IDM_PRINT   40001  
#define IDM_ABOUT   40002  

EZTEST.C中的PaintRoutine函数将映像方式设定为LogicalTwips,然后建立字体范围从8点到12点(间隔为0.1点)的Times NewRoman字体。第一次执行此程序时,它的输出可能会使您困惑。许多行文字使用大小明显相同的字体,并且TEXTMETRIC函数也报告这些字体具有相同的高度。这一切都是点阵处理的结果。显示器上的图素是不连续的,它不能显示每一个可能的字体大小。但是,FONTDEMO外壳程序使打印输出的字体是不同的。这里您会发现字体大小区分得更加精确。

字体的旋转

您在PICKFONT中可能已经实验过了,LOGFONT结构的lfOrientation和lfEscapement字段可以旋转TrueType文字。如果仔细考虑一下,这对GDI不会造成多大困难,因为围绕原点旋转坐标点的公式是公开的。

虽然EzCreateFont不能指定字体的旋转角度,但是如FONTROT(「字体旋转」)程序展示的那样,在呼叫函数后,进行调整是非常容易的。程序17-4显示了FONTROT.C文件,该程序也需要上面显示的EZFONT文件和FONTDEMO文件。

程序17-4 FONTROT
FONTROT.C  
/*----------------------------------------------------------------------------  
  FONTROT.C --  Rotated Fonts (c) Charles Petzold, 1998  
-----------------------------------------------------------------------------*/  
#include <windows.h>  
#include "..\\eztest\\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") ;   HFONThFont ;   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 NewRoman字体相关的LOGFONT结构。然后,程序删除该字体。在for循环中,对于每隔30度的角度,建立新字体并显示文字。结果如图17-2所示。

图17-2 FONTROT的屏幕显示

如果您对图形旋转和其它线性转换的更专业方法感兴趣,并且知道您的程序在WindowsNT下执行将受到限制,您可以使用XFORM矩阵和坐标转换函数数。

字体列举

字体列举是从GDI中取得设备的全部有效字体列表的程序。程序可以选择其中一种字体,或将它们显示在对话框中供使用者选择。我先简单地介绍一下列举函数,然后显示使用ChooseFont函数的方法,ChooseFont降低了应用程序中进行字体列举的必要性。

列举函数

在Windows的早期,字体列举需要使用EnumFonts函数:

EnumFonts (hdc, szTypeFace, EnumProc, pData) ;  

程序可以列举所有的字体(将第二个参数设定为NULL)或只列出特定的字样。第三个参数是列举callback函数;第四个参数是传递给该函数的可选数据。GDI为系统中的每种字体呼叫callback函数,将定义字体的LOGFONT和TEXTMETRIC结构以及一些表示字体型态的旗标传递给它。

EnumFontFamilies函数是Windows3.1下列举TrueType字体的函数:

EnumFontFamilies (hdc, szFaceName, EnumProc, pData) ;  

通常第一次呼叫EnumFontFamilies时,第二个参数设定为NULL。为每个字体系列(例如TimesNewRoman)呼叫一次EnumProccallback函数。然后,应用程序使用该字体名称和不同的callback函数再次呼叫EnumFontFamilies。GDI为字体系列中的每种字体(例如TimesNew RomanItalic)呼叫第二个callback函数。对于非TrueType字体,向callback函数传递ENUMLOGFONT结构(它是由LOGFONT结构加上「全名」字段和「型态」字段构成,「型态」字段如文字名称「Italic」或「Bold」)和TEXTMETRIC结构,对于TrueType字体传递NEWTEXTMETRIC结构。NEWTEXTMETRIC结构相对于TEXTMETRIC结构中的信息添加了四个字段。

EnumFontFamiliesEx函数被推荐在Windows的32位的版本下使用:

EnumFontFamiliesEx (hdc, &logfont, EnumProc, pData, dwFlags) ;  

第二个参数是指向LOGFONT结构的指针,其中lfCharSet和lfFaceName字段指出了所要列举的字体信息。Callback函数在ENUMLOGFONTEX和NEWTEXTMETRICEX结构中得到每种字体的信息。

「ChooseFont」对话框

在第十一章稍微介绍了ChooseFont的通用对话框。现在,我们讨论字体列举,需要详细了解一下ChooseFont函数的内部工作原理。ChooseFont函数得到指向CHOOSEFONT结构的指针以此作为它的唯一参数,并显示列出所有字体的对话框。利用从ChooseFont中的传回值,LOGFONT结构(CHOOSEFONT结构的一部分)能够建立逻辑字体。

程序17-5所示的CHOSFONT程序展示了使用ChooseFont函数的方法,并显示了函数定义的LOGFONT结构的字段。程序也显示了在PICKFONT中显示的相同字符串。

程序17-5 CHOSFONT
  
CHOSFONT.C  
/*-----------------------------------------------------------------------------  
  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 intcyChar ;   static LOGFONTif ;   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 ;   TCHARszBuffer [64] ;   TEXTMETRIC   tm ;  
  switch (message)   {   case WM_CREATE: // Get text height  cyChar = HIWORD (GetDialogBaseUnits ()) ; // Initialize the LOGFONT structure  GetObject (GetStockObject (SYSTEM_FONT), sizeof (lf), &lf) ; // 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.lpfnHook   = 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 Developer Studio generated resource script.  
#include "resource.h"  
#include "afxres.h"  
/////////////////////////////////////////////////////////////////////////////  
// Menu  
CHOSFONT MENU DISCARDABLE  
BEGIN  
   MENUITEM "&Font!", IDM_FONT  
END  
RESOURCE.H  
// Microsoft Developer Studio generated include file.  
// Used by ChosFont.rc  
#define IDM_FONT 40001  

与一般的对话框一样,CHOOSEFONT结构的Flags字段列出了许多选项。CHOSFONT指定的CF_INITLOGFONTSTRUCT旗标使Windows根据传递给ChooseFont结构的LOGFONT结构对对话框的选择进行初始化。您可以使用旗标来指定只要列出TrueType字体(CF_TTONLY)或只要列出定宽字体(CF_FIXEDPITCHONLY)或无符号字体(CF_SCRIPTSONLY)。也可以显示屏幕字体(CF_SCREENFONTS)、打印字体(CF_PRINTERFONTS)或者两者都显示(CF_BOTH)。在后两种情况下,CHOOSEFONT结构的hDC字段必须是打印机设备内容。CHOSFONT程序使用CF_SCREENFONTS旗标。

CF_EFFECTS旗标(CHOSFONT程序使用的第三个旗标)强迫对话框包括用于底线和删除线的复选框并且允许选择文字的颜色。在程序代码中变换文字颜色不难,您可以试一试。

注意「Font」对话框中由ChooseFont显示的「Script」字段。它让使用者选择用于特殊字体的字符集,适当的字符集ID在LOGFONT结构中传回。

ChooseFont函数使用逻辑英…即拥阒抵屑扑鉲fHeight字段。例如,假定您从「显示属性」对话框中安装了「小字体」。这意味着带有视讯显示装置内容的GetDeviceCaps和参数LOGPIXELSY传回96。如果使用ChooseFont选择72点的TimesRoman字体,实际上是想要1英…几叩淖痔濉5盋hooseFont传回后,LOGFONT结构的lfHeight字段等于-96(注意负号),这是指字体的点值等于96图素,或者1逻辑英…肌?/p>

以上大概是我们想要知道的。但请记住以下几点:

  • 如果在Windows NT下设定了度量映像方式,则逻辑坐标与字体的实际大小不一致。例如,如果在依据度量映像方式的文字旁画一把尺,会发现它与字体不搭调。应该使用上面描述的Logical Twips映射方式来绘制图形,才能与字体大小一致。
  • 如果要使用任何非MM_TEXT映像方式,请确保在把字体选入设备内容和显示文字时,没有设定映像方式。否则, GDI会认为LOGFONT结构的lfHeight字段是逻辑坐标。
  • 由ChooseFont设定的LOGFONT结构的lfHeight字段总是图素值,并且它只适用于视讯显示器。当您为打印机设备内容建立字体时,必须调整lfHeight值。ChooseFont函数使用CHOOSEFONT结构的hDC字段只为获得列在对话框中的打印机字体。此设备内容句柄不影响lfHeight值。

幸运的是,CHOOSEFONT结构包括一个iPointSize字段,它提供以十分之一点为单位的所选字体的大小。无论是什么设备内容和映像方式,都能把这个字段转化为逻辑大小并用于lfHeight字段。在EZFONT.C中能找到合适的程序代码,您可以根据需要简化它。

另一个使用ChooseFont的程序是UNICHARS,这个程序让您查看一种字体的所有字符,对于研究LucidaSans Unicode字体(内定的显示字体)或BitstreamCyberBit字体尤其有用。UNICHARS总是使用TextOutW函数来显示字体的字符,因此可以在WindowsNT或Windows 98下执行它。

程序17-6 UNICHARS
  
UNICHARS.C  
/*---------------------------------------------------------------------------  
  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 requies 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 ;   intcxChar, cyChar, x, y, i, cxLabels ;   PAINTSTRUCT  ps ;   SIZE size ;   TCHARszBuffer [8] ;   TEXTMETRIC   tm ;   WCHARch ;  
 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 Developer Studio generated resource script.  
#include "resource.h"  
#include "afxres.h"  
/////////////////////////////////////////////////////////////////////////////  
// Menu  
UNICHARS MENU DISCARDABLE  
BEGIN  
  MENUITEM "&Font!",   IDM_FONT  
END  
RESOURCE.H  
// Microsoft Developer Studio generated include file.  
// Used by Unichars.rc  
#define IDM_FONT40001  

段落格式

具有选择并建立逻辑字体的能力后,就可以处理文字格式了。这个程序包括以四种方式之一来把文字的每一行放在页边距内:左对齐、向右对齐、居中或分散对齐-即从页边距的一端到另一端,文字间距相等。对于前三种方式,可以使用带有DT_WORDBREAK参数的DrawText函数,但这种方法有局限性。例如,您无法确定DrawText会把文字的哪个部分恰好放在矩形内。DrawText对于一些简单任务是很方便的,但对更复杂的格式化任务,则可能要用到TextOut。

简单文字格式

对文字的最有用的一个函数是GetTextExtentPoint32(这个函数的名称显示了Windows早期版本的一些变化)。该函数根据设备内容中选入的目前字体得出字符串的宽度和高度:

GetTextExtentPoint32 (hdc, pString, iCount, &size) ;  

逻辑单位的文字宽度和高度在SIZE结构的cx和cy字段中传回。我使用一行文字的例子,假定您把一种字体选入设备内容,现在要写入文字:

TCHAR * szText [] = TEXT ("Hello, how are you?") ;  

您希望文字从垂直坐标yStart开始,页边距由坐标xLeft和xRight设定。您的任务就是计算文字开始处的水平坐标的xStart值。

如果文字以定宽字体显示,那么这项任务就相当容易,但通常不是这样的。首先您得到字符串的文字宽度:

GetTextExtentPoint32 (hdc, szText, lstrlen (szText), &size) ;  

如果size.cx比(xRight-xLeft)大,这一行就太长了,不能放在页边距内。我们假定它能放进去。

要向左对齐文字,只要把xStart设定为与xLeft相等,然后写入文字:

TextOut (hdc, xStart, yStart, szText, lstrlen (szText)) ;  

这很容易。现在可以把size.cy加到yStart中写下一行文字了。

要向右对齐文字,用以下公式计算xStart:

xStart = xRight - size.cx ;  

居中文字用以下公式:

xStart = (xLeft + xRight - size.cx) / 2 ;  

现在开始艰巨的任务-在左右页边距内分散对齐文字。页边距之间的距离是(xRight-xLeft)。如不调整,文字宽度就是size.cx。两者之差

xRight - xLeft - size.cx  

必须在字符串的三个空格字符处平均配置。这听起来很讨厌,但还不是太糟。可以呼叫

SetTextJustification (hdc, xRight - xLeft - size.cx, 3)  

来完成。第二个参数是字符串内空格字符中需要分配的空间量。第三个参数是空格字符的数量,这里为3。现在把xStart设定与xLeft相等,用TextOut写入文字:

TextOut (hdc, xStart, yStart, szText, lstrlen (szText)) ;  

文字会在xLeft和xRight页边距之间分散对齐。

无论何时呼叫SetTextJustification,如果空间量不能在空格字符中平均分配,它就会累积一个错误值。这将影响后面的GetTextExtentPoint32呼叫。每次开始新的一行,都必须通过呼叫

SetTextJustification (hdc, 0, 0) ;  

来清除错误值。

使用段落

如果您处理整个段落,就必须从头开始并扫描字符串来寻找空格字符。每当碰到一个空格(或其它能用于断开一行的字符),需呼叫GetTextExtentPoint32来确定文字是否能放入左右页边距之间。当文字超出允许的空间时,就要退回上一个空白。现在,您已经能够确定一行的字符串了。如果想要分散对齐该行,呼叫SetTextJustification和TextOut,清除错误值,并继续下一行。

显示在程序17-7中的JUSTIFY1对Mark Twain的《The Adventures ofHuckleberryFinn》中的第一段做了这样的处理。您可以从对话框中选择想要的字体,也可以使用菜单选项来更改对齐方式(左对齐、向右对齐、居中或分散对齐)。图17-3是典型的JUSTIFY1屏幕显示。

程序17-7 JUSTIFY1
  
JUSTIFY1.C  
/*--------------------------------------------------------------------------  
  JUSTIFY1.C -- Justified Type Program #1(c) Charles Petzold, 1998  
---------------------------------------------------------------------------*/  
#include <windows.h>  
#include "resource.h"  

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;  
TCHAR szAppName[] = TEXT ("Justify1") ;  

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow)  
{   HWND  hwnd ;   MSG   msg ;   WNDCLASSwndclass ;  
  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 ("Justified Type #1"),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 ;  
}  

void DrawRuler (HDC hdc, RECT * prc)  
{   static int iRuleSize [16] = {360, 72,144, 72,216, 72,144,72,  
288, 72,144, 72,216, 72,144,72 } ;   int  i, j ;   POINTptClient ;  
  SaveDC (hdc) ;  // Set Logical Twips mapping mode   SetMapMode (hdc, MM_ANISOTROPIC) ;   SetWindowExtEx (hdc, 1440, 1440, NULL) ;   SetViewportExtEx (hdc,GetDeviceCaps (hdc, LOGPIXELSX),  GetDeviceCaps (hdc, LOGPIXELSY), NULL) ;  
 // Move the origin to a half inch from upper left  
  SetWindowOrgEx (hdc, -720, -720, NULL) ;  // Find the right margin (quarter inch from right)   ptClient.x = prc->right ;   ptClient.y = prc->bottom ;   DPtoLP (hdc, &ptClient, 1) ;   ptClient.x -= 360 ;  
 // Draw the rulers  
MoveToEx  (hdc, 0,  -360, NULL) ;   LineTo(hdc, ptClient.x, -360) ;   MoveToEx  (hdc, -360,  0, NULL) ;   LineTo(hdc, -360, ptClient.y) ;  
  for (i = 0, j = 0 ; i <= ptClient.x ; i += 1440 / 16, j++)  
{  MoveToEx  (hdc, i, -360, NULL) ;  LineTo(hdc, i, -360 - iRuleSize [j % 16]) ;   }  
  for (i = 0, j = 0 ; i <= ptClient.y ; i += 1440 / 16, j++)  
{  MoveToEx  (hdc, -360, i, NULL) ;LineTo(hdc, -360 - iRuleSize [j % 16], i) ;  
}  
  RestoreDC (hdc, -1) ;  
}  
void Justify (HDC hdc, PTSTR pText, RECT * prc, int iAlign)  
{   int   xStart, yStart, cSpaceChars ;   PTSTR pBegin, pEnd ;   SIZE  size ;  
  yStart = prc->top ;   do// for each text line  
{   cSpaceChars = 0 ; // initialize number of spaces in line  
while (*  pText == '')  // skip over leading spaces pText++ ;  
  pBegin = pText ; // set pointer to char at beginning of line  do   // until the line is known  { pEnd =pText ;// set pointer to char at end of line  
 // skip to next space  while (*pText != '\0' && *pText++ != ' ') ;  
   if (*pText == '\0')   break ;  
  // after each space encountered, calculate extents  
   cSpaceChars++ ; GetTextExtentPoint32(hdc, pBegin, pText - pBegin - 1, &size) ; }  while (size.cx < (prc->right - prc->left)) ;  cSpaceChars-- ;  // discount last space at end of line  while (*(pEnd - 1) == ' ')// eliminate trailing spaces  { pEnd-- ; cSpaceChars-- ;   }  
// if end of text and no space characters, set pEnd to end  if (*  pText == '\0' || cSpaceChars <= 0) pEnd = pText ;  
GetTextExtentPoint32 (hdc, pBegin, pEnd - pBegin, &size) ;  switch (iAlign)  // use alignment for xStart  {  case   IDM_ALIGN_LEFT: xStart = prc->left ; break ;   case   IDM_ALIGN_RIGHT: xStart = prc->right - size.cx ;   break ;   case   IDM_ALIGN_CENTER: xStart = (prc->right + prc->left - size.cx) / 2 ; break ;   case   IDM_ALIGN_JUSTIFIED: if (*  pText != '\0' && cSpaceChars > 0)  
SetTextJustification (hdc, prc->right-prc->left - size.cx, cSpaceChars) ; xStart = prc->left ; break ;  } // display the text  TextOut (hdc, xStart, yStart, pBegin, pEnd - pBegin) ;  
// prepare for next line  
SetTextJustification (hdc, 0, 0) ;  yStart += size.cy ;  pText = pEnd ;  
}   while (*pText && yStart < prc->bottom - size.cy) ;  
}  

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)  
{  static CHOOSEFONT cf ;   static DOCINFO  di = { sizeof (DOCINFO), TEXT ("Justify1: Printing") } ;   static int   iAlign = IDM_ALIGN_LEFT ;   static LOGFONTlf ;   static PRINTDLG   pd ;   static TCHAR szText[] = {  
 TEXT ("You don't know about me, without you ")  
 TEXT ("have read a book by the name of \"The ")  
 TEXT ("Adventures of Tom Sawyer,\" but that ")  
 TEXT ("ain't no matter. That book was made by ")  
 TEXT ("Mr. Mark Twain, and he told the truth, ")  
 TEXT ("mainly. There was things which he ")  
 TEXT ("stretched, but mainly he told the truth. ")  
 TEXT ("That is nothing. I never seen anybody ")  
 TEXT ("but lied, one time or another, without ")  
 TEXT ("it was Aunt Polly, or the widow, or ")  
 TEXT ("maybe Mary. Aunt Polly -- Tom's Aunt ")  
 TEXT ("Polly, she is -- and Mary, and the Widow ")  
 TEXT ("Douglas, is all told about in that book ")  
 TEXT ("-- which is mostly a true book; with ")  
 TEXT ("some stretchers, as I said before.") } ;   BOOL fSuccess ;   HDC  hdc, hdcPrn ;   HMENUhMenu ;   int  iSavePointSize ;   PAINTSTRUCT   ps ;   RECT rect ;  
  switch (message)   {   case   WM_CREATE: // Initialize the CHOOSEFONT structure  
GetObject (GetStockObject (SYSTEM_FONT), sizeof (lf), &lf) ;  
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.lpfnHook = NULL ;  cf.lpTemplateName= NULL ;  cf.hInstance = NULL ;  cf.lpszStyle = NULL ;  cf.nFontType = 0 ;  cf.nSizeMin  = 0 ;  cf.nSizeMax = 0 ;  
return 0 ;  
 case   WM_COMMAND:  hMenu = GetMenu (hwnd) ;  switch (LOWORD (wParam))  {  case IDM_FILE_PRINT: // Get printer DC  
   pd.lStructSize= sizeof (PRINTDLG) ; 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 ; } // Set margins of 1 inch  
rect.left =  GetDeviceCaps (hdcPrn, LOGPIXELSX) - GetDeviceCaps (hdcPrn, PHYSICALOFFSETX) ;  
rect.top  = GetDeviceCaps (hdcPrn, LOGPIXELSY)   GetDeviceCaps (hdcPrn, PHYSICALOFFSETY) ;  
rect.right =   GetDeviceCaps (hdcPrn, PHYSICALWIDTH) -   GetDeviceCaps (hdcPrn, LOGPIXELSX) -   GetDeviceCaps (hdcPrn, PHYSICALOFFSETX) ;  
rect.bottom=   GetDeviceCaps (hdcPrn, PHYSICALHEIGHT) -   GetDeviceCaps (hdcPrn, LOGPIXELSY) -   GetDeviceCaps (hdcPrn, PHYSICALOFFSETY) ;  
  // Display text on printer  
  SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ;  
   fSuccess = FALSE ;  
if ((StartDoc (hdcPrn, &di) > 0) && (StartPage (hdcPrn) > 0)) { // Select font using adjusted lfHeight  
 iSavePointSize = lf.lfHeight ;   lf.lfHeight = -(GetDeviceCaps (hdcPrn, LOGPIXELSY) *  cf.iPointSize) / 720 ;  
 SelectObject (hdcPrn, CreateFontIndirect (&lf)) ;   lf.lfHeight = iSavePointSize ;  
 // Set text color  SetTextColor (hdcPrn, cf.rgbColors) ; // Display text  
 Justify (hdcPrn, szText, &rect, iAlign) ;  
 if (EndPage (hdcPrn) > 0){ fSuccess = TRUE ;   EndDoc (hdcPrn) ;} } ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ;  
   DeleteDC (hdcPrn) ;  
   if (!fSuccess)  MessageBox (hwnd, TEXT ("Could not print text"),   szAppName, MB_ICONEXCLAMATION | MB_OK) ;  return 0 ;  
  case   IDM_FONT: if (ChooseFont (&cf)) InvalidateRect (hwnd, NULL, TRUE) ; return 0 ;   case   IDM_ALIGN_LEFT:  case   IDM_ALIGN_RIGHT:  case   IDM_ALIGN_CENTER:  case   IDM_ALIGN_JUSTIFIED: CheckMenuItem (hMenu, iAlign, MF_UNCHECKED) ; iAlign = LOWORD (wParam) ; CheckMenuItem (hMenu, iAlign, MF_CHECKED) ; InvalidateRect (hwnd, NULL, TRUE) ; return 0 ;  } return 0 ;  
 case   WM_PAINT:  hdc = BeginPaint (hwnd, &ps) ;  GetClientRect (hwnd, &rect) ;  DrawRuler (hdc, &rect) ;  rect.left += GetDeviceCaps (hdc, LOGPIXELSX) / 2 ;  rect.top  += GetDeviceCaps (hdc, LOGPIXELSY) / 2 ;  rect.right-= GetDeviceCaps (hdc, LOGPIXELSX) / 4 ;  
SelectObject (hdc, CreateFontIndirect (&lf)) ;  SetTextColor (hdc, cf.rgbColors) ; Justify (hdc, szText, &rect, iAlign) ;  DeleteObject (SelectObject (hdc, GetStockObject (SYSTEM_FONT))) ;  EndPaint (hwnd, &ps) ;  return 0 ;   case   WM_DESTROY:  PostQuitMessage (0) ;  return 0 ;  
}   return DefWindowProc (hwnd, message, wParam, lParam) ;  
}  
JUSTIFY1.RC  
//Microsoft Developer Studio generated resource script.  
#include "resource.h"  
#include "afxres.h"  
/////////////////////////////////////////////////////////////////////////////  
// Menu  
JUSTIFY1 MENU DISCARDABLE BEGIN POPUP "&File"  
   BEGIN  MENUITEM "&Print",   IDM_FILE_PRINT  
   END  
   POPUP "&Font"  
   BEGIN   MENUITEM "&Font...",  IDM_FONT  
   END  
   POPUP "&Align"  
   BEGIN  MENUITEM "&Left",  IDM_ALIGN_LEFT, CHECKED  MENUITEM "&Right", IDM_ALIGN_RIGHT  MENUITEM "&Centered",  IDM_ALIGN_CENTER  MENUITEM "&Justified", IDM_ALIGN_JUSTIFIED  
END  
END  
RESOURCE.H  
// Microsoft Developer Studio generated include file.  
// Used by Justify1.rc  
#defineIDM_FILE_PRINT   40001  
#defineIDM_FONT 40002  
#defineIDM_ALIGN_LEFT   40003  
#defineIDM_ALIGN_RIGHT  40004  
#defineIDM_ALIGN_CENTER 40005  
#defineIDM_ALIGN_JUSTIFIED  40006  

JUSTIFY1在显示区域的上部和左侧显示了标尺(当然单位是逻辑英…迹1瓿哂蒁rawRuler函数画出。一个矩形结构定义了分散对齐文字的区域。

涉及对文字进行格式处理的大量工作由Justify函数实作。函数搜寻文字开始的空白,并使用GetTextExtentPoint32测量每一行。当行的长度超过显示区域的宽度,JUSTIFY1传回先前的空格并使该行到达linefeed处。根据iAlign常数的值,行的对齐方式有:同左对齐、向右对齐、居中或分散对齐。

JUSTIFY1并不完美。例如,它没有处理连字符的问题。此外,当每行少于两个字时,分散对齐的做法会失效。即使我们解决了这个不是特别难的问题,当一个单字太长在左右边距间放不下时,程序仍不能正常运作。当然,当我们在程序中对同一行使用多种字体(如同Windows文书处理程序轻松做出的那样)时,情况会更复杂。还没有人声称这种处理容易,它只是比我们亲自做所有的工作容易一些。

图17-3 典型的JUSTIFY1屏幕显示

打印输出预览

有些字体不是为了在屏幕上查看用的,这些字体是用于打印的。通常在这种情况下,文字的屏幕预览必须与打印输出的格式精确配合。显示同样的字体、大小和字符格式是不够的。使用TrueType是个快捷方式。另外还需要将段落中的每一行在同样位置断开。这是WYSIWYG中的难点。

JUSTIFY1包含一个「Print」选项,但该选项仅在页面的上、左和右边设定1英…嫉谋呔唷U庋袷交耆肫聊幌允酒魑薰亍U饫镉幸桓鲇腥さ牧废埃涸贘USTIFY1中更改几行程序代码,使屏幕和打印机逻辑依据一个6英…嫉母袷交匦巍7椒ň褪窃赪M_PAINT和「Print」命令处理程序中更改rect.right的定义。在WM_PAINT处理程序中,相对应叙述为:

rect.right = rect.left + 6 * GetDeviceCaps (hdc, LOGPIXELSX) ;  

在「Print」命令处理程序中,相对应叙述为:

rect.right = rect.left + 6 * GetDeviceCaps (hdcPrn, LOGPIXELSX) ;  

如果选择了一种TrueType字体,屏幕上的linefeed情况应与打印机的输出相同。

但实际情况并不是这样。即使两种设备使用同样点值的相同字体,并将文字显示在同样的格式化矩形中,不同的显示分辨率及凑整误差也会使linefeed出现在不同地方。显然,需要一种更高明的方法进行屏幕上的打印输出预览。

程序17-8所示的JUSTIFY2示范了这种方法的一个尝试。JUSTIFY2中的程序代码是依据Microsoft的DavidWeise所写的TTJUST(「TrueTypeJustify」)程序,而该程序又是依据本书前面的一个版本中的JUSTIFY1程序。为表现出这一程序中所增加的复杂性,用HermanMelville的《Moby-Dick》中的第一章代替了Mark Twain小说的摘录。

程序17-8 JUSTIFY2
  
JUSTIFY2.C  
/*-----------------------------------------------------------------------------  
  JUSTIFY2.C -- Justified Type Program #2(c) Charles Petzold, 1998  
-----------------------------------------------------------------------------*/  
#include <windows.h>  
#include "resource.h"  

#define OUTWIDTH 6// Width of formatted output in inches  
#define LASTCHAR 127  // Last character code used in text  

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;  
TCHAR szAppName[] = TEXT ("Justify2") ;  
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 ("Justified Type #2"), 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 ;  
}  

void DrawRuler (HDC hdc, RECT * prc)  
{   static int iRuleSize [16] = {360,72,144, 72,216,72,144,72,288,72,144,  
72,216,72,144, 72 } ;   int   i, j ;   POINT ptClient ;  
  SaveDC (hdc) ;  // Set Logical Twips mapping mode   SetMapMode (hdc, MM_ANISOTROPIC) ;   SetWindowExtEx (hdc, 1440, 1440, NULL) ;   SetViewportExtEx (hdc,GetDeviceCaps (hdc, LOGPIXELSX), GetDeviceCaps (hdc, LOGPIXELSY), NULL) ;  
 // Move the origin to a half inch from upper left  
  SetWindowOrgEx (hdc, -720, -720, NULL) ;// Find the right margin (quarter inch from right)   ptClient.x = prc->right ;   ptClient.y = prc->bottom ;   DPtoLP (hdc, &ptClient, 1) ;   ptClient.x -= 360 ;  
 // Draw the rulers   MoveToEx  (hdc, 0,-360, NULL) ;   LineTo(hdc, OUTWIDTH * 1440,-360) ;   MoveToEx  (hdc, -360,   0, NULL) ;   LineTo(hdc, -360,  ptClient.y) ;  
  for (i = 0, j = 0 ;   i <= ptClient.x && i <= OUTWIDTH * 1440 ; i += 1440 / 16, j++)   {  MoveToEx  (hdc, i, -360, NULL) ;  LineTo(hdc, i, -360 - iRuleSize [j % 16]) ;   }  
 
for (i = 0, j = 0 ; i <= ptClient.y ; i += 1440 / 16, j++)   {  MoveToEx  (hdc, -360, i, NULL) ;  LineTo(hdc, -360 - iRuleSize [j % 16], i) ;  
}  
  RestoreDC (hdc, -1) ;  
}  

/*---------------------------------------------------------------------------  
  GetCharDesignWidths:  Gets character widths for font as large as theoriginal design size  
----------------------------------------------------------------------------*/  

UINT GetCharDesignWidths (HDC hdc, UINT uFirst, UINT uLast, int * piWidths)  
{   HFONT   hFont, hFontDesign ;   LOGFONT  lf ;   OUTLINETEXTMETRIC otm ;  
 hFont = GetCurrentObject (hdc, OBJ_FONT) ;   GetObject (hFont, sizeof (LOGFONT), &lf) ;  
// Get outline text metrics (we'll only be using a field that is  //independent of the DC the font is selected into)  
 otm.otmSize = sizeof (OUTLINETEXTMETRIC) ;   GetOutlineTextMetrics (hdc, sizeof (OUTLINETEXTMETRIC), &otm) ;  
// Create a new font based on the design size   lf.lfHeight   = - (int) otm.otmEMSquare ;   lf.lfWidth  = 0 ;   hFontDesign   = CreateFontIndirect (&lf) ;  
// Select the font into the DC and get the character widths  
 SaveDC (hdc) ;   SetMapMode (hdc, MM_TEXT) ;   SelectObject (hdc, hFontDesign) ;  
 GetCharWidth (hdc, uFirst, uLast, piWidths) ;   SelectObject (hdc, hFont) ;   RestoreDC (hdc, -1) ;  
// Clean up   DeleteObject (hFontDesign) ;   return otm.otmEMSquare ;  
}  

/*--------------------------------------------------------------------------  
  GetScaledWidths:  Gets floating point character widths for selectedfont size  
----------------------------------------------------------------------------*/  

void GetScaledWidths (HDC hdc, double * pdWidths)  
{   doubledScale ;   HFONT hFont ;   int   aiDesignWidths [LASTCHAR + 1] ;   int   i ;   LOGFONT   lf ;   UINT  uEMSquare ;  
// Call function above  
 uEMSquare = GetCharDesignWidths (hdc, 0, LASTCHAR, aiDesignWidths) ;  // Get LOGFONT for current font in device context   hFont = GetCurrentObject (hdc, OBJ_FONT) ;   GetObject (hFont, sizeof (LOGFONT), &lf) ;  // Scale the widths and store as floating point values   dScale = (double) -lf.lfHeight / (double) uEMSquare ;   for (  i = 0 ; i <= LASTCHAR ; i++)  pdWidths[i] = dScale * aiDesignWidths[i] ;  
}  

/*----------------------------------------------------------------------------  
  GetTextExtentFloat:  Calculates text width in floating point  
----------------------------------------------------------------------------*/  

double GetTextExtentFloat (double * pdWidths, PTSTR psText, int iCount)  
{   double dWidth = 0 ;   int   i ;  
 for (  i = 0 ; i < iCount ; i++)  dWidth += pdWidths [psText[i]] ;  

return dWidth ;  
}  

/*----------------------------------------------------------------------------  
  Justify: Based on design units for screen/printer compatibility  
-----------------------------------------------------------------------------*/  

void Justify (HDC hdc, PTSTR pText, RECT * prc, int iAlign)  
{   double dWidth, adWidths[LASTCHAR + 1] ;   int   xStart, yStart, cSpaceChars ;   PTSTR pBegin, pEnd ;   SIZE  size ;  
// Fill the adWidths array with floating point character widths  
 GetScaledWidths (hdc, adWidths) ;   yStart = prc->top ;   do// for each text line  
{  cSpaceChars = 0 ;// initialize number of spaces in line  
while (*pText == ' ')// skip over leading spacespText++ ;  
 pBegin = pText ;  // set pointer to char at beginning of line   do   // until the line is known  {  pEnd = pText ;// set pointer to char at end of line  
  // skip to next space   while (*pText != '\0' && *pText++ != ' ') ;  
if (*pText == '\0') break ;  
// after each space encountered, calculate extents  
cSpaceChars++ ;  dWidth = GetTextExtentFloat (adWidths,   pBegin,   pText - pBegin - 1) ;  }  while (dWidth < (double) (prc->right - prc->left)) ;  cSpaceChars-- ;// discount last space at end of line  while (*(pEnd - 1) == ' ')// eliminate trailing spaces  {  pEnd-- ;  cSpaceChars-- ;  }  
// if end of text and no space characters, set pEnd to end  if (*pText == '\0' || cSpaceChars <= 0) pEnd = pText ;  
// Now get integer extents  
GetTextExtentPoint32(hdc, pBegin, pEnd - pBegin, &size) ;  switch (iAlign)   // use alignment for xStart  {  case   IDM_ALIGN_LEFT: xStart = prc->left ; break ;  
case   IDM_ALIGN_RIGHT: xStart = prc->right - size.cx ; break ;  case   IDM_ALIGN_CENTER: xStart = (prc->right + prc->left - size.cx) / 2 ; break ;   case   IDM_ALIGN_JUSTIFIED: if (*pText != '\0' && cSpaceChars > 0) SetTextJustification (hdc, prc->right - prc->left - size.cx,   cSpaceChars) ; xStart = prc->left ; break ;  } // display the text  TextOut (hdc, xStart, yStart, pBegin, pEnd - pBegin) ;  
   // prepare for next line  
SetTextJustification (hdc, 0, 0) ;  yStart += size.cy ;  pText = pEnd ;  
}   while (*pText && yStart < prc->bottom - size.cy) ;  
}  

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)  
{   static CHOOSEFONTcf ;   staticDOCINFO   di = { sizeof (DOCINFO), TEXT ("Justify2: Printing") } ;   static int   iAlign = IDM_ALIGN_LEFT ;   static LOGFONT   lf ;   static PRINTDLG  pd ;   static TCHAR szText[] = {  
  TEXT ("Call me Ishmael. Some years ago -- never ")  
  TEXT ("mind how long precisely -- having little ")  
  TEXT ("or no money in my purse, and nothing ")  
  TEXT ("particular to interest me on shore, I ")  
  TEXT ("thought I would sail about a little and ")  
  TEXT ("see the watery part of the world. It is ")  
  TEXT ("a way I have of driving off the spleen, ")  
  TEXT ("and regulating the circulation. Whenever ")  
  TEXT ("I find myself growing grim about the ")  
  TEXT ("mouth; whenever it is a damp, drizzly ")  
  TEXT ("November in my soul; whenever I find ")  
  TEXT ("myself involuntarily pausing before ")  
  TEXT ("coffin warehouses, and bringing up the ")  
  TEXT ("rear of every funeral I meet; and ")  
  TEXT ("especially whenever my hypos get such an ")  
  TEXT ("upper hand of me, that it requires a ")  
  TEXT ("strong moral principle to prevent me ")  
  TEXT ("from deliberately stepping into the ")  
  TEXT ("street, and methodically knocking ")  
  TEXT ("people's hats off -- then, I account it ")  
  TEXT ("high time to get to sea as soon as I ")  
  TEXT ("can. This is my substitute for pistol ")  
  TEXT ("and ball. With a philosophical flourish ")  
  TEXT ("Cato throws himself upon his sword; I ")  
 TEXT ("quietly take to the ship. There is ")  
  TEXT ("nothing surprising in this. If they but ")  
  TEXT ("knew it, almost all men in their degree, ")  
  TEXT ("some time or other, cherish very nearly ")  
  TEXT ("the same feelings towards the ocean with ")  
  TEXT ("me.") } ;   BOOL fSuccess ;   HDC  hdc, hdcPrn ;   HMENUhMenu ;   int iSavePointSize ;   PAINTSTRUCT  ps ;   RECT rect ;  
  switch (message)   {   case   WM_CREATE: // Initialize the CHOOSEFONT structure  
hdc = GetDC (hwnd) ;  lf.lfHeight = - GetDeviceCaps (hdc, LOGPIXELSY) / 6 ;  lf.lfOutPrecision = OUT_TT_ONLY_PRECIS ;  lstrcpy (lf.lfFaceName, TEXT ("Times New Roman")) ;  ReleaseDC (hwnd, hdc) ;  
cf.lStructSize   = sizeof (CHOOSEFONT) ;  cf.hwndOwner = hwnd ;  cf.hDC   = NULL ;  cf.lpLogFont= &lf ;  cf.iPointSize= 120 ;  
   // Set flags for TrueType only!  
cf.Flags= CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS | CF_TTONLY | CF_EFFECTS ;  cf.rgbColors = 0 ;  cf.lCustData  = 0 ;  cf.lpfnHook   = NULL ;  cf.lpTemplateName= NULL ;  cf.hInstance  = NULL ;  cf.lpszStyle  = NULL ;  cf.nFontType  = 0 ; cf.nSizeMin   = 0 ;  cf.nSizeMax   = 0 ;  
return 0 ;  
 case   WM_COMMAND:  hMenu = GetMenu (hwnd) ;  switch (LOWORD (wParam))  {  case   IDM_FILE_PRINT:// Get printer DC  
   pd.lStructSize = sizeof (PRINTDLG) ;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 ;} // Set margins for OUTWIDTH inches wide  
   rect.left = (GetDeviceCaps (hdcPrn, PHYSICALWIDTH) -   GetDeviceCaps (hdcPrn, LOGPIXELSX)*OUTWIDTH)/2  -  GetDeviceCaps (hdcPrn, PHYSICALOFFSETX) ; rect.right = rect.left +   GetDeviceCaps (hdcPrn, LOGPIXELSX) * OUTWIDTH ;  
// Set margins of 1 inch at top and bottom  
   rect.top  = GetDeviceCaps (hdcPrn, LOGPIXELSY) -  GetDeviceCaps (hdcPrn, PHYSICALOFFSETY) ;  
  rect.bottom =GetDeviceCaps (hdcPrn, PHYSICALHEIGHT) -  GetDeviceCaps (hdcPrn, LOGPIXELSY) -GetDeviceCaps (hdcPrn, PHYSICALOFFSETY) ;  
   // Display text on printer  
   SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ;  
   fSuccess = FALSE ;  
   if ((StartDoc (hdcPrn, &di) > 0) && (StartPage (hdcPrn) > 0)) {  // Select font using adjusted lfHeight  
 iSavePointSize = lf.lfHeight ;   lf.lfHeight = -(GetDeviceCaps (hdcPrn, LOGPIXELSY) *  cf.iPointSize) / 720 ;  
 SelectObject (hdcPrn, CreateFontIndirect (&lf)) ; lf.lfHeight = iSavePointSize ;  
  // Set text color  
  SetTextColor (hdcPrn, cf.rgbColors) ; // Display text  
  Justify (hdcPrn, szText, &rect, iAlign) ;  
   if (EndPage (hdcPrn) > 0) {   fSuccess = TRUE ;EndDoc (hdcPrn) ; } }  ShowCursor (FALSE) ;  SetCursor (LoadCursor (NULL, IDC_ARROW)) ;  
DeleteDC (hdcPrn) ;  
if (!fSuccess)   MessageBox (hwnd, TEXT ("Could not print text"),   szAppName, MB_ICONEXCLAMATION | MB_OK) ;  return 0 ;  case   IDM_FONT:   if (ChooseFont (&cf)) InvalidateRect (hwnd, NULL, TRUE) ; return 0 ;   case   IDM_ALIGN_LEFT:  case   IDM_ALIGN_RIGHT:  case   IDM_ALIGN_CENTER:  case   IDM_ALIGN_JUSTIFIED:CheckMenuItem (hMenu, iAlign, MF_UNCHECKED) ; iAlign = LOWORD (wParam) ; CheckMenuItem (hMenu, iAlign, MF_CHECKED) ; InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; }  return 0 ;  
 case   WM_PAINT:  hdc = BeginPaint (hwnd, &ps) ;  GetClientRect (hwnd, &rect) ;  DrawRuler (hdc, &rect) ;  rect.left += GetDeviceCaps (hdc, LOGPIXELSX) / 2 ;  rect.top  += GetDeviceCaps (hdc, LOGPIXELSY) / 2 ;  rect.right= rect.left + OUTWIDTH * GetDeviceCaps (hdc, LOGPIXELSX) ;  
SelectObject (hdc, CreateFontIndirect (&lf)) ;  SetTextColor (hdc, cf.rgbColors) ;  Justify (hdc, szText, &rect, iAlign) ;  DeleteObject (SelectObject (hdc, GetStockObject (SYSTEM_FONT))) ;  EndPaint (hwnd, &ps) ;return 0 ;   case   WM_DESTROY:  PostQuitMessage (0) ;  return 0 ;  
}  return DefWindowProc (hwnd, message, wParam, lParam) ;  
}  
JUSTIFY2.RC  
//Microsoft Developer Studio generated resource script.  
#include "resource.h"  
#include "afxres.h"  
/////////////////////////////////////////////////////////////////////////////  
// Menu  
JUSTIFY2 MENU DISCARDABLE BEGIN POPUP "&File"  
   BEGIN  MENUITEM "&Print",IDM_FILE_PRINT  
   END  
   POPUP "&Font"  
   BEGIN  MENUITEM "&Font...",  IDM_FONT  
   END  
   POPUP "&Align"  
   BEGIN  MENUITEM "&Left",IDM_ALIGN_LEFT, CHECKED  MENUITEM "&Right",IDM_ALIGN_RIGHT  MENUITEM "&Centered", IDM_ALIGN_CENTER MENUITEM "&Justified",IDM_ALIGN_JUSTIFIED  
   END  
END  
RESOURCE.H  
// Microsoft Developer Studio generated include file.  
// Used by Justify2.rc  
#defineIDM_FILE_PRINT  40001  
#defineIDM_FONT40002  
#defineIDM_ALIGN_LEFT  40003  
#defineIDM_ALIGN_RIGHT 40004  
#defineIDM_ALIGN_CENTER40005  
#defineIDM_ALIGN_JUSTIFIED 40006  

JUSTIFY2仅使用TrueType字体。在它的GetCharDesignWidths函数中,程序使用GetOutlineTextMetrics函数取得一个表面上似乎不重要的信息,即OUTLINETEXTMETRIC的otmEMSquare字段。

TrueType字体在全方(em-square)的网格上设计(如我说过「em」是指一种方块型态的宽度,M在宽度上等于字体点值的大小)。任何特定TrueType字体的所有字符都是在同样的网格上设计的,虽然这些字符通常有不同的宽度。OUTLINETEXTMETRIC结构的otmEMSquare字段给出了任意特定字体的这种全方形式的大小。您会发现:对于大多数TrueType字体,otmEMSquare字段等于2048,这意味着字体是在2048×2048的网格上设计的。

关键在于:可以为想要使用的特定TrueType字体名称设定一个LOGFONT结构,其lfHeight字段等于otmEMSquare值的负数。在建立字体并将其选入设备内容后,可呼叫GetCharWidth。该函数以逻辑单位提供字体中单个字符的宽度。通常,因为这些字符被缩放为不同的字体大小,所以字符宽度并不准确。但使用依据otmEMSquare大小的字体,这些宽度总是与任何设备内容无关的精确整数。

GetCharDesignWidths函数以这种方式获得原始的字符设计宽度,并将它们储存在整数数组中。JUSTIFY2程序在自己的文字中仅使用ASCII字符,因此,这个数组不需要很大。GetScaledWidths函数将这些整数型态宽度转变为依据设备逻辑坐标中字体的实际点值的浮点宽度。GetTextExtentFloat函数使用这些浮点宽度计算整个字符串的宽度。这是新的Justify函数用以计算文字行宽度的操作。

有趣的东西

根据外形轮廓表示字体字符提供了将字体与其它图形技术相结合的可能性。前面我们讨论了旋转字体的方式。这里讲述一些其它技巧。继续之前,先了解两个重要的预备知识:绘图路径和扩展画笔。

GDI绘图路径

绘图路径是储存在GDI内的直线和曲线的集合。绘图路径是在Windows的32位版本中发表的。绘图路径看上去类似于区域,我们确实可以将绘图路径转换为区域,并使用绘图路径进行剪裁。但随后我们会发现两者的不同。

要定义绘图路径,可先简单呼叫

BeginPath (hdc) ;  

进行该呼叫之后,所画的任何线(例如,直线、弧及贝塞尔曲线)将作为绘图路径储存在GDI内部,不被显示到设备内容上。绘图路径经常由连结起来的线组成。要制作连结线,应使用LineTo、PolylineTo和BezierTo函数,这些函数都以目前位置为起点划线。如果使用MoveToEx改变了目前位置,或呼叫其它的画线函数,或者呼叫了会导致目前位置改变的窗口/视端口函数,您就在整个绘图路径中建立了一个新的子绘图路径。因此,绘图路径包含一或多个子绘图路径,每一个子绘图路径是一系列连结的线段。

绘图路径中的每个子绘图路径可以是敞开的或封闭的。封闭子绘图路径之第一条连结线的第一个点与最后一条连结线的最后一点相同,并且子绘图路径通过呼叫CloseFigure结束。如果必要的话,CloseFigure将用一条直线封闭子绘图路径。随后的画线函数将开始一个新的子绘图路径。最后,通过下面的呼叫结束绘图路径定义:

EndPath (hdc) ;  

这时,接着呼叫下列五个函数之一:

StrokePath (hdc) ;  
FillPath (hdc) ;  
StrokeAndFillPath (hdc) ;  
hRgn = PathToRegion (hdc) ;  
SelectClipPath (hdc, iCombine) ;  

这些函数中的每一个都会在绘图路径定义完成后,将其清除。

StrokePath使用目前画笔绘制绘图路径。您可能会好奇:绘图路径上的点有哪些?为什么不能跳过这些绘图路径片段正常地画线?稍后我会告诉您原因。

另外四个函数用直线关闭任何敞开的绘图路径。FillPath依照目前的多边填充模式使用目前画刷填充绘图路径。StrokeAndFillPath一次完成这两项工作。也可将绘图路径转换为区域,或者将绘图路径用于某个剪裁区域。iCombine参数是CombineRgn函数使用的RGN_系列常数之一,它指出了绘图路径与目前剪裁区域的结合方式。

用于填充或剪取时,绘图路径比绘图区域更灵活,这是因为绘图区域仅能由矩形、椭圆及多边形的组合定义;绘图路径可由贝塞尔曲线定义,至少在WindowsNT中还可由弧线组成。在GDI中,绘图路径和区域的储存也完全不同。绘图路径是直线及曲线定义的集合;而绘图区域(通常意义上)是扫描线的集合。

扩展画笔

在呼叫StrokePath时,使用目前画笔绘制绘图路径。在第四章讨论了用以建立画笔对象的CreatePen函数。伴随绘图路径的发表,Windows也支持一个称为ExtCreatePen的扩展画笔函数呼叫。该函数揭示了其建立绘图路径以及使用绘图路径要比不使用绘图路径画线有用。ExtCreatePen函数如下所示:

hPen = ExtCreatePen (iStyle, iWidth, &lBrush, 0, NULL) ;  

您可以使用该函数正常地绘制线段,但在这种情况下Windows98不支持一些功能。甚至用以显示绘图路径时,Windows98仍不支持一些功能,这就是上面函数的最后两个参数被设定为0及NULL的原因。

对于ExtCreatePen的第一个参数,可使用第四章中所讨论的用在CreatePen上的所有样式。您可使用PS_GEOMETRIC另外组合这些样式(其中iWidth参数以逻辑单位表示线宽并能够转换),或者使用PS_COSMETIC(其中iWidth参数必须是1)。Windows 98中,虚线或点画线样式的画笔必须是PS_COSMETIC,在Windows NT中取消了这个限制。

CreatePen的一个参数表示颜色;ExtCreatePen的相应参数不只表示颜色,它还使用画刷给PS_GEOMETRIC画笔内部着色。该画刷甚至能透过位图定义。

在绘制宽线段时,我们可能要关注线段端点的外观。在连结直线或曲线时,可能还要关注线段间连结点的外观。画笔由CreatePen建立时,这些端点及连结点通常是圆形的;使用ExtCreatePen建立画笔时我们可以选择。(实际上,在Windows98中,只有在使用画笔实作绘图路径时我们可以选择;在WindowsNT中要更加灵活)。宽线段的端点可以使用ExtCreatePen中的下列画笔样式定义:

PS_ENDCAP_ROUND  
PS_ENDCAP_SQUARE  
PS_ENDCAP_FLAT  

「square」样式与「flat」样式的不同点是:前者将线伸展到一半宽。与端点类似,绘图路径中线段间的连结点可通过如下样式设定:

PS_JOIN_ROUND  
PS_JOIN_BEVEL  
PS_JOIN_MITER  

「bevel」样式将连结点切断;「miter」样式将连结点变为箭头。程序17-9所示的ENDJOIN是对此的一个较好的说明。

程序17-9 ENDJOIN
  
ENDJOIN.C  
/*---------------------------------------------------------------------------  
  ENDJOIN.C --  Ends and Joins Demo(c) Charles Petzold, 1998  
----------------------------------------------------------------------------*/  
#include <windows.h>  
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;  

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,   PSTR szCmdLine, int iCmdShow)  
{   static TCHAR szAppName[] = TEXT ("EndJoin") ;   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= NULL ;   wndclass.lpszClassName   = szAppName ;  
  if (!RegisterClass (&wndclass))  
{  MessageBox (  NULL, TEXT ("This program requires Windows NT!"),szAppName, MB_ICONERROR) ;  return 0 ;  
}  
  hwnd = CreateWindow ( szAppName, TEXT ("Ends and Joins Demo"),  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 iMsg, WPARAM wParam,LPARAM lParam)  
{   static int iEnd[] =   {PS_ENDCAP_ROUND,PS_ENDCAP_SQUARE,PS_ENDCAP_FLAT } ;   static int iJoin[]=   {PS_JOIN_ROUND,PS_JOIN_BEVEL,PS_JOIN_MITER } ;   static intcxClient, cyClient ;   HDC   hdc ;   int   i ;   LOGBRUSH  ib ;   PAINTSTRUCT   ps ;  
  switch (iMsg)   {   case   WM_SIZE:  cxClient = LOWORD (lParam) ;  cyClient = HIWORD (lParam) ;  return 0 ;   case   WM_PAINT:  hdc = BeginPaint (hwnd, &ps) ;  SetMapMode (hdc, MM_ANISOTROPIC) ;  SetWindowExtEx (hdc, 100, 100, NULL) ; SetViewportExtEx (hdc, cxClient, cyClient, NULL) ;  lb.lbStyle = BS_SOLID ;  lb.lbColor = RGB (128, 128, 128) ;  lb.lbHatch = 0 ;  for (i = 0 ; i < 3 ; i++)  { SelectObject (hdc, ExtCreatePen (PS_SOLID | PS_GEOMETRIC | iEnd [i] | iJoin [i], 10, &lb, 0, NULL)) ; BeginPath (hdc) ; MoveToEx (hdc, 10 + 30 * i, 25, NULL) ;   LineTo   (hdc, 20 + 30 * i, 75) ; LineTo   (hdc, 30 + 30 * i, 25) ;  EndPath (hdc) ; StrokePath (hdc) ;  DeleteObject (  SelectObject (hdc,GetStockObject (BLACK_PEN))) ;  
   MoveToEx  (hdc, 10 + 30 * i, 25, NULL) ; LineTo(hdc, 20 + 30 * i, 75) ; LineTo(hdc, 30 + 30 * i, 25) ;  }   EndPaint (hwnd, &ps) ;   return 0 ;   case   WM_DESTROY:  PostQuitMessage (0) ;return 0 ;   }  
  return DefWindowProc (hwnd, iMsg, wParam, lParam) ;  
}  

程序使用上述端点和连结点样式画了三条V形的宽线段。程序也使用备用黑色画笔画了三条同样的线。这样就将宽线与通常的细线做了比较。结果如图17-4所示。

图17-4 ENDJOIN的屏幕显示

现在大家该明白为什么Windows支持StrokePath函数了:如果分别画两条直线,GDI不得不在每一条在线使用端点。只有在绘图路径定义中,GDI知道线段是连结的并使用线段的连结点。

四个范例程序

这究竟有什么好处呢?仔细考虑一下:轮廓字体的字符由一系列坐标值定义,这些坐标定义了直线和转折线。因而,直线及曲线能成为绘图路径定义的一部分。

确实可以!程序17-10所示的FONTOUT1程序对此做了展示。

程序17-10 FONTOUT1
  
FONTOUT1.C  
/*-----------------------------------------------------------------------------  
  FONTOUT1.C -- Using Path to Outline Font(c) Charles Petzold, 1998  
----------------------------------------------------------------------------*/  
#include <windows.h>  
#include "..\\eztest\\ezfont.h"  

TCHAR szAppName [] = TEXT ("FontOut1") ;  
TCHAR szTitle [] = TEXT ("FontOut1: Using Path to Outline Font") ;  

void PaintRoutine (HWND hwnd, HDC hdc, int cxArea, int cyArea)  
{   static TCHAR szString [] = TEXT ("Outline") ;   HFONThFont ;   SIZE size ;  
 hFont = EzCreateFont (hdc, TEXT ("Times New Roman"), 1440, 0, 0, TRUE) ;   SelectObject (hdc, hFont) ;   GetTextExtentPoint32 (hdc, szString, lstrlen (szString), &size) ;  BeginPath (hdc) ;   TextOut (hdc, (cxArea - size.cx) / 2, (cyArea - size.cy) / 2,szString, lstrlen (szString)) ;  EndPath (hdc) ;   StrokePath (hdc) ;   SelectObject (hdc, GetStockObject (SYSTEM_FONT)) ;   DeleteObject (hFont) ;  
}  

此程序和本章后面的程序都使用了前面所示的EZFONT和FONTDEMO文件。

程序建立了144点的TrueType字体并呼叫GetTextExtentPoint32函数取得文字方块的大小。然后,呼叫绘图路径定义中的TextOut函数使文字在显示区域窗口中处于中心的位置。因为对TextOut函数的呼叫是被绘图路径设定命令所包围的(即BeginPath和EndPath呼叫之间)程序中进行的,GDI不立即显示文字。相反,程序将字符轮廓储存在绘图路径定义中。

在绘图路径定义结束后,FONTOUT1呼叫StrokePath。因为设备内容中未选入指定的画笔,所以GDI仅仅使用内定画笔绘制字符轮廓,如图17-5所示。

图17-5 FONTOUT1的屏幕显示

现在我们都得到什么呢?我们已经获得了所期望的轮廓字符,但是字符串外面为什么会围绕着矩形呢?

回想一下,文字背景模式使用内定的OPAQUE,而不是TRANSPARENT。该矩形就是文字方块的轮廓。这清晰地展示了在内定的OPAQUE模式下GDI绘制文字时所使用的两个步骤:首先绘制一个填充的矩形,接着绘制字符。文字方块矩形的轮廓也因此成为绘图路径的一部分。

使用ExtCreatePen函数就能够使用内定画笔以外的东西绘制字体字符的轮廓。程序17-11所示的FONTOUT2对此做了展示。

程序17-11 FONTOUT2
  
FONTOUT2.C  
/*-----------------------------------------------------------------------------  
  FONTOUT2.C -- Using Path to Outline Font(c) Charles Petzold, 1998  
----------------------------------------------------------------------------*/  
#include <windows.h>  
#include "..\\eztest\\ezfont.h"  

TCHAR szAppName [] = TEXT ("FontOut2") ;  
TCHAR szTitle [] = TEXT ("FontOut2: Using Path to Outline Font") ;  

void PaintRoutine (HWND hwnd, HDC hdc, int cxArea, int cyArea)  
{   static TCHAR szString [] = TEXT ("Outline") ;   HFONThFont ;   LOGBRUSH  lb ;   SIZE size ;  
 hFont = EzCreateFont (hdc, TEXT ("Times New Roman"), 1440, 0, 0, TRUE) ;   SelectObject (hdc, hFont) ;   SetBkMode (hdc, TRANSPARENT) ;  
 GetTextExtentPoint32 (hdc, szString, lstrlen (szString), &size) ;   BeginPath (hdc) ;   TextOut (hdc, (cxArea - size.cx) / 2, (cyArea - size.cy) / 2,  szString, lstrlen (szString)) ;   EndPath (hdc) ;   lb.lbStyle=  BS_SOLID ;   lb.lbColor=  RGB (255, 0, 0) ;   lb.lbHatch=  0 ;  
 SelectObject (hdc, ExtCreatePen (PS_GEOMETRIC | PS_DOT, GetDeviceCaps (hdc, LOGPIXELSX) / 24, &lb, 0, NULL)) ;   StrokePath (hdc) ;   DeleteObject (SelectObject (hdc, GetStockObject (BLACK_PEN))) ;   SelectObject (hdc, GetStockObject (SYSTEM_FONT)) ;   DeleteObject (hFont) ;  
}  

此程序呼叫StrokePath之前建立(并选入设备内容)一个3点(1/24英寸)宽的红色点线笔。程序在WindowsNT下执行时,结果如图17-6所示。Windows98不支持超过1图素宽的非实心笔,因此Windows98将以实心的红色笔绘制。

图17-6 FONTOUT2的屏幕显示

您也可以使用绘图路径定义填充区域。请用前面两个程序所示的方法建立绘图路径,选择一种填充图案,然后呼叫FillPath。能呼叫的另一个函数是StrokeAndFillPath,它绘制绘图路径的轮廓并用一个函数呼叫将其填充。

StrokeAndFillPath函数如程序17-12 FONTFILL所展示。

程序17-12 FONTFILL
  
FONTFILL.C  
/*----------------------------------------------------------------------------  
  FONTFILL.C -- Using Path to Fill Font(c) Charles Petzold, 1998  
-----------------------------------------------------------------------------*/  
#include <windows.h>  
#include "..\\eztest\\ezfont.h"  

TCHAR szAppName [] = TEXT ("FontFill") ;  
TCHAR szTitle [] = TEXT ("FontFill: Using Path to Fill Font") ;  
void PaintRoutine (HWND hwnd, HDC hdc, int cxArea, int cyArea)  
{   static TCHAR szString [] = TEXT ("Filling") ;   HFONThFont ;   SIZE size ;  
 hFont = EzCreateFont (hdc, TEXT ("Times New Roman"), 1440, 0, 0, TRUE) ;   SelectObject (hdc, hFont) ;   SetBkMode (hdc, TRANSPARENT) ;  
 GetTextExtentPoint32 (hdc, szString, lstrlen (szString), &size) ;   BeginPath (hdc) ;   TextOut (hdc, (cxArea - size.cx) / 2, (cyArea - size.cy) / 2,   szString, lstrlen (szString)) ;   EndPath (hdc) ;   SelectObject (hdc, CreateHatchBrush (HS_DIAGCROSS, RGB (255, 0, 0))) ;   SetBkColor (hdc, RGB (0, 0, 255)) ;   SetBkMode (hdc, OPAQUE) ;  
 StrokeAndFillPath (hdc) ;   DeleteObject (SelectObject (hdc, GetStockObject (WHITE_BRUSH))) ;   SelectObject (hdc, GetStockObject (SYSTEM_FONT)) ;   DeleteObject (hFont) ;  
}  

FONTFILL使用内定画笔绘制绘图路径的轮廓,但使用HS_DIAGCROSS样式建立红色的阴影画刷。注意程序在建立绘图路径时将背景模式设定为TRANSPARENT,在填充绘图路径时又将其重设为OPAQUE,这样它能够为区域图案使用蓝色的背景颜色。结果如图17-7所示。

您可能想在本程序中尝试几个变更,观察变更的影响。首先,如果您将第一个SetBkMode呼叫变为注解,将得到由图案而不是字符本身所覆盖的文字方块背景。这通常不是我们实际所需要的,但确实可这样做。

此外,填充字符及将它们用做剪裁时,您可能想有效地放弃内定的ALTERNATE多边填充模式。我的经验表示:如果使用WINDING填充模式,则构建TrueType字体以避免出现奇怪的现象(例如「O」的内部被填充),但使用ALTERNATE模式更安全。

图17-7 FONTFILL的屏幕显示

最后,可使用一个绘图路径,因此也是一个TrueType字体,来定义剪裁区域。如程序17-13FONTCLIP所示。

程序17-13 FONTCLIP
  
FONTCLIP.C  
/*---------------------------------------------------------------------------  
  FONTCLIP.C -- Using Path for Clipping on Font(c) Charles Petzold, 1998  
----------------------------------------------------------------------------*/  
#include <windows.h>  
#include "..\\eztest\\ezfont.h"  

TCHAR szAppName [] = TEXT ("FontClip") ;  
TCHAR szTitle  [] = TEXT ("FontClip: Using Path for Clipping on Font") ;  

void PaintRoutine (HWND hwnd, HDC hdc, int cxArea, int cyArea)  
{   static TCHAR szString [] = TEXT ("Clipping") ;   HFONThFont ;   int  y, iOffset ;  
POINT pt [4] ;   SIZE  size ;  
 hFont = EzCreateFont (hdc, TEXT ("Times New Roman"), 1200, 0, 0, TRUE) ;   SelectObject (hdc, hFont) ;  
GetTextExtentPoint32 (hdc, szString, lstrlen (szString), &size) ;   BeginPath (hdc) ;   TextOut (hdc, (cxArea - size.cx) / 2, (cyArea - size.cy) / 2, szString, lstrlen (szString)) ;   EndPath (hdc) ; // Set clipping area   SelectClipPath (hdc, RGN_COPY) ; // Draw Bezier splines  
   iOffset = (cxArea + cyArea) / 4 ;   for (y = -iOffset ; y < cyArea + iOffset ; y++)   {  pt[0].x = 0 ;  pt[0].y = y ;  
pt[1].x = cxArea / 3 ;  pt[1].y = y + iOffset ;  
   pt[2].x = 2 * cxArea / 3 ;  pt[2].y = y - iOffset ;  
pt[3].x = cxArea ;  pt[3].y = y ;  
SelectObject (hdc, CreatePen (PS_SOLID, 1, RGB (rand () % 256, rand () % 256, rand () % 256))) ; PolyBezier (hdc, pt, 4) ;  DeleteObject (SelectObject (hdc, GetStockObject (BLACK_PEN))) ;  
}  

DeleteObject (SelectObject (hdc, GetStockObject (WHITE_BRUSH))) ;  
   SelectObject (hdc, GetStockObject (SYSTEM_FONT)) ;  
DeleteObject (hFont) ;  
}  

程序中故意不使用SetBkMode呼叫以实作不同的效果。程序在绘图路径支架中绘制一些文字,然后呼叫SelectClipPath。接着使用随机颜色绘制一系列贝塞尔曲线。

如果FONTCLIP程序使用TRANSPARENT选项呼叫SetBkMode,贝塞尔曲线将被限制在字符轮廓的内部。在内定OPAQUE选项的背景模式下,剪裁区域被限制在文字方块内部而不是文字内部。如图17-8所示。

图17-8 FONTCLIP得屏幕显示

您或许会想在FONTCLIP中插入SetBkMode呼叫来观察TRANSPARENT选项的变化。

FONTDEMO外壳程序允许您打印并显示这些效果,甚至允许您尝试自己的一些特殊效果。