WINDOWS程式设计--文字和字体

文字和字体

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

Windows 3.1发表的TrueType使程式写作者和使用者以灵活的方式处理文字的能力大幅增强。TrueType是轮廓字体技术,由Apple Computer公司和Microsoft公司开发,并被许多字体制造商支援。由於TrueType字体能够连续缩放,并能应用於视讯显示器和印表机,现在能够在Windows下实作真的WYSIWYG(what you see is what you get:所见即所得)。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会将文字解释为一系列由carriage return字元(‘/r’或0x0D)或linefeed字元(‘/n’或0x0A)分隔的行。文字从矩形的左上角开始,carriage return字元或linefeed字元被解释为换行字元,因此Windows会结束目前行而开始新的一行。新的一行从矩形的左侧开始,在上一行的下面空开一个字元的高度(没有外部间隔)。包含字母的任何文字都应该显示在所剪裁矩形底部的右边或下边。

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

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

在显示多行文字时,Windows通常只在carriage return字元或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
    	int  	iTabLength ;     		// size of each tab stop
    	int  	iLeftMargin ;    		// left margin
    	int  	iRightMargin ;   		// 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)) ;

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

case 	WM_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(我在本书的前面几个程式中应用过),可以获得等宽字体的代号,这一字体与Windows 3.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」指字体字母笔划在结束时拐个小弯。「sans serif」字体不是serif类的字体。在Windows的早期版本中,MS(Microsoft)Serif和MS Sans Serif字体被称为Tms Rmn(指它与Times Roman相似)和Helv(与Helvetica相似)。Small Fonts是专为显示小字设计的。

在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版本中,这个列表更长了。在此特别指出,我将使用Lucida Sans Unicode字体,它包括了一些在世界其他地方使用的字母表。

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

属性或样式
 

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

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

点值
 

在传统的版式中,您可以用字体名称和大小来指定字体,字体的大小以点的单位来表示。一点与1/72英寸很接近──它们非常接近,因此在电脑中它通常定义为1/72英寸。点值通常描述为字母顶端(不包括发音符号)到字母底端的高度,例如,字母「bq」的总高度。这是一个考虑字体大小的简单方式,但它通常不是很精确。

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

间隔和间距
 

在第四章我们曾提到,可以通过呼叫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图素。这样,字体的选择就暗指以每英寸的点数为单位的设备解析度,选择「小字体」即为96dpi,选择「大字体」即为120dpi。

您可以用LOGPIXELSX或LOGPIXELSY参数呼叫GetDeviceCaps来取得该设备解析度。因此,96或120图素在萤幕上占有的度量距离可以称为「逻辑英寸」。如果您用尺测量萤幕并计算图素,就可能发现逻辑英寸要比实际的英寸大一些,为什么会这样呢?

在纸张上,每英寸放设14个8点的字元很方便阅读。如果您在作文书处理或写作应用程式时,可能希望在显示器上显示清晰的8点字型,但如果使用视讯显示器的实际尺寸,就没有足够的图素清晰地显示字元。即使显示器具有足够的解析度,在萤幕上阅读8点字体仍然会有问题。当人们阅读纸上的印刷物时,眼睛与文字的距离通常为一英尺,而使用视讯显示器时,这个距离通常为两英尺。

逻辑英寸有效地对萤幕进行了放大,能够显示小至8点的清晰字体。而且,每英寸96点使640图素的最小显示大小等於大约6.5英寸。这恰恰是在页边距为1英寸的8.5英寸宽的纸上列印的文字的宽度。因而,逻辑英寸也利用了萤幕宽度,尽可能大地显示文字。

您可能还记得在第五章,Windows NT的做法有些不同。在Windows NT中,从GetDeviceCaps中得到的LOGPIXELSX(每英寸的图素数)值不等於HORZRES值(图素数)除以HORZSIZE值(毫米数)再乘以25.4的值。以此类似,LOGPIXELSY、VERTRES和VERTSIZE也不一致。Windows在为不同映射方式计算视窗和偏移范围时,使用HORZRES、HORZSIZE、VERTRES和VERTSIZE值。然而,显示文字的程式最好不要使用根据LOGPIXELSX和LOGPIXELSY使用假定的显示解析度,这一点与Windows 98更为一致。

所以,在Windows NT下,当程式以特定的点值显示文字时,它可能不使用Windows提供的映射方式,程式根据与Windows 98一样的每英寸的逻辑图素数来定义自己的映射方式。我将这种用於文字的映射方式称为「Logical Twips」映射方式。您可以设定如下:

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 ;
     	TEXTMETRIC 		tm ;
     	TCHAR      				szFaceName [LF_FULLFACESIZE] ;
}
DLGPARAMS ;
     	// Formatting for BCHAR fields of TEXTMETRIC structure
#ifdef UNICODE
#define BCHARFORM TEXT ("0x%04X")
#else
#define BCHARFORM TEXT ("0x%02X")
#endif

     	// Global variables
HWND  hdlg ;
TCHAR szAppName[] = TEXT ("PickFont") ;

     	// Forward declarations of functions
LRESULT	CALLBACK WndProc 		(HWND, UINT, WPARAM, LPARAM) ;
BOOL  		CALLBACK DlgProc 	(HWND, UINT, WPARAM, LPARAM) ;
void SetLogFontFromFields    	(HWND hdlg, DLGPARAMS * pdp) ;
void SetFieldsFromTextMetric 	(HWND hdlg, DLGPARAMS * pdp) ;
void MySetMapMode	(HDC hdc, int iMapMode) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    							PSTR szCmdLine, int iCmdShow)
{
     	HWND     				hwnd ;
     	MSG      				msg ;
     	WNDCLASS 			wndclass ;
     
     	wndclass.style         		    = CS_HREDRAW | CS_VREDRAW ;
     	wndclass.lpfnWndProc   			= WndProc ;
     	wndclass.cbClsExtra    			= 0 ;
     	wndclass.cbWndExtra    			= 0 ;
     	wndclass.hInstance     			= hInstance ;
     	wndclass.hIcon         			= LoadIcon (NULL, IDI_APPLICATION) ;
     	wndclass.hCursor       			= LoadCursor (NULL, IDC_ARROW) ;
     	wndclass.hbrBackground 		= (HBRUSH) GetStockObject (WHITE_BRUSH) ;
     	wndclass.lpszMenuName  		= szAppName ; 
     	wndclass.lpszClassName 		= szAppName ;
     
     	if (!RegisterClass (&wndclass))
     	{
          			MessageBox (	NULL, TEXT ("This program requires Windows NT!"),
               								szAppName, MB_ICONERROR) ;
      			return 0 ;
     }
     
     	hwnd = CreateWindow (	szAppName, TEXT ("PickFont: Create Logical Font"),
                          		WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
                          		CW_USEDEFAULT, CW_USEDEFAULT,
                          		CW_USEDEFAULT, CW_USEDEFAULT,
                          		NULL, NULL, hInstance, NULL) ;
     
     	ShowWindow (hwnd, iCmdShow) ;
     	UpdateWindow (hwnd) ;
     
     	while (GetMessage (&msg, NULL, 0, 0))
	{
          		if (hdlg == 0 || !IsDialogMessage (hdlg, &msg))
          		{
               				TranslateMessage (&msg) ;
               				DispatchMessage (&msg) ;
          		}
     	}
     	return msg.wParam ;
}

LRESULT CALLBACK WndProc (	HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
{
     	static DLGPARAMS dp ;
     	static TCHAR     		szText[] = 	TEXT ("/x41/x42/x43/x44/x45 ")
                        TEXT ("/x61/x62/x63/x64/x65 ")

                        TEXT ("/xC0/xC1/xC2/xC3/xC4/xC5 ")
                        TEXT ("/xE0/xE1/xE2/xE3/xE4/xE5 ") 
#ifdef UNICODE
                        TEXT ("/x0390/x0391/x0392/x0393/x0394/x0395 ")
                        TEXT ("/x03B0/x03B1/x03B2/x03B3/x03B4/x03B5 ")
                        TEXT ("/x0410/x0411/x0412/x0413/x0414/x0415 ")
                        TEXT ("/x0430/x0431/x0432/x0433/x0434/x0435 ")
                        TEXT ("/x5000/x5001/x5002/x5003/x5004") 
#endif
                                 ;
     	HDC              						hdc ;
     	PAINTSTRUCT      				ps ;
     	RECT             						rect ;
     
     	switch (message)
     	{
     	case WM_CREATE:
          			dp.iDevice = IDM_DEVICE_SCREEN ;
          			hdlg = CreateDialogParam (((LPCREATESTRUCT) lParam)->hInstance, 
                szAppName, hwnd, DlgProc, (LPARAM) &dp) ;
          			return 0 ;
     	case 	WM_SETFOCUS:
          			SetFocus (hdlg) ;
          			return 0 ;

     	case 	WM_COMMAND:
			switch (LOWORD (wParam))
          			{
          			case 	IDM_DEVICE_SCREEN:
          			case 	IDM_DEVICE_PRINTER:
               			CheckMenuItem (GetMenu (hwnd), dp.iDevice, MF_UNCHECKED) ;
               			dp.iDevice = LOWORD (wParam) ;
               			CheckMenuItem (GetMenu (hwnd), dp.iDevice, MF_CHECKED) ;
               			SendMessage (hwnd, WM_COMMAND, IDOK, 0) ;
               			return 0 ;
          			}
          			break ;

     	case 	WM_PAINT:
          			hdc = BeginPaint (hwnd, &ps) ;

               				// Set graphics mode so escapement works in Windows NT

          SetGraphicsMode (hdc, dp.fAdvGraphics ? GM_ADVANCED : GM_COMPATIBLE) ;

               				// Set the mapping mode and the mapper flag

          			MySetMapMode (hdc, dp.iMapMode) ;
          			SetMapperFlags (hdc, dp.fMatchAspect) ;

               				// Find the point to begin drawing text

          			GetClientRect (hdlg, &rect) ;
          			rect.bottom += 1 ;
          			DPtoLP (hdc, (PPOINT) &rect, 2) ;

               				// Create and select the font; display the text

          		SelectObject (hdc, CreateFontIndirect (&dp.lf)) ;
          		TextOut (hdc, rect.left, rect.bottom, szText, lstrlen (szText)) ;

          		DeleteObject (SelectObject (hdc, GetStockObject (SYSTEM_FONT))) ;
          		EndPaint (hwnd, &ps) ;
          			return 0 ;
          
     	case 	WM_DESTROY:
          			PostQuitMessage (0) ;
          			return 0 ;
     	}
     	return DefWindowProc (hwnd, message, wParam, lParam) ;
}

BOOL CALLBACK DlgProc (	HWND hdlg, UINT message, WPARAM wParam,LPARAM lParam)
{
     	static DLGPARAMS 		* 	pdp ;
     	static PRINTDLG    			pd = { sizeof (PRINTDLG) } ;
     	HDC                							hdcDevice ;
     	HFONT        							hFont ;
     
	switch (message)
     	{
     	case 	WM_INITDIALOG:
               		// Save pointer to dialog-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) ;

     	SetDlgItem	Text (hdlg, IDC_TM_VARIABLE, 
               					TMPF_FIXED_PITCH & pdp->tm.tmPitchAndFamily ? szYes : szNo) ;

     	SetDlgItem	Text (hdlg, IDC_TM_VECTOR, 
               					TMPF_VECTOR & pdp->tm.tmPitchAndFamily ? szYes : szNo) ;

     	SetDlgItem	Text (hdlg, IDC_TM_TRUETYPE, 
               					TMPF_TRUETYPE & pdp->tm.tmPitchAndFamily ? szYes : szNo) ;

     	SetDlgItem	Text (hdlg, IDC_TM_DEVICE, 
               					TMPF_DEVICE & pdp->tm.tmPitchAndFamily ? szYes : szNo) ;

     	SetDlgItem	Text (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

#define 		IDC_LF_HEIGHT      1000
#define 		IDC_LF_WIDTH       1001
#define 		IDC_LF_ESCAPE      1002
#define 		IDC_LF_ORIENT      1003
#define 		IDC_LF_WEIGHT      1004
#define 		IDC_MM_TEXT        1005
#define 		IDC_MM_LOMETRIC    1006
#define 		IDC_MM_HIMETRIC    1007
#define 		IDC_MM_LOENGLISH   1008
#define 		IDC_MM_HIENGLISH   1009
#define 		IDC_MM_TWIPS       1010
#define 		IDC_MM_LOGTWIPS    1011
#define 		IDC_LF_ITALIC      1012
#define 		IDC_LF_UNDER       1013
#define 		IDC_LF_STRIKE      1014
#define 		IDC_MATCH_ASPECT   1015
#define 		IDC_ADV_GRAPHICS   1016
#define 		IDC_LF_CHARSET     1017
#define 		IDC_CHARSET_HELP   1018
#define 		IDC_DEFAULT_QUALITY     1019
#define 		IDC_DRAFT_QUALITY       1020
#define 		IDC_PROOF_QUALITY       1021
#define 		IDC_LF_FACENAME         1022
#define 		IDC_OUT_DEFAULT         1023
#define 		IDC_OUT_STRING          1024
#define 		IDC_OUT_CHARACTER       1025
#define 		IDC_OUT_STROKE          1026
#define 		IDC_OUT_TT              1027
#define 		IDC_OUT_DEVICE          1028
#define 		IDC_OUT_RASTER          1029
#define 		IDC_OUT_TT_ONLY         1030
#define 		IDC_OUT_OUTLINE         1031
#define 		IDC_DEFAULT_PITCH       1032
#define 		IDC_FIXED_PITCH         1033
#define 		IDC_VARIABLE_PITCH      1034
#define 		IDC_FF_DONTCARE         1035
#define 		IDC_FF_ROMAN            1036
#define 		IDC_FF_SWISS            1037
#define 		IDC_FF_MODERN           1038
#define 		IDC_FF_SCRIPT           1039
#define 		IDC_FF_DECORATIVE       1040
#define 		IDC_TM_HEIGHT           1041
#define 		IDC_TM_ASCENT           1042
#define 		IDC_TM_DESCENT          1043
#define 		IDC_TM_INTLEAD          1044
#define 		IDC_TM_EXTLEAD          1045
#define 		IDC_TM_AVECHAR          1046
#define 		IDC_TM_MAXCHAR          1047
#define 		IDC_TM_WEIGHT           1048
#define 		IDC_TM_OVERHANG         1049
#define 		IDC_TM_DIGASPX          1050
#define 		IDC_TM_DIGASPY          1051
#define 		IDC_TM_FIRSTCHAR        1052
#define 		IDC_TM_LASTCHAR         1053
#define 		IDC_TM_DEFCHAR          1054
#define 		IDC_TM_BREAKCHAR        1055
#define 		IDC_TM_ITALIC           1056
#define 		IDC_TM_UNDER            1057
#define 		IDC_TM_STRUCK           1058
#define 		IDC_TM_VARIABLE         1059
#define 		IDC_TM_VECTOR           1060
#define 		IDC_TM_TRUETYPE         1061
#define 		IDC_TM_DEVICE           1062
#define 		IDC_TM_FAMILY           1063
#define 		IDC_TM_CHARSET          1064
#define 		IDC_TM_FACENAME         1065
#define 		IDM_DEVICE_SCREEN       40001
#define 		IDM_DEVICE_PRINTER      40002

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


 

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

非模态对话方块还包含一些非逻辑字体结构的选项,它们是包括「Logical Twips」方式的映射方式、「Match Aspect」选项(更改Windows将逻辑字体与真实字体匹配的方式)和「Adv Grtx Mode」(设定Windows NT中的高级图形模式)。稍後我将对这些作详细讨论。

从「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字体、执行Windows NT以及首先用CM_ADVANCED旗标设定呼叫SetGraphicsMode。通过选中「Adv Grfx Mode」核取方块,您能够完成PICKFONT中的最终需要。

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

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

    在Windows 98中,这个值设定了TrueType文字的移位角度和方向。在Windows NT中,这个值通常也是这样设定,除了用GM_ADVANCED参数呼叫SetGraphicsMode时,它按文件中说明的那样工作。

    表17-2
    字元外观
    0正常(内定)
    900向右倾斜90度
    1800颠倒
    2700向左倾斜90度

    这个栏位一般不起作用,除非在Windows NT下使用TrueType字体,并把图像模式设定为GM_ADVANCED, 在这种情况下它按文件中说明的那样工作。

    其余10个栏位如下:

    表17-3
    识别字
    0FW_DONTCARE
    100FW_THIN
    200FW_EXTRALIGHT或FW_ULTRALIGHT
    300FW_LIGHT
    400FW_NORMAL或FW_REGULAR
    500FW_MEDIUM
    600FW_SEMIBOLD或FW_DEMIBOLD
    700FW_BOLD
    800FW_EXTRABOLD或FW_ULTRABOLD
    900FW_HEAVY或FW_BLACK

    事实上,它比以前用过的任何一组值都完善。您可以对标准字使用0或400,对粗体使用700。

    注意lfCharSet栏位是唯一不用零表示预设值的栏位。零值相当於ANSI_CHARSET,ANSI字元在美国和西欧使用。DEFAULT_CHARSET代码等於1,表示程式执行的机器上内定的字元集。

    表17-4
    识别字
    0DEFAULT_PITCH
    1FIXED_PITCH
    2VARIABLE_PITCH

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

    表17-5
    识别字
    0x00FW_DONTCARE
    0x10FF_ROMAN(变宽,serifs)
    0x20FF_SWISS(变宽,非serifs)
    0x30FF_MODERN(定宽)
    0x40FF_SCRIPT(模仿手写)
    0x50FF_DECORATIVE
  •  lfOrientation 这是从水平方向逆时针测量的十分之几的角度,它影响单个字元的外观。表17-2提供了几个例子:
     
  •  lfWeight 这个栏位使您能够指定粗体。WINGDI.H表头档案定义了可用於这个栏位的一组值(参见表17-3)。
     
  •  lfItalic 在非零值时,它指定斜体。Windows能在GDI点阵字体上合成斜体。亦即,Windows仅仅移动若干行字元点阵图来模仿斜体。对於TrueType字体,Windows使用真正的斜体或字体的倾斜版本。
     
  •  lfUnderline 在非零值时,它指定底线,这项属性在GDI字体上都是用合成的。也就是说,Windows GDI只是在包括空格的每个字元底线。
     
  •  lfStrikeOut 在非零值时,它指定字体上应该有一条线穿过。这也是由GDI字体合成的。
     
  •  lfCharSet 这是指定字体字元集的一个位元组的值。我会在下一节「字元集和Unicode」中更详细地讨论这个栏位。在PICKFONT中,您可以按下带有问号的按钮来取得能够使用的字元集列表。
     
  •  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)。
     
  •  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时是粗体。
     
  •  tmOverhang Windows在合成斜体或粗体时添加到点阵字体字元的额外宽度量(逻辑单位)。当点阵字体斜体化时,tmAveCharWidth值保持不变,因为斜体化的字串与相同的正常字串的总宽度相等。要为字体加粗,Windows必须稍微增加每个字元的宽度。对於粗体,tmAveCharWidth值小於tmOverhang值,等於没有加粗的相同字体的tmAveCharWidth值。
     
  •  tmDigitizedAspectX和tmDigitizedAspectY 字体合适的纵横比。它们与使用LOGPIXELSX和LOGPIXELSY识别字从GetDeviceCaps得到的值相同。
     
  •  tmFirstChar 字体中第一个字元的字元代码。
     
  •  tmLastChar 字体中最後一个字元的字元代码。如果TEXTMETRIC结构通过呼叫GetTextMetricsW(函式的宽字元版本)获得,那么这个值可能大於255。
     
  •  tmDefaultChar Windows用於显示不在字体中的字元的字元代码,通常是矩形。
     
  •  tmBreakChar 在调整文字时,Windows和您的程式用於确定单字断开的字元。如果您不用一些奇怪的东西(例如EBCDIC字体),它就是32-空白字元。
     
  •  tmItalic 对於斜体字为非零值。
     
  •  tmUnderlined 对於底线字体为非零值。
     
  •  tmStruckOut 对於删除线字体为非零值。
     
  •  tmPitchAndFamily 低四位元是表示字体某些特徵的旗标,由在WINGDI.H中定义的识别字指出(参见表17-6)。
     
    表17-6
    识别字
    0x01TMPF_FIXED_PITCH
    0x02TMPF_VECTOR
    0x04TMPF_TRUETYPE
    0x08TMPF_DEVICE

    不管TMPF_FIXED_PITCH旗标的名称是什么,如果字体字元是变宽的,则最低位元为1。第二最低位元(TMPF_VECTOR)对於TrueType字体和使用其他可缩放的轮廓技术的字体(如PostScript的字体)为1。TMPF_DEVICE旗标表示设备字体(即印表机内置的字体),而不是依据GDI的字体。

    这个栏位的第四高的位元表示字体系列,并且与LOGFONT的lfPitchAndFamily栏位中所用的值相同。

  •  tmCharSet 字元集识别字。
     

字元集和Unicode
 

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

#define 		ANSI_CHARSET            			0
#define 		DEFAULT_CHARSET         			1
#define 		SYMBOL_CHARSET          			2
#define 		MAC_CHARSET             			77
#define 		SHIFTJIS_CHARSET        			128
#define 		HANGEUL_CHARSET         			129
#define 		HANGUL_CHARSET          			129	
#define 		JOHAB_CHARSET           			130
#define 		GB2312_CHARSET          			134
#define 		CHINESEBIG5_CHARSET     			136
#define 		GREEK_CHARSET           			161
#define 		TURKISH_CHARSET         			162
#define 		VIETNAMESE_CHARSET      			163
#define 		HEBREW_CHARSET          			177
#define 		ARABIC_CHARSET          			178
#define 		BALTIC_CHARSET          			186
#define 		RUSSIAN_CHARSET         			204
#define 		THAI_CHARSET            			222
#define 		EASTEUROPE_CHARSET      			238
#define 		OEM_CHARSET             			255

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

与本书的所有程式一样,您可以带有定义的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字体是「Big fonts」并且包含几种不同字元集的字母。如果您执行Windows的远东版本,这些字元会被解释为双位元组字元,并且会按方块字显示,而不是按字母显示。

在Windows NT下执行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通常的Big Fonts中,因此作业系统选择Lucida Sans Unicode,这一点您可以在非模态对话方块的右下角中验证。

PICKFONT也显示0x5000到0x5004的字元代码,它们对应於汉语、日语和朝鲜语象形文字的一部分。如果您执行Windows的远东版本,或者下载了比Lucida Sans Unicode范围更广的免费Unicode字体,就可以看到这些。Bitstream CyberBit字体就是这样的一种字体,您可以从 

本章的後面有一个程式可让您查看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) ;

#define 		EZ_ATTR_BOLD          						1
#define 		EZ_ATTR_ITALIC        						2
#define 		EZ_ATTR_UNDERLINE     						4
#define 		EZ_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)
{
     	FLOAT      				cxDpi, cyDpi ;
     	HFONT      				hFont ;
     	LOGFONT    			lf ;
     	POINT      				pt ;
     	TEXTMETRIC 		tm ;
     
  	SaveDC (hdc) ;
	SetGraphicsMode (hdc, GM_ADVANCED) ;
     	ModifyWorldTransform 		(hdc, NULL, MWT_IDENTITY) ;
     	SetViewportOrgEx 			(hdc, 0, 0, NULL) ;
     	SetWindowOrgEx   				(hdc, 0, 0, NULL) ;
     
     	if (fLogRes)
     	{
          		cxDpi = (FLOAT) GetDeviceCaps (hdc, LOGPIXELSX) ;
          		cyDpi = (FLOAT) GetDeviceCaps (hdc, LOGPIXELSY) ;
     	}
     	else
     	{
          		cxDpi = (FLOAT) (25.4 * GetDeviceCaps (hdc, HORZRES) /
                                GetDeviceCaps (hdc, HORZSIZE)) ;
          		cyDpi = (FLOAT) (25.4 * GetDeviceCaps (hdc, VERTRES) /
                                GetDeviceCaps (hdc, VERTSIZE)) ;
	}
     
     		pt.x = (int) (iDeciPtWidth	* cxDpi / 72) ;
     		pt.y = (int) (iDeciPtHeight	* cyDpi / 72) ;
     
     		DPtoLP (hdc, &pt, 1) ;
     		lf.lfHeight         			= - (int) (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计算出来的。这仅对於Windows NT下的视讯显示器才有所不同。

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

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)
{
     	HFONT      				hFont ;
     	int        				y, iPointSize ;
     	LOGFONT    			lf ;
     	TCHAR      				szBuffer [100] ;
     	TEXTMETRIC 		tm ;

          		// Set Logical Twips mapping mode

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

        		// Try some fonts

     	y = 0 ;
     	for (iPointSize = 80 ; iPointSize <= 120 ; iPointSize++)
     	{
          		hFont = EzCreateFont (	hdc, TEXT ("Times New Roman"), 
                                		iPointSize, 0, 0, TRUE) ;

          		GetObject (hFont, sizeof (LOGFONT), &lf) ;

          		SelectObject (hdc, hFont) ;
          		GetTextMetrics (hdc, &tm) ;
          		TextOut (hdc, 0, y, szBuffer, 
               			wsprintf (	szBuffer, 
                TEXT ("Times New Roman font of %i.%i points, ")
                TEXT ("lf.lfHeight = %i, tm.tmHeight = %i"),
                 iPointSize / 10, iPointSize % 10,
                 lf.lfHeight, tm.tmHeight)) ;

          		DeleteObject (SelectObject (hdc, GetStockObject (SYSTEM_FONT))) ;
          		y += tm.tmHeight ;
     	}
}
 FONTDEMO.C
/*---------------------------------------------------------------------------
   	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)
{
     	TCHAR    				szResource [] = TEXT ("FontDemo") ;
     	HWND     				hwnd ;
     	MSG      				msg ;
     	WNDCLASS 			wndclass ;
     
     	hInst = hInstance ;
     	wndclass.style         			= CS_HREDRAW | CS_VREDRAW ;
     	wndclass.lpfnWndProc   			= WndProc ;
     	wndclass.cbClsExtra    			= 0 ;
     	wndclass.cbWndExtra    			= 0 ;
     	wndclass.hInstance     			= hInstance ;
     	wndclass.hIcon         			= LoadIcon (NULL, IDI_APPLICATION) ;
     	wndclass.hCursor       			= LoadCursor (NULL, IDC_ARROW) ;
     	wndclass.hbrBackground 		= (HBRUSH) GetStockObject (WHITE_BRUSH) ;
     	wndclass.lpszMenuName  		= szResource ;
     	wndclass.lpszClassName 		= szAppName ;
     
     	if (!RegisterClass (&wndclass))
     	{
          			MessageBox (	NULL, TEXT ("This program requires Windows NT!"),
                      								szAppName, MB_ICONERROR) ;
          			return 0 ;
     	}
     
     	hwnd = CreateWindow (	szAppName, szTitle,
                       WS_OVERLAPPEDWINDOW,
                       CW_USEDEFAULT, CW_USEDEFAULT,
                       CW_USEDEFAULT, CW_USEDEFAULT,
                       NULL, NULL, hInstance, NULL) ;
     
     	ShowWindow (hwnd, iCmdShow) ;
     	UpdateWindow (hwnd) ;
     
     	while (GetMessage (&msg, NULL, 0, 0))
     	{
          		TranslateMessage (&msg) ;
          		DispatchMessage (&msg) ;
     	}
	return msg.wParam ;
}

LRESULT CALLBACK WndProc (	HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
{
     	static DOCINFO  		di = { sizeof (DOCINFO), TEXT ("Font Demo: Printing") } ;
     	static int      			cxClient, cyClient ;
     	static PRINTDLG 	pd 	= { sizeof (PRINTDLG) } ;
     	BOOL            					fSuccess ;
     	HDC             					hdc, hdcPrn ;
     	int             					cxPage, cyPage ;
     	PAINTSTRUCT    			ps ;
     
     	switch (message)
     	{
     	case 	WM_COMMAND:
          			switch (wParam)
          		{
          			case 	IDM_PRINT:

                    							// Get printer DC

               					pd.hwndOwner 	= hwnd ;
               					pd.Flags     	= PD_RETURNDC | PD_NOPAGENUMS | PD_NOSELECTION ;

               					if (!	PrintDlg (&pd))
                    							return 0 ;

               					if (NULL == (hdcPrn = pd.hDC))
               					{
                    MessageBox(	hwnd, TEXT ("Cannot obtain Printer DC"),
                                szAppName, MB_ICONEXCLAMATION | MB_OK) ;
                    						return 0 ;
               					}
                    						// Get size of printable area of page

               					cxPage = GetDeviceCaps (hdcPrn, HORZRES) ;
               					cyPage = GetDeviceCaps (hdcPrn, VERTRES) ;

               					fSuccess = FALSE ;

                    						// Do the printer page

               					SetCursor (LoadCursor (NULL, IDC_WAIT)) ;
               					ShowCursor (TRUE) ;

               					if ((StartDoc (hdcPrn, &di) > 0) && (StartPage (hdcPrn) > 0))
               				{
                    		PaintRoutine (hwnd, hdcPrn, cxPage, cyPage) ;
                    
                    					if (EndPage (hdcPrn) > 0)
                    					{
                         							fSuccess = TRUE ;
                         							EndDoc (hdcPrn) ;
                    					}
               				}
               				DeleteDC (hdcPrn) ;

               				ShowCursor (FALSE) ;
               				SetCursor (LoadCursor (NULL, IDC_ARROW)) ;

               				if (!fSuccess)
                    						MessageBox (hwnd, 
             TEXT ("Error encountered during printing"),
             szAppName, MB_ICONEXCLAMATION | MB_OK) ;
               				return 0 ;

          		case 	IDM_ABOUT:
               				MessageBox (	hwnd, 	TEXT ("Font 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函式将映射方式设定为Logical Twips,然後建立字体范围从8点到12点(间隔为0.1点)的Times New Roman字体。第一次执行此程式时,它的输出可能会使您困惑。许多行文字使用大小明显相同的字体,并且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") ;
     	HFONT        				hFont ;
     	int          				i ;
     	LOGFONT      			lf ;

     	hFont = EzCreateFont (hdc, TEXT ("Times New Roman"), 540, 0, 0, TRUE) ;
     	GetObject (hFont, sizeof (LOGFONT), &lf) ;
  	DeleteObject (hFont) ;

     	SetBkMode (hdc, TRANSPARENT) ;
     	SetTextAlign (hdc, TA_BASELINE) ;
     	SetViewportOrgEx (hdc, cxArea / 2, cyArea / 2, NULL) ;

     	for (i = 0 ; i < 12 ; i ++)
     	{
          			lf.lfEscapement = lf.lfOrientation = i * 300 ;
          			SelectObject (hdc, CreateFontIndirect (&lf)) ;

          			TextOut (hdc, 0, 0, szString, lstrlen (szString)) ;
          			DeleteObject (SelectObject (hdc, GetStockObject (SYSTEM_FONT))) ;
     	}
}

FONTROT呼叫EzCreateFont只是为了获得与54点Times New Roman字体相关的LOGFONT结构。然後,程式删除该字体。在for回圈中,对於每隔30度的角度,建立新字体并显示文字。结果如图17-2所示。


 

图17-2 FONTROT的萤幕显示

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

字体列举
 

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

列举函式
 

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

EnumFonts (hdc, szTypeFace, EnumProc, pData) ;

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

EnumFontFamilies函式是Windows 3.1下列举TrueType字体的函式:

EnumFontFamilies (hdc, szFaceName, EnumProc, pData) ;

通常第一次呼叫EnumFontFamilies时,第二个参数设定为NULL。为每个字体系列(例如Times New Roman)呼叫一次EnumProccallback函式。然後,应用程式使用该字体名称和不同的callback函式再次呼叫EnumFontFamilies。GDI为字体系列中的每种字体(例如Times New Roman Italic)呼叫第二个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 int        		cyChar ;
     	static LOGFONT    		if ;
     	static TCHAR  szText[] = TEXT ("/x41/x42/x43/x44/x45 ")
                                 TEXT ("/x61/x62/x63/x64/x65 ")
                                 TEXT ("/xC0/xC1/xC2/xC3/xC4/xC5 ")
                                 TEXT ("/xE0/xE1/xE2/xE3/xE4/xE5 ") 
#ifdef UNICODE
       TEXT ("/x0390/x0391/x0392/x0393/x0394/x0395 ")
       TEXT ("/x03B0/x03B1/x03B2/x03B3/x03B4/x03B5 ")
     TEXT ("/x0410/x0411/x0412/x0413/x0414/x0415 ")
       TEXT ("/x0430/x0431/x0432/x0433/x0434/x0435 ")
       TEXT ("/x5000/x5001/x5002/x5003/x5004") 
#endif
                                 		;
     	HDC               				hdc ;
     	int               				y ;
     	PAINTSTRUCT       				ps ;
     	TCHAR             				szBuffer [64] ;
     	TEXTMETRIC        				tm ;
     
     	switch (message)
     	{
     	case WM_CREATE:
               				// Get text height
        			cyChar = HIWORD (GetDialogBaseUnits ()) ;
               				// Initialize the 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函式使用逻辑英寸从点值中计算lfHeight栏位。例如,假定您从「显示属性」对话方块中安装了「小字体」。这意味著带有视讯显示装置内容的GetDeviceCaps和参数LOGPIXELSY传回96。如果使用ChooseFont选择72点的Times Roman字体,实际上是想要1英寸高的字体。当ChooseFont传回後,LOGFONT结构的lfHeight栏位等於-96(注意负号),这是指字体的点值等於96图素,或者1逻辑英寸。

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

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

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

另一个使用ChooseFont的程式是UNICHARS,这个程式让您查看一种字体的所有字元,对於研究Lucida Sans Unicode字体(内定的显示字体)或Bitstream CyberBit字体尤其有用。UNICHARS总是使用TextOutW函式来显示字体的字元,因此可以在Windows NT或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 ;
     	int               				cxChar, cyChar, x, y, i, cxLabels ;
     	PAINTSTRUCT       				ps ;
     	SIZE              				size ;
     	TCHAR             				szBuffer [8] ;
     	TEXTMETRIC        				tm ;
     	WCHAR             				ch ;

     	switch (message)
     	{
     	case 	WM_CREATE:
          			hdc = GetDC (hwnd) ;
          			lf.lfHeight = - GetDeviceCaps (hdc, LOGPIXELSY) / 6 ;  // 12 points
          			lstrcpy (lf.lfFaceName, TEXT ("Lucida Sans Unicode")) ;
          			ReleaseDC (hwnd, hdc) ;

          			cf.lStructSize 	= sizeof (CHOOSEFONT) ;
          			cf.hwndOwner   		= hwnd ;
          			cf.lpLogFont   		= &lf ;
          			cf.Flags       		= CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS ;

          			SetScrollRange 		(hwnd, SB_VERT, 0, 255, FALSE) ;
          			SetScrollPos 		(hwnd, SB_VERT, iPage,  TRUE ) ;
          			return 0 ;

     	case 	WM_COMMAND:
          			switch (LOWORD (wParam))
          			{
          			case 	IDM_FONT:
               					if (	ChooseFont (&cf))
                    					InvalidateRect (hwnd, NULL, TRUE) ;
               					return 0 ;
			}
         	 		return 0 ;
     	case 	WM_VSCROLL:
          			switch (LOWORD (wParam))
             				{
               				case SB_LINEUP:    iPage -=  1 	;  break 	;
               				case SB_LINEDOWN:  iPage +=  1 	;  break 	;
               				case SB_PAGEUP:    iPage -= 16 	;  break 	;
               				case SB_PAGEDOWN:  iPage += 16 	;  break 	;
               				case SB_THUMBPOSITION:iPage= HIWORD (wParam);  break ;

               				default:
                    					return 0 ;
               				}

          			iPage = max (0, min (iPage, 255)) ;

          			SetScrollPos (hwnd, SB_VERT, iPage, TRUE) ;
          			InvalidateRect (hwnd, NULL, TRUE) ;
          			return 0 ;

     	case 	WM_PAINT:
          			hdc = BeginPaint (hwnd, &ps) ;

          			SelectObject (hdc, CreateFontIndirect (&lf)) ;

          			GetTextMetrics (hdc, &tm) ;
          			cxChar = tm.tmMaxCharWidth ;
          			cyChar = tm.tmHeight + tm.tmExternalLeading ;

          			cxLabels = 0 ;

          			for (i = 0 ; i < 16 ; i++)
          			{
               			wsprintf (szBuffer, TEXT (" 000%1X: "), i) ;
               			GetTextExtentPoint (hdc, szBuffer, 7, &size) ;

               				cxLabels = max (cxLabels, size.cx) ;
          			}

          			for (y = 0 ; y < 16 ; y++)
          			{
               wsprintf (szBuffer, TEXT (" %03X_: "), 16 * iPage + y) ;
               					TextOut (hdc, 0, y * cyChar, szBuffer, 7) ;

               					for (x = 0 ; x < 16 ; x++)
               					{
                    				ch = (WCHAR) (256 * iPage + 16 * y + x) ;
                    				TextOutW (hdc, 	x * cxChar + cxLabels,
                                   					y * cyChar, &ch, 1) ;
               }
          			}

          DeleteObject (SelectObject (hdc, GetStockObject (SYSTEM_FONT))) ;
          			EndPaint (hwnd, &ps) ;
          			return 0 ;

     	case 	WM_DESTROY:
          			PostQuitMessage (0) ;
          			return 0 ;
 	}
     	return DefWindowProc (hwnd, message, wParam, lParam) ;
}
 UNICHARS.RC
//Microsoft 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_FONT            40001

段落格式
 

具有选择并建立逻辑字体的能力後,就可以处理文字格式了。这个程序包括以四种方式之一来把文字的每一行放在页边距内:左对齐、向右对齐、居中或分散对齐-即从页边距的一端到另一端,文字间距相等。对於前三种方式,可以使用带有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 of Huckleberry Finn》中的第一段做了这样的处理。您可以从对话方块中选择想要的字体,也可以使用功能表选项来更改对齐方式(左对齐、向右对齐、居中或分散对齐)。图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 ;
     	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 #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 ;
     	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, 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 LOGFONT    		lf ;
     	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 ;
     	HMENU             				hMenu ;
     	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

#define 		IDM_FILE_PRINT   40001
#define 		IDM_FONT         40002
#define 		IDM_ALIGN_LEFT   40003
#define 		IDM_ALIGN_RIGHT  40004
#define 		IDM_ALIGN_CENTER 40005
#define 		IDM_ALIGN_JUSTIFIED  40006

JUSTIFY1在显示区域的上部和左侧显示了尺规(当然单位是逻辑英寸)。尺规由DrawRuler函式画出。一个矩形结构定义了分散对齐文字的区域。

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

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


 

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

列印输出预览
 

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

JUSTIFY1包含一个「Print」选项,但该选项仅在页面的上、左和右边设定1英寸的边距。这样,格式化完全与萤幕显示器无关。这里有一个有趣的练习:在JUSTIFY1中更改几行程式码,使萤幕和印表机逻辑依据一个6英寸的格式化矩形。方法就是在WM_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的David Weise所写的TTJUST(「TrueType Justify」)程式,而该程式又是依据本书前面的一个版本中的JUSTIFY1程式。为表现出这一程式中所增加的复杂性,用Herman Melville的《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,    -36	0, NULL) ;
     	LineTo   		(hdc, OUTWIDTH * 1440,	-36	0) ;
     	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 the
                         								original 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 selected
                     							font size
----------------------------------------------------------------------------*/

void GetScaledWidths (HDC hdc, double * pdWidths)
{
     	double  		dScale ;
     	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 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++ ;
               			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 	CHOOSEFONT	cf ;
     	static 		DOCINFO  			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 ;
     	HMENU             			hMenu ;
     	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

#define 		IDM_FILE_PRINT      40001
#define 		IDM_FONT            40002
#define 		IDM_ALIGN_LEFT      40003
#define 		IDM_ALIGN_RIGHT     40004
#define 		IDM_ALIGN_CENTER    40005
#define 		IDM_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_ 系列常数之一,它指出了绘图路径与目前剪裁区域的结合方式。

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

扩展画笔
 

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

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

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

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

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

在绘制宽线段时,我们可能要关注线段端点的外观。在连结直线或曲线时,可能还要关注线段间连结点的外观。画笔由CreatePen建立时,这些端点及连结点通常是圆形的;使用ExtCreatePen建立画笔时我们可以选择。(实际上 ,在Windows 98中,只有在使用画笔实作绘图路径时我们可以选择;在Windows NT中要更加灵活)。宽线段的端点可以使用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 int	cxClient, 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") ;
     	HFONT        				hFont ;
     	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") ;
     	HFONT        				hFont ;
     	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英寸)宽的红色点线笔。程式在Windows NT下执行时,结果如图17-6所示。Windows 98不支援超过1图素宽的非实心笔,因此Windows 98将以实心的红色笔绘制。


 

 

图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") ;
     	HFONT        				hFont ;
     	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-13 FONTCLIP所示。

 程式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") ;
     	HFONT        				hFont ;
     	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外壳程式允许您列印并显示这些效果,甚至允许您尝试自己的一些特殊效果。

  http://www.bitstream.com/products/world/cyberbits 中找到。(Lucida Sans Unicode大约有300K,而Bitstream CyberBit大约有13M)。如果您安装了这种字体,当需要一种Lucida Sans Unicode不支援的字体时,Windows会选择它,这些字体如:SHIFTJIS_CHARSET (日语)、HANGUL_CHARSET(朝鲜语)、JOHAB_CHARSET(朝鲜语)、GB2312_CHARSET(简体中文)或CHINESEBIG5_CHARSET (繁体中文)。
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您好!对于使用C语言编写的BLDC方波控制程序,以下是一个简单的示例: ```c #include <stdio.h> #include <stdlib.h> #include <wiringPi.h> #define PIN_A 0 #define PIN_B 1 #define PIN_C 2 void bldc_drive(int pin_a, int pin_b, int pin_c, int delay) { digitalWrite(pin_a, HIGH); digitalWrite(pin_b, LOW); digitalWrite(pin_c, LOW); delayMicroseconds(delay); digitalWrite(pin_a, LOW); digitalWrite(pin_b, HIGH); digitalWrite(pin_c, LOW); delayMicroseconds(delay); digitalWrite(pin_a, LOW); digitalWrite(pin_b, LOW); digitalWrite(pin_c, HIGH); delayMicroseconds(delay); } int main() { if (wiringPiSetup() == -1) { printf("WiringPi initialization failed!\n"); return 1; } pinMode(PIN_A, OUTPUT); pinMode(PIN_B, OUTPUT); pinMode(PIN_C, OUTPUT); while (1) { bldc_drive(PIN_A, PIN_B, PIN_C, 1000); // 设置延时时间,控制转速 } return 0; } ``` 这是一个简单的BLDC电机方波控制程序。它使用wiringPi库来管理树莓派的GPIO引脚。在main函数中,我们首先进行了wiringPi的初始化,并设置了引脚A、B和C为输出模式。 然后,在一个无限循环中,调用bldc_drive函数来控制电机的运行。该函数通过依次设置引脚A、B和C的电平状态来生成方波信号,控制电机的转动。您可以通过调整延时时间来改变转速。 请注意,这只是一个简单的示例程序,实际应用中可能需要更复杂的控制算法和保护机制。还要确保正确连接电机和正确配置引脚。 希望对您有所帮助!如有任何问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值