17. 文字和字體

Post date: 2012/3/23 上午 05:41:23

17. 文字和字體

顯示文字是本書所要解決的首要問題,現在我們來研究Microsoft Windows中各種有效字體和字體大小的使用方法以及調整文字的方式。

Windows 3.1發表的TrueType使程式寫作者和使用者以靈活的方式處理文字的能力大幅增強。TrueType是輪廓字體技術,由Apple Computer公司和Microsoft公司開發,並被許多字體製造商支援。由於TrueType字體能夠連續縮放,並能應用於視訊顯示器和印表機,現在能夠在Windows下實作真的WYSIWYG(what you see is what you get:所見即所得)。TrueType也便於製作「奇妙」字體,例如旋轉的字母、內部填充圖案的字母或將它們用於剪裁區域,在本章我將展示它們。

簡單的文字輸出

讓我們先來看看Windows為文字輸出、影響文字的裝置內容屬性以及備用字體提供的各種函式。

文字輸出函式

我已經在許多範例程式中使用過最常用的文字輸出函式:

參數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函式:

如果文字字串中含有嵌入的跳位字元(‘\t’或0x09),則TabbedTextOut會根據傳遞給它的整數陣列將跳位字元擴展為空格。

TabbedTextOut的前五個參數與TextOut相同,第六個參數是跳位間隔數,第七個是以圖素為單位的跳位間隔陣列。例如,如果平均字元寬度是8個圖素,而您希望每5個字元加一個跳位間隔,則這個陣列將包含40、80、120,按遞增順序依此類推。

如果第六個和第七個參數是0或NULL,則跳位間隔按每八個平均字元寬度設定。如果第六個參數是1,則第七個參數指向一個整數,表示跳位間隔重複增大的倍數(例如,如果第六個參數是1,並且第七個參數指向值為30的變數,則跳位間隔設定在30、60、90…圖素處)。最後一個參數給出了從跳位間隔開始測量的邏輯x座標,它與字串的起始位置可能相同也可能不同。

另一個進階的文字輸出函式是ExtTextOut(字首Ext表示它是擴展的):

第五個參數是指向矩形結構的指標,在iOptions設定為ETO_CLIPPED時,該結構為剪裁矩形,在iOptions設定為ETO_OPAQUE時,該結構為用目前背景色填充的背景矩形。這兩種選擇您可以都採用,也可以都不採用。

最後一個參數是整數陣列,它指定了字串中連續字元的間隔。程式可以使用它使字元間距變窄或變寬,因為有時需要在較窄的列中調整單個文字。該參數可以設定為NULL來使用內定的字元間距。

用於寫文字的高級函式是DrawText,我們第一次遇到它是在第三章討論HELLOWIN程式時,它不指定座標的起始位置,而是通過RECT結構型態定義希望顯示文字的區域:

和其他文字輸出函式一樣,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來解決,它含有一個額外的參數:

最後一個參數是指向DRAWTEXTPARAMS結構的指標,它的定義如下:

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

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

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

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

DrawTextEx (hdc, pString, iCount, &rect, iFormat, &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外,其他幾個裝置內容屬性也對文字產生了影響。在內定的裝置內容下,文字顏色是黑色,但您可以用下面的敘述進行更改:

使用畫筆的顏色和畫刷的顏色,Windows把rgbColor的值轉換為純色,您可以通過呼叫GetTextColor取得目前文字的顏色。

Windows在矩形的背景區域中顯示文字,它可能根據背景模式的設定進行著色,也可能不這樣做。您可以使用

更改背景模式,其中iMode的值為OPAQUE或TRANSPARENT。內定的背景模式為OPAQUE,它表示Windows使用背景顏色來填充矩形的背景。您可以使用

來改變背景顏色。rgbColor的值是轉換為純色的值。內定背景色是白色。

如果兩行文字靠得太近,其中一個的背景矩形就會遮蓋另一個的文字。由於這種原因,我通常希望內定的背景模式是TRANSPARENT。在背景模式為TRANSPARENT的情況下,Windows會忽略背景色,也不對矩形背景區域著色。Windows也使用背景模式和背景色對點和虛線之間的空隙及陰影刷中陰影間的區域著色,就像第五章所討論的那樣。

許多Windows程式將WHITE_BRUSH指定為Windows用於擦出視窗背景的畫刷,畫刷在視窗類別結構中指定。然而,您可能希望您程式的視窗背景與使用者在「控制台」中設定的系統顏色保持一致,在這種情況下,可以在WNDCLASS結構中指定背景顏色的這種方式:

當您想要在顯示區域書寫文字時,可以使用目前系統顏色設定文字色和背景色:

完成這些以後,就可以使您的程式隨系統顏色的更改而變化:

SetTextColor (hdc, rgbColor) ;

SetBkMode (hdc, iMode) ;

SetBkColor (hdc, rgbColor) ;

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不在字元之間添加任何空間,但您可以使用以下函式插入空間:

參數iExtra是邏輯單位,Windows將其轉換為最接近的圖素,它可以是0。如果您將iExtra取為負值(希望將字元緊緊壓在一起),Windows會接受這個數值的絕對值─也就是說,您不能使iExtra的值小於0。您可以通過呼叫GetTextCharacterExtra取得目前的字元間距,Windows在傳回該值前會將圖素間距轉換為邏輯單位。

使用備用字體

當您呼叫TextOut、TabbedTextOut、ExtTextOut、DrawText或DrawTextEx書寫文字時,Windows使用裝置內容中目前選擇的字體。字體定義了特定的字樣和大小。以不同字體顯示文字的最簡單方法是使用Windows提供的備用字體,然而,它的範圍是很有限的。

您可以呼叫下面的函式取得某種備用字體的代號:

其中,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」映射方式。您可以設定如下:

SetTextCharacterExtra (hdc, iExtra) ;

hFont = GetStockObject (iFont) ;

SelectObject (hdc, hFont) ;

SelectObject (hdc, GetStockObject (iFont)) ;

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:

  • 您可以採用簡單的方法並呼叫ChooseFont函式,我在 第十一章 曾討論過這個函式,能夠使用LOGFONT結構直接建立字體。

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

下面是建立、選擇和刪除邏輯字體的程序:

  • 您可以簡單地將LOGFONT結構的欄位設定為所需的字體特徵。在這種情況下,在呼叫SelectObject時,Windows使用「字體映射」演算法從設備上有效的字體中選擇與這些特徵最匹配的字體。由於這依賴於視訊顯示器和印表機上的有效字體,所以其結果可能與您的要求有相當大的差別。
  • 您可以列舉設備上的所有字體並從中選擇,甚至用對話方塊把它們顯示給使用者。我將在本章後面討論字體列舉函式。不過,它們現在已經不常用了,因為第三種方法也可以進行列舉。
  1. 通過呼叫CreateFont或CreateFontIndirect建立邏輯字體,這些函式傳回HFONT型態的邏輯字體代號。
  2. 使用SelectObject將邏輯字體選入裝置內容,Windows會選擇與邏輯字體最匹配的真實字體。
  3. 使用GetTextMetrics(及可能用到的其他函式)確定真實字體的大小和特徵。在該字體選入裝置內容後,可以使用這些資訊來適當地設定文字的間距。
  4. 在使用完邏輯字體後,呼叫DeleteObject刪除邏輯字體,當字體選入有效的裝置內容時,不要刪除字體,也不要刪除備用字體。

GetTextFace函式使程式能夠確定目前選入裝置內容的字體名稱:

詳細的字體資訊可以從GetTextMetrics中得到:

其中,textmetric是TEXTMETRIC型態的變數,它具有20個欄位。

稍後我將詳細討論LOGFONT和TEXTMETRIC結構的欄位,這兩個結構有一些相似的欄位,所以它們容易混淆。現在您只需記住,LOGFONT用於定義邏輯字體,而TEXTMETRIC用於取得目前選入裝置內容中的字體資訊。

PICKFONT程式

使用程式17-1所示的PICKFONT,可以定義LOGFONT結構的許多欄位。這個程式建立邏輯字體,並在邏輯字體選入裝置內容後顯示真實字體的特徵。這是個方便的程式,通過它我們可以瞭解邏輯字體映射為真實字體的方式。

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

GetTextMetrics (hdc, &textmetric) ;

程式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或更大的顯示大小下執行這個程式。

非模態對話方塊還包含一些非邏輯字體結構的選項,它們是包括「Logical Twips」方式的映射方式、「Match Aspect」選項(更改Windows將邏輯字體與真實字體匹配的方式)和「Adv Grtx Mode」(設定Windows NT中的高級圖形模式)。稍後我將對這些作詳細討論。

從「Device」功能表中,可以選擇內定印表機而不是視訊顯示器。在這種情況下,PICKFONT將邏輯字體選入印表機裝置內容中,並從印表機顯示TEXTMETRIC結構。然後,程式將邏輯字體選入視窗裝置內容中,以顯示樣本字串。因此,程式顯示的文字可能會使用與TEXTMETRIC欄位所描述的字體(印表機字體)不同的字體(螢幕字體)。

PICKFONT程式的大部分邏輯都在處理對話方塊的必要動作,因此我不會詳細討論該程式的工作方式,只解釋建立和選擇邏輯字體的原理。

邏輯字體結構

您可以呼叫CreateFont來建立邏輯字體,它是具有14個參數的函式。一般,定義一個LOGFONT型態的結構

然後再定義該結構的欄位會更容易一些。完成後,可以使用指向該結構的指標呼叫CreateFontIndirect:

您不必設定LOGFONT結構的每個欄位。如果邏輯字體結構定義為靜態變數,那麼所有的欄位都會初始化為0,0一般是預設值。然後,可以不用更改而直接使用這個結構,CreateFontIndirect會傳回字體的代號。當您將該字體選入裝置內容時,會得到一個合理的內定字體。您可以根據自己的需要,明確或模糊地填充LOGFONT結構,Windows會用一種真實字體與您的要求相匹配。

在我討論LOGFONT結構中每個欄位時,您可能想用PICKFONT程式來測試它們。當您希望程式使用您輸入的任何欄位時,別忘了按下Enter或「OK」按鈕。

LOGFONT結構的前兩個欄位是邏輯單位,因此它們依賴於映射方式的目前設定:

LOGFONT lf ;

hFont = CreatFontIndirect (&lf) ;

圖17-1 典型的PICKFONT螢幕顯示(Windows NT下的Unicode版本)

  • 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
      • 在Windows 98中,這個值設定了TrueType文字的移位角度和方向。在Windows NT中,這個值通常也是這樣設定,除了用GM_ADVANCED參數呼叫SetGraphicsMode時,它按文件中說明的那樣工作。
  • lfOrientation 這是從水平方向逆時針測量的十分之幾的角度,它影響單個字元的外觀。表17-2提供了幾個例子:
            • 表17-2
      • 這個欄位一般不起作用,除非在Windows NT下使用TrueType字體,並把圖像模式設定為GM_ADVANCED, 在這種情況下它按文件中說明的那樣工作。
      • 其餘10個欄位如下:
  • lfWeight 這個欄位使您能夠指定粗體。WINGDI.H表頭檔案定義了可用於這個欄位的一組值(參見表17-3)。
            • 表17-3
      • 事實上,它比以前用過的任何一組值都完善。您可以對標準字使用0或400,對粗體使用700。
  • lfCharSet 這是指定字體字元集的一個位元組的值。我會在下一節「字元集和Unicode」中更詳細地討論這個欄位。在PICKFONT中,您可以按下帶有問號的按鈕來取得能夠使用的字元集列表。
    • 注意lfCharSet欄位是唯一不用零表示預設值的欄位。零值相當於ANSI_CHARSET,ANSI字元在美國和西歐使用。DEFAULT_CHARSET代碼等於1,表示程式執行的機器上內定的字元集。
  • lfItalic 在非零值時,它指定斜體。Windows能在GDI點陣字體上合成斜體。亦即,Windows僅僅移動若干行字元點陣圖來模仿斜體。對於TrueType字體,Windows使用真正的斜體或字體的傾斜版本。
  • lfUnderline 在非零值時,它指定底線,這項屬性在GDI字體上都是用合成的。也就是說,Windows GDI只是在包括空格的每個字元底線。
  • lfStrikeOut 在非零值時,它指定字體上應該有一條線穿過。這也是由GDI字體合成的。
  • lfOutPrecision 它指定了Windows用實際的字體匹配期望的字體大小和特徵的方式。這是一個複雜的欄位,一般很少使用。請查看關於LOGFONT結構的文件以得到更詳細的資訊。注意,可以使用OUT_TT_ONLY_PRECIS旗標來確保得到的是TrueType字體。
  • lfClipPrecision 這個欄位指定了當字元的一部分位於剪裁區以外時,剪裁字元的方式。這個欄位不經常使用,PICKFONT程式也沒有使用它。
  • lfQuality 這是一個給Windows的指令,有關於期望字體與實際字體相匹配的指令。它實際只對點陣字體有意義,並不影響TrueType字體。DRAFT_QUALITY旗標指出需要GDI縮放點陣字體以得到想要的大小;PROOF_QUALITY旗標指出不需縮放。PROOF_QUALITY字體最漂亮,但它們可能比所希望的要小一些。這個欄位中也可以使用DEFAULT_QUALITY(或0)。
  • lfPitchAndFamily 這個位元組由兩部分組成。您可以使用位元或運算符號結合用於此欄位的兩個識別字。最低的兩位元指定字體是定寬(即所有字元的寬度相等)還是變寬(參見表17-4)。
            • 表17-4
      • 位元組的上半部分指定字體系列(參見表17-5)。
              • 表17-5
  • 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
      • 不管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字體就是這樣的一種字體,您可以從 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 (繁體中文)。

本章的後面有一個程式可讓您查看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,如下所示:

函式傳回字體代號。可通過呼叫SelectObject將該字體選入裝置內容,然後呼叫GetTextMetrics或GetOutlineTextMetrics以確定字體尺寸在邏輯座標中的實際大小。在程式終止前,應該呼叫DeleteObject刪除任何建立的字體。

szFaceName參數可以是任何TrueType字體名稱。您選擇的字體越接近標準字體,則該字體在系統中存在的機率就越大。

第三個參數指出所需的點值,但是它的單位是十分之一點。因而,如果所需要的點值為十二又二分之一,則值應為125。

第四個參數通常應設定為零或與第三個參數相同。然而,通過將此欄位設定為不同值可以建立更寬或更窄的TrueType字體。它以點為單位描述了字體的寬度,有時稱之為字體的「全寬(em-width)」。不要將它與字體字元的平均寬度或其他類似的東西相混淆。在過去的排版技術中,大寫字母M的寬度與高度是相等的。於是,「完全正方形(em-square)」的概念產生了,這是全寬測量的起源。當字體的全寬等於字體的全高(字體的點值)時,字元寬度是字體設計者設定的寬度。寬或窄的全寬值可以產生更細或更寬的字元。

您可以將iAttributes參數設定為以下定義在EZFONT.H中的值:

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

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所示。

如果您對圖形旋轉和其他線性轉換的更專業方法感興趣,並且知道您的程式在Windows NT下執行將受到限制,您可以使用XFORM矩陣和座標轉換函式數。

字體列舉

字體列舉是從GDI中取得設備的全部有效字體列表的程序。程式可以選擇其中一種字體,或將它們顯示在對話方塊中供使用者選擇。我先簡單地介紹一下列舉函式,然後顯示使用ChooseFont函式的方法,ChooseFont降低了應用程式中進行字體列舉的必要性。

列舉函式

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

程式可以列舉所有的字體(將第二個參數設定為NULL)或只列出特定的字樣。第三個參數是列舉callback函式;第四個參數是傳遞給該函式的可選資料。GDI為系統中的每種字體呼叫callback函式,將定義字體的LOGFONT和TEXTMETRIC結構以及一些表示字體型態的旗標傳遞給它。

EnumFontFamilies函式是Windows 3.1下列舉TrueType字體的函式:

通常第一次呼叫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位元的版本下使用:

第二個參數是指向LOGFONT結構的指標,其中lfCharSet和lfFaceName欄位指出了所要列舉的字體資訊。Callback函式在ENUMLOGFONTEX和NEWTEXTMETRICEX結構中得到每種字體的資訊。

「ChooseFont」對話方塊

第十一章 稍微介紹了ChooseFont的通用對話方塊。現在,我們討論字體列舉,需要詳細瞭解一下ChooseFont函式的內部工作原理。ChooseFont函式得到指向CHOOSEFONT結構的指標以此作為它的唯一參數,並顯示列出所有字體的對話方塊。利用從ChooseFont中的傳回值,LOGFONT結構(CHOOSEFONT結構的一部分)能夠建立邏輯字體。

程式17-5所示的CHOSFONT程式展示了使用ChooseFont函式的方法,並顯示了函式定義的LOGFONT結構的欄位。程式也顯示了在PICKFONT中顯示的相同字串。

EnumFonts (hdc, szTypeFace, EnumProc, pData) ;

EnumFontFamilies (hdc, szFaceName, EnumProc, pData) ;

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

圖17-2 FONTROT的螢幕顯示

程式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早期版本的一些變化)。該函式根據裝置內容中選入的目前字體得出字串的寬度和高度:

邏輯單位的文字寬度和高度在SIZE結構的cx和cy欄位中傳回。我使用一行文字的例子,假定您把一種字體選入裝置內容,現在要寫入文字:

您希望文字從垂直座標yStart開始,頁邊距由座標xLeft和xRight設定。您的任務就是計算文字開始處的水平座標的xStart值。

如果文字以定寬字體顯示,那麼這項任務就相當容易,但通常不是這樣的。首先您得到字串的文字寬度:

如果size.cx比(xRight- xLeft)大,這一行就太長了,不能放在頁邊距內。我們假定它能放進去。

要向左對齊文字,只要把xStart設定為與xLeft相等,然後寫入文字:

這很容易。現在可以把size.cy加到yStart中寫下一行文字了。

要向右對齊文字,用以下公式計算xStart:

居中文字用以下公式:

現在開始艱鉅的任務-在左右頁邊距內分散對齊文字。頁邊距之間的距離是(xRight-xLeft)。如不調整,文字寬度就是size.cx。兩者之差

必須在字串的三個空格字元處平均配置。這聽起來很討厭,但還不是太糟。可以呼叫

來完成。第二個參數是字串內空格字元中需要分配的空間量。第三個參數是空格字元的數量,這裏為3。現在把xStart設定與xLeft相等,用TextOut寫入文字:

文字會在xLeft和xRight頁邊距之間分散對齊。

無論何時呼叫SetTextJustification,如果空間量不能在空格字元中平均分配,它就會累積一個錯誤值。這將影響後面的GetTextExtentPoint32呼叫。每次開始新的一行,都必須通過呼叫

來清除錯誤值。

使用段落

如果您處理整個段落,就必須從頭開始並掃描字串來尋找空格字元。每當碰到一個空格(或其他能用於斷開一行的字元),需呼叫GetTextExtentPoint32來確定文字是否能放入左右頁邊距之間。當文字超出允許的空間時,就要退回上一個空白。現在,您已經能夠確定一行的字串了。如果想要分散對齊該行,呼叫SetTextJustification和TextOut,清除錯誤值,並繼續下一行。

顯示在程式17-7中的JUSTIFY1對Mark Twain的《The Adventures of Huckleberry Finn》中的第一段做了這樣的處理。您可以從對話方塊中選擇想要的字體,也可以使用功能表選項來更改對齊方式(左對齊、向右對齊、居中或分散對齊)。圖17-3是典型的JUSTIFY1螢幕顯示。

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

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

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

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

xStart = xRight - size.cx ;

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

xRight - xLeft - size.cx

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

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

SetTextJustification (hdc, 0, 0) ;

程式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文書處理程式輕鬆做出的那樣)時,情況會更複雜。還沒有人聲稱這種處理容易,它只是比我們親自做所有的工作容易一些。

列印輸出預覽

有些字體不是為了在螢幕上查看用的,這些字體是用於列印的。通常在這種情況下,文字的螢幕預覽必須與列印輸出的格式精確配合。顯示同樣的字體、大小和字元格式是不夠的。使用TrueType是個捷徑。另外還需要將段落中的每一行在同樣位置斷開。這是WYSIWYG中的難點。

JUSTIFY1包含一個「Print」選項,但該選項僅在頁面的上、左和右邊設定1英吋的邊距。這樣,格式化完全與螢幕顯示器無關。這裏有一個有趣的練習:在JUSTIFY1中更改幾行程式碼,使螢幕和印表機邏輯依據一個6英吋的格式化矩形。方法就是在WM_PAINT和「Print」命令處理程式中更改rect.right的定義。在WM_PAINT處理程式中,相對應敘述為:

在「Print」命令處理程式中,相對應敘述為:

如果選擇了一種TrueType字體,螢幕上的linefeed情況應與印表機的輸出相同。

但實際情況並不是這樣。即使兩種設備使用同樣點值的相同字體,並將文字顯示在同樣的格式化矩形中,不同的顯示解析度及湊整誤差也會使linefeed出現在不同地方。顯然,需要一種更高明的方法進行螢幕上的列印輸出預覽。

程式17-8所示的JUSTIFY2示範了這種方法的一個嘗試。JUSTIFY2中的程式碼是依據Microsoft的David Weise所寫的TTJUST(「TrueType Justify」)程式,而該程式又是依據本書前面的一個版本中的JUSTIFY1程式。為表現出這一程式中所增加的複雜性,用Herman Melville的《Moby-Dick》中的第一章代替了Mark Twain小說的摘錄。

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

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

圖17-3 典型的JUSTIFY1螢幕顯示

程式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位元版本中發表的。繪圖路徑看上去類似於區域,我們確實可以將繪圖路徑轉換為區域,並使用繪圖路徑進行剪裁。但隨後我們會發現兩者的不同。

要定義繪圖路徑,可先簡單呼叫

進行該呼叫之後,所畫的任何線(例如,直線、弧及貝塞爾曲線)將作為繪圖路徑儲存在GDI內部,不被顯示到裝置內容上。繪圖路徑經常由連結起來的線組成。要製作連結線,應使用LineTo、PolylineTo和BezierTo函式,這些函式都以目前位置為起點劃線。如果使用MoveToEx改變了目前位置,或呼叫其他的畫線函式,或者呼叫了會導致目前位置改變的視窗/視埠函式,您就在整個繪圖路徑中建立了一個新的子繪圖路徑。因此,繪圖路徑包含一或多個子繪圖路徑,每一個子繪圖路徑是一系列連結的線段。

繪圖路徑中的每個子繪圖路徑可以是敞開的或封閉的。封閉子繪圖路徑之第一條連結線的第一個點與最後一條連結線的最後一點相同,並且子繪圖路徑通過呼叫CloseFigure結束。如果必要的話,CloseFigure將用一條直線封閉子繪圖路徑。隨後的畫線函式將開始一個新的子繪圖路徑。最後,通過下面的呼叫結束繪圖路徑定義:

這時,接著呼叫下列五個函式之一:

BeginPath (hdc) ;

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函式如下所示:

您可以使用該函式正常地繪製線段,但在這種情況下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中的下列畫筆樣式定義:

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

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所示。

現在大家該明白為什麼Windows支援StrokePath函式了:如果分別畫兩條直線,GDI不得不在每一條線上使用端點。只有在繪圖路徑定義中,GDI知道線段是連結的並使用線段的連結點。

四個範例程式

這究竟有什麼好處呢?仔細考慮一下:輪廓字體的字元由一系列座標值定義,這些座標定義了直線和轉折線。因而,直線及曲線能成為繪圖路徑定義的一部分。

確實可以!程式17-10所示的FONTOUT1程式對此做了展示。

圖17-4 ENDJOIN的螢幕顯示

程式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所示。

現在我們都得到什麼呢?我們已經獲得了所期望的輪廓字元,但是字串外面為什麼會圍繞著矩形呢?

回想一下,文字背景模式使用內定的OPAQUE,而不是TRANSPARENT。該矩形就是文字方塊的輪廓。這清晰地展示了在內定的OPAQUE模式下GDI繪製文字時所使用的兩個步驟:首先繪製一個填充的矩形,接著繪製字元。文字方塊矩形的輪廓也因此成為繪圖路徑的一部分。

使用ExtCreatePen函式就能夠使用內定畫筆以外的東西繪製字體字元的輪廓。程式17-11所示的FONTOUT2對此做了展示。

圖17-5 FONTOUT1的螢幕顯示

程式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將以實心的紅色筆繪製。

您也可以使用繪圖路徑定義填充區域。請用前面兩個程式所示的方法建立繪圖路徑,選擇一種填充圖案,然後呼叫FillPath。能呼叫的另一個函式是StrokeAndFillPath,它繪製繪圖路徑的輪廓並用一個函式呼叫將其填充。

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

圖17-6 FONTOUT2的螢幕顯示

程式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模式更安全。

最後,可使用一個繪圖路徑,因此也是一個TrueType字體,來定義剪裁區域。如程式17-13 FONTCLIP所示。

圖17-7 FONTFILL的螢幕顯示

程式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所示。

您或許會想在FONTCLIP中插入SetBkMode呼叫來觀察TRANSPARENT選項的變化。

FONTDEMO外殼程式允許您列印並顯示這些效果,甚至允許您嘗試自己的一些特殊效果。

圖17-8 FONTCLIP得螢幕顯示