第十七章 文字和字體(有趣的東西)

有趣的東西

根據外形輪廓表示字體字符提供了將字體與其它圖形技術相結合的可能性。前面我們討論了旋轉字體的方式。這裏講述一些其它技巧。繼續之前,先了解兩個重要的預備知識:繪圖路徑和擴展畫筆。

GDI繪圖路徑

繪圖路徑是儲存在GDI內的直線和曲線的集合。繪圖路徑是在Windows的32位版本中發表的。繪圖路徑看上去類似於區域,我們確實可以將繪圖路徑轉換爲區域,並使用繪圖路徑進行剪裁。但隨後我們會發現兩者的不同。

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

BeginPath (hdc) ;

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

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

EndPath (hdc) ;

這時,接着呼叫下列五個函數之一:

StrokePath (hdc) ;

FillPath (hdc) ;

StrokeAndFillPath (hdc) ;

hRgn = PathToRegion (hdc) ;

SelectClipPath (hdc, iCombine) ;

這些函數中的每一個都會在繪圖路徑定義完成後,將其清除。

StrokePath使用目前畫筆繪製繪圖路徑。您可能會好奇:繪圖路徑上的點有哪些?爲什麼不能跳過這些繪圖路徑片段正常地畫線?稍後我會告訴您原因。

另外四個函數用直線關閉任何敞開的繪圖路徑。FillPath依照目前的多邊填充模式使用目前畫刷填充繪圖路徑。StrokeAndFillPath一次完成這兩項工作。也可將繪圖路徑轉換爲區域,或者將繪圖路徑用於某個剪裁區域。iCombine參數是CombineRgn函數使用的RGN_ 系列常數之一,它指出了繪圖路徑與目前剪裁區域的結合方式。

用於填充或剪取時,繪圖路徑比繪圖區域更靈活,這是因爲繪圖區域僅能由矩形、橢圓及多邊形的組合定義;繪圖路徑可由貝塞爾曲線定義,至少在Windows NT中還可由弧線組成。在GDI中,繪圖路徑和區域的儲存也完全不同。繪圖路徑是直線及曲線定義的集合;而繪圖區域(通常意義上)是掃描線的集合。

擴展畫筆

在呼叫StrokePath時,使用目前畫筆繪製繪圖路徑。在第四章討論了用以建立畫筆對象的CreatePen函數。伴隨繪圖路徑的發表,Windows也支持一個稱爲ExtCreatePen的擴展畫筆函數呼叫。該函數揭示了其建立繪圖路徑以及使用繪圖路徑要比不使用繪圖路徑畫線有用。ExtCreatePen函數如下所示:

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

您可以使用該函數正常地繪製線段,但在這種情況下Windows 98不支持一些功能。甚至用以顯示繪圖路徑時,Windows 98仍不支持一些功能,這就是上面函數的最後兩個參數被設定爲0及NULL的原因。

對於ExtCreatePen的第一個參數,可使用第四章中所討論的用在CreatePen上的所有樣式。您可使用PS_GEOMETRIC另外組合這些樣式(其中iWidth參數以邏輯單位表示線寬並能夠轉換),或者使用PS_COSMETIC(其中iWidth參數必須是1)。Windows 98中,虛線或點畫線樣式的畫筆必須是PS_COSMETIC,在Windows NT中取消了這個限制。

CreatePen的一個參數表示顏色;ExtCreatePen的相應參數不只表示顏色,它還使用畫刷給PS_GEOMETRIC畫筆內部着色。該畫刷甚至能透過位圖定義。

在繪製寬線段時,我們可能要關注線段端點的外觀。在連結直線或曲線時,可能還要關注線段間連結點的外觀。畫筆由CreatePen建立時,這些端點及連結點通常是圓形的;使用ExtCreatePen建立畫筆時我們可以選擇。(實際上 ,在Windows 98中,只有在使用畫筆實作繪圖路徑時我們可以選擇;在Windows NT中要更加靈活)。寬線段的端點可以使用ExtCreatePen中的下列畫筆樣式定義:

PS_ENDCAP_ROUND

PS_ENDCAP_SQUARE

PS_ENDCAP_FLAT

「square」樣式與「flat」樣式的不同點是:前者將線伸展到一半寬。與端點類似,繪圖路徑中線段間的連結點可通過如下樣式設定:

PS_JOIN_ROUND

PS_JOIN_BEVEL

PS_JOIN_MITER

「bevel」樣式將連結點切斷;「miter」樣式將連結點變爲箭頭。程序17-9所示的ENDJOIN是對此的一個較好的說明。

程序17-9 ENDJOIN
        
ENDJOIN.C

/*---------------------------------------------------------------------------

ENDJOIN.C -- Ends and Joins Demo

(c) Charles Petzold, 1998

----------------------------------------------------------------------------*/

#include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;


int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static TCHAR szAppName[] = TEXT ("EndJoin") ;

HWND hwnd ;

MSG msg ;

WNDCLASS wndclass ;



wndclass.style = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc = WndProc ;

wndclass.cbClsExtra = 0 ;

wndclass.cbWndExtra = 0 ;

wndclass.hInstance = hInstance ;

wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName = NULL ;

wndclass.lpszClassName = szAppName ;



if (!RegisterClass (&wndclass))

{

MessageBox ( NULL, TEXT ("This program requires Windows NT!"),

szAppName, MB_ICONERROR) ;

return 0 ;

}



hwnd = CreateWindow ( szAppName, TEXT ("Ends and Joins Demo"),

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT,

CW_USEDEFAULT, CW_USEDEFAULT,

NULL, NULL, hInstance, NULL) ;



ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;



while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

LRESULT CALLBACK WndProc ( HWND hwnd, UINT iMsg, WPARAM wParam,LPARAM lParam)

{

static int iEnd[] = {PS_ENDCAP_ROUND,PS_ENDCAP_SQUARE,PS_ENDCAP_FLAT } ;

static int iJoin[]= {PS_JOIN_ROUND,PS_JOIN_BEVEL,PS_JOIN_MITER } ;

static int cxClient, cyClient ;

HDC hdc ;

int i ;

LOGBRUSH ib ;

PAINTSTRUCT ps ;



switch (iMsg)

{

case WM_SIZE:

cxClient = LOWORD (lParam) ;

cyClient = HIWORD (lParam) ;

return 0 ;



case WM_PAINT:

hdc = BeginPaint (hwnd, &ps) ;



SetMapMode (hdc, MM_ANISOTROPIC) ;

SetWindowExtEx (hdc, 100, 100, NULL) ;

SetViewportExtEx (hdc, cxClient, cyClient, NULL) ;



lb.lbStyle = BS_SOLID ;

lb.lbColor = RGB (128, 128, 128) ;

lb.lbHatch = 0 ;



for (i = 0 ; i < 3 ; i++)

{

SelectObject (hdc, ExtCreatePen (PS_SOLID | PS_GEOMETRIC |

iEnd [i] | iJoin [i], 10, &lb, 0, NULL)) ;

BeginPath (hdc) ;

MoveToEx (hdc, 10 + 30 * i, 25, NULL) ;

LineTo (hdc, 20 + 30 * i, 75) ;

LineTo (hdc, 30 + 30 * i, 25) ;



EndPath (hdc) ;

StrokePath (hdc) ;



DeleteObject (

SelectObject (hdc,GetStockObject (BLACK_PEN))) ;


MoveToEx (hdc, 10 + 30 * i, 25, NULL) ;

LineTo (hdc, 20 + 30 * i, 75) ;

LineTo (hdc, 30 + 30 * i, 25) ;

}

EndPaint (hwnd, &ps) ;

return 0 ;



case WM_DESTROY:

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

}

程序使用上述端點和連結點樣式畫了三條V形的寬線段。程序也使用備用黑色畫筆畫了三條同樣的線。這樣就將寬線與通常的細線做了比較。結果如圖17-4所示。

 

 

圖17-4 ENDJOIN的屏幕顯示

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

四個範例程序

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

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

程序17-10 FONTOUT1
        
FONTOUT1.C

/*-----------------------------------------------------------------------------

FONTOUT1.C -- Using Path to Outline Font

(c) Charles Petzold, 1998

----------------------------------------------------------------------------*/

#include <windows.h>

#include "..//eztest//ezfont.h"


TCHAR szAppName [] = TEXT ("FontOut1") ;

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


void PaintRoutine (HWND hwnd, HDC hdc, int cxArea, int cyArea)

{

static TCHAR szString [] = TEXT ("Outline") ;

HFONT hFont ;

SIZE size ;


hFont = EzCreateFont (hdc, TEXT ("Times New Roman"), 1440, 0, 0, TRUE) ;

SelectObject (hdc, hFont) ;

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

BeginPath (hdc) ;

TextOut (hdc, (cxArea - size.cx) / 2, (cyArea - size.cy) / 2,

szString, lstrlen (szString)) ;

EndPath (hdc) ;

StrokePath (hdc) ;

SelectObject (hdc, GetStockObject (SYSTEM_FONT)) ;

DeleteObject (hFont) ;

}

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

程序建立了144點的TrueType字體並呼叫GetTextExtentPoint32函數取得文字方塊的大小。然後,呼叫繪圖路徑定義中的TextOut函數使文字在顯示區域窗口中處於中心的位置。因爲對TextOut函數的呼叫是被繪圖路徑設定命令所包圍的(即BeginPath和EndPath呼叫之間)程序中進行的,GDI不立即顯示文字。相反,程序將字符輪廓儲存在繪圖路徑定義中。

在繪圖路徑定義結束後,FONTOUT1呼叫StrokePath。因爲設備內容中未選入指定的畫筆,所以GDI僅僅使用內定畫筆繪製字符輪廓,如圖17-5所示。

 

 

圖17-5 FONTOUT1的屏幕顯示

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

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

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

程序17-11 FONTOUT2
        
FONTOUT2.C

/*-----------------------------------------------------------------------------

FONTOUT2.C -- Using Path to Outline Font

(c) Charles Petzold, 1998

----------------------------------------------------------------------------*/

#include <windows.h>

#include "..//eztest//ezfont.h"


TCHAR szAppName [] = TEXT ("FontOut2") ;

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


void PaintRoutine (HWND hwnd, HDC hdc, int cxArea, int cyArea)

{

static TCHAR szString [] = TEXT ("Outline") ;

HFONT hFont ;

LOGBRUSH lb ;

SIZE size ;


hFont = EzCreateFont (hdc, TEXT ("Times New Roman"), 1440, 0, 0, TRUE) ;

SelectObject (hdc, hFont) ;

SetBkMode (hdc, TRANSPARENT) ;


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

BeginPath (hdc) ;

TextOut (hdc, (cxArea - size.cx) / 2, (cyArea - size.cy) / 2,

szString, lstrlen (szString)) ;

EndPath (hdc) ;

lb.lbStyle = BS_SOLID ;

lb.lbColor = RGB (255, 0, 0) ;

lb.lbHatch = 0 ;


SelectObject (hdc, ExtCreatePen (PS_GEOMETRIC | PS_DOT,

GetDeviceCaps (hdc, LOGPIXELSX) / 24, &lb, 0, NULL)) ;

StrokePath (hdc) ;

DeleteObject (SelectObject (hdc, GetStockObject (BLACK_PEN))) ;

SelectObject (hdc, GetStockObject (SYSTEM_FONT)) ;

DeleteObject (hFont) ;

}

此程序呼叫StrokePath之前建立(並選入設備內容)一個3點(1/24英寸)寬的紅色點線筆。程序在Windows NT下執行時,結果如圖17-6所示。Windows 98不支持超過1圖素寬的非實心筆,因此Windows 98將以實心的紅色筆繪製。

 

 

圖17-6 FONTOUT2的屏幕顯示

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

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

程序17-12 FONTFILL
        
FONTFILL.C

/*----------------------------------------------------------------------------

FONTFILL.C -- Using Path to Fill Font

(c) Charles Petzold, 1998

-----------------------------------------------------------------------------*/

#include <windows.h>

#include "..//eztest//ezfont.h"


TCHAR szAppName [] = TEXT ("FontFill") ;

TCHAR szTitle [] = TEXT ("FontFill: Using Path to Fill Font") ;

void PaintRoutine (HWND hwnd, HDC hdc, int cxArea, int cyArea)

{

static TCHAR szString [] = TEXT ("Filling") ;

HFONT hFont ;

SIZE size ;


hFont = EzCreateFont (hdc, TEXT ("Times New Roman"), 1440, 0, 0, TRUE) ;

SelectObject (hdc, hFont) ;

SetBkMode (hdc, TRANSPARENT) ;


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

BeginPath (hdc) ;

TextOut (hdc, (cxArea - size.cx) / 2, (cyArea - size.cy) / 2,

szString, lstrlen (szString)) ;

EndPath (hdc) ;

SelectObject (hdc, CreateHatchBrush (HS_DIAGCROSS, RGB (255, 0, 0))) ;

SetBkColor (hdc, RGB (0, 0, 255)) ;

SetBkMode (hdc, OPAQUE) ;


StrokeAndFillPath (hdc) ;

DeleteObject (SelectObject (hdc, GetStockObject (WHITE_BRUSH))) ;

SelectObject (hdc, GetStockObject (SYSTEM_FONT)) ;

DeleteObject (hFont) ;

}

FONTFILL使用內定畫筆繪製繪圖路徑的輪廓,但使用HS_DIAGCROSS樣式建立紅色的陰影畫刷。注意程序在建立繪圖路徑時將背景模式設定爲TRANSPARENT,在填充繪圖路徑時又將其重設爲OPAQUE,這樣它能夠爲區域圖案使用藍色的背景顏色。結果如圖17-7所示。

您可能想在本程序中嘗試幾個變更,觀察變更的影響。首先,如果您將第一個SetBkMode呼叫變爲註解,將得到由圖案而不是字符本身所覆蓋的文字方塊背景。這通常不是我們實際所需要的,但確實可這樣做。

此外,填充字符及將它們用做剪裁時,您可能想有效地放棄內定的ALTERNATE多邊填充模式。我的經驗表示:如果使用WINDING填充模式,則構建TrueType字體以避免出現奇怪的現象(例如 「O」的內部被填充),但使用ALTERNATE模式更安全。

 

 

圖17-7 FONTFILL的屏幕顯示

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

程序17-13 FONTCLIP
        
FONTCLIP.C

/*---------------------------------------------------------------------------

FONTCLIP.C -- Using Path for Clipping on Font

(c) Charles Petzold, 1998

----------------------------------------------------------------------------*/

#include <windows.h>

#include "..//eztest//ezfont.h"


TCHAR szAppName [] = TEXT ("FontClip") ;

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


void PaintRoutine (HWND hwnd, HDC hdc, int cxArea, int cyArea)

{

static TCHAR szString [] = TEXT ("Clipping") ;

HFONT hFont ;

int y, iOffset ;

POINT pt [4] ;

SIZE size ;


hFont = EzCreateFont (hdc, TEXT ("Times New Roman"), 1200, 0, 0, TRUE) ;

SelectObject (hdc, hFont) ;


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

BeginPath (hdc) ;

TextOut (hdc, (cxArea - size.cx) / 2, (cyArea - size.cy) / 2,

szString, lstrlen (szString)) ;

EndPath (hdc) ;

// Set clipping area

SelectClipPath (hdc, RGN_COPY) ;

// Draw Bezier splines

iOffset = (cxArea + cyArea) / 4 ;

for (y = -iOffset ; y < cyArea + iOffset ; y++)

{

pt[0].x = 0 ;

pt[0].y = y ;


pt[1].x = cxArea / 3 ;

pt[1].y = y + iOffset ;


pt[2].x = 2 * cxArea / 3 ;

pt[2].y = y - iOffset ;


pt[3].x = cxArea ;

pt[3].y = y ;


SelectObject (hdc, CreatePen (PS_SOLID, 1,

RGB (rand () % 256, rand () % 256, rand () % 256))) ;

PolyBezier (hdc, pt, 4) ;

DeleteObject (SelectObject (hdc, GetStockObject (BLACK_PEN))) ;

}


DeleteObject (SelectObject (hdc, GetStockObject (WHITE_BRUSH))) ;

SelectObject (hdc, GetStockObject (SYSTEM_FONT)) ;

DeleteObject (hFont) ;

}

程序中故意不使用SetBkMode呼叫以實作不同的效果。程序在繪圖路徑支架中繪製一些文字,然後呼叫SelectClipPath。接着使用隨機顏色繪製一系列貝塞爾曲線。

如果FONTCLIP程序使用TRANSPARENT選項呼叫SetBkMode,貝塞爾曲線將被限制在字符輪廓的內部。在內定OPAQUE選項的背景模式下,剪裁區域被限制在文字方塊內部而不是文字內部。如圖17-8所示。

 

 

圖17-8 FONTCLIP得屏幕顯示

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

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

發佈了60 篇原創文章 · 獲贊 1 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章