TrueType字體通常包含在單個(gè)TrueType字體文件中,其文件后綴為.TTF。OpenType字體是以類似 于TrueType字體的格式編碼的POSTSCRIPT字體。OPENTYPE字體使用.OTF文件后綴。OPENTYPE還允許把多個(gè)OPENTYPE字體組合在一個(gè)文件中以利于數(shù)據(jù)共享。這些字體被稱為TrueType字體集(TrueType colleCTion),其文件后綴為.TTC。
TrueType字體用machintosh的輪廓字體資源的格式編碼,有一個(gè)唯一的標(biāo)記名"sfnt"。windows沒(méi)有macintosh的位圖字體資源格式,字體目錄 包含了字體格式的版本號(hào)和幾個(gè)表,每個(gè)表都有一個(gè)tableentry結(jié)構(gòu)項(xiàng),tableentry結(jié)構(gòu)包含了資源標(biāo)記、校驗(yàn)和、偏移量和每個(gè)表的大小。下面是TrueType字體目錄的c語(yǔ)言定義:
typedef sturct
{
char tag[4];
ULONG checkSum;
ULONG offset;
ULONG length;
}TableEntry;
typedef struct
{
Fixed sfntversion; //0x10000 for version 1.0
USHORT numTables;
USHORT searchRange;
USHORT entrySelector;
USHORT rangeShift;
TableEntry entries[1];//variable number of TableEntry
}TableDirectory;
TrueType 字體中的所有數(shù)據(jù)都使用big-endian編碼,最高位字節(jié)在最前面(因?yàn)門rueType字體最初是由apple公司定義的,而apple公司的os運(yùn)行在motorola的cpu上)。如果一人TrueType字體以00 01 00 00 ,00 17開頭,我們就可以知道它的格式是輪廓字體資源("sfnt")版本1.0的格式,有23個(gè)表。
TableDirectory結(jié)構(gòu)的最后一個(gè)字段是可變長(zhǎng)度的tableentry結(jié)構(gòu)的數(shù)組,安體中的每個(gè)表對(duì)應(yīng)其中一項(xiàng)。TrueType字體中的每個(gè)表都保存了不同的邏輯信息-----如圖元中數(shù)據(jù)、字符到圖元的映射、字距調(diào)整信息等等。有表是必須的,有些是可選的。下表列出了TrueType字體中常見的表。
head 字體頭 字體的全局信息
cmap 字符代碼到圖元的映射 把字符代碼映射為圖元索引
glyf 圖元數(shù)據(jù) 圖元輪廓定義以及網(wǎng)格調(diào)整指令
maxp 最大需求表 字體中所需內(nèi)存分配情況的匯總數(shù)據(jù)
mmtx 水平規(guī)格 圖元水平規(guī)格
loca 位置表索引 把元索引轉(zhuǎn)換為圖元的位置
name 命名表 版權(quán)說(shuō)明、字體名、字體族名、風(fēng)格名等等
hmtx 水平布局 字體水平布局星系:上高、下高、行間距、最大前進(jìn)寬度、最小左支撐、最小右支撐
kerm 字距調(diào)整表 字距調(diào)整對(duì)的數(shù)組
post PostScript信息 所有圖元的PostScript FontInfo目錄項(xiàng)和PostScript名
PCLT PCL 5數(shù)據(jù) HP PCL 5Printer Language 的字體信息:字體數(shù)、寬度、x高度、風(fēng)格、記號(hào)集等等
OS/2 OS/2和Windows特有的規(guī)格 TrueType字體所需的規(guī)格集
在TableDirectory結(jié)構(gòu)中,所有的TableEntry結(jié)構(gòu)都必須根據(jù)它們的標(biāo)記名排序。比如,cmap必須出現(xiàn)在head前,而head必須在glyf前。但是實(shí)際的表可以出現(xiàn)在TrueType字體文件中的任意位置。
Win32API 提供了一個(gè)應(yīng)用程序可用于查詢?cè)糡rueType字體信息的函數(shù):
DWORD GetFontData(HDC hDC,DWORD dwTable ,DWORD dwOffset, LPVOID lpbBuffer ,DWORD cbData);
GetFontData函數(shù)可以用于查詢?cè)O(shè)備上下文中當(dāng)前邏輯字體所對(duì)應(yīng)的TrueType字體,因此傳遞的不是邏輯字體句柄,而是設(shè)備上下文句柄。你可以查詢整個(gè)TrueType文件基是文件中的一個(gè)表。要查詢整個(gè)文件的話dwTable參數(shù)應(yīng)該為0;否則,應(yīng)該傳遞要查詢的表的四字符標(biāo)記的DWORD格式。參數(shù)dwOffset是要查詢的表中的起始偏移,要查詢整個(gè)表的話應(yīng)該為0;參數(shù);pvBuffer是緩沖區(qū)的地址,cbData是緩沖區(qū)的大小。如果最后個(gè)參數(shù)為NULL和0,GetFontData函數(shù)返回字體文件或表的大小;就會(huì)把到的數(shù)據(jù)拷貝到應(yīng)用程序所提供的緩沖區(qū)中。
下面的例和查詢整個(gè)TrueType字體的原始數(shù)據(jù):
TableDirctory * GetTrueTypeFont (HDC hDC ,DWORD &nFontSize)
{
//query font size
nFontSize=GetFontData(hDC,0,0,NULL,0);
TableDirectory * pFont =(TableDirectory *)new BYTE(nFontSize);
if (pFont==NULL)
return NULL;
GetFontData(hDC,0,0,pFont,nFontSize);
return pFont;
}
GetFontData使得應(yīng)用程序能夠在自己的文檔中內(nèi)嵌TrueType字體,以確保這些文檔能在沒(méi)有相應(yīng)字體的其他機(jī)器上顯示。它的做法是允許應(yīng)用程序查詢字體數(shù)據(jù),然后寫入到文檔中作為文檔的一部分,在文檔被打于時(shí)再安裝該字體以確保文檔能以創(chuàng)建時(shí)同樣的方式顯示。比如,Windows NT/2000的假脫機(jī)程序在打印到遠(yuǎn)端服務(wù)器時(shí)會(huì)在假脫機(jī)文件中內(nèi)嵌入TrueType字體以保證文檔能在另一臺(tái)機(jī)器上正確地打印。
一旦接受到TrueType字體的原始數(shù)據(jù),它的頭中的TableDirectory結(jié)構(gòu)很容易分析。需要檢查的只有版本號(hào)和表的數(shù)目,然后就可以檢查單個(gè)的表。我們來(lái)看一些重要的和有趣的表。
1.字體頭
字體頭表(head表)中包含了TrueType字體的全局信息。下面是字體頭表的結(jié)構(gòu)。
typedef sturct
{
Fixed Table;//x00010000 ro version 1.0
Fixed fontRevision;//Set by font manufacturer.
ULONG checkSumAdjustment;
ULONG magicNumer; //Set to 0x5f0f3cf5
USHORT flags;
USHORT unitsPerEm; //Valid range is from 16 to 16384
longDT created; //International date (8-byte field).
longDT modified; //International date (8-byte field).
FWord xMin; //For all glyph bounding boxes.
FWord yMin; //For all glyph bounding boxes.
FWord xMax; //For all glyph bounding boxes.
FWord xMax; //For all glyph bounding boxes.
USHORT macStyle;
USHORT lowestRecPPEM; //Smallest readable size in pixels.
SHORT fontDirctionHint;
SHORT indexToLocFormat; //0 for short offsets ,1 for long.
SHORT glyphDataFormat; //0 for current format.
}Table_head;
字體的歷史記錄在三個(gè)字段中:字全版本號(hào)、字體最初創(chuàng)建時(shí)間和字體最后修改時(shí)間。有8 個(gè)字節(jié)用于記錄時(shí)間戳,記錄的是從1904年1月1日午夜12:00開始的秒數(shù),因此我們不用擔(dān)心y2k問(wèn)題,或是什么y2m問(wèn)題。
字體設(shè)計(jì)時(shí)是針對(duì)一個(gè)參考網(wǎng)格設(shè)計(jì)的,該網(wǎng)格被稱為em-square,字體中的圖元用網(wǎng)格中的坐標(biāo)表示。因此em-squrare的大小決定胃該字體的圖元被縮放的方式,同時(shí)也反映胃該字體的質(zhì)量。字體頭中保存了每個(gè)em-square的格數(shù)和能 包含所有圖元的邊界框。Em-square的有效值是從16到16384,常見的值是2048、4096和8192。比如,Windings字體的em-square的格數(shù)是2048,圖元的邊界框是[0,-432,2783,1841]。
字體頭表中的其他信息包括最小可讀像素大小、字體方向、在位置表中圖元索引的格式和圖元數(shù)據(jù)格式等等。
最大需求表
TrueType字體是一種非常靈活的數(shù)據(jù)結(jié)構(gòu),它可以包含可變數(shù)目的圖元,每個(gè)圖元可以有不同數(shù)目的控制點(diǎn),甚至還可以有數(shù)量可變的圖元指令。最大需求表的目的是告知字體柵格器(rasterizer)對(duì)內(nèi)存的需求,以便 在出來(lái)字體前分配合適大小的內(nèi)存。因?yàn)樾阅軐?duì)字體柵格器非常重要,像MFC的CAarray那樣需要頻繁進(jìn)行數(shù)據(jù)拷貝操作的動(dòng)態(tài)增長(zhǎng)的數(shù)據(jù)結(jié)構(gòu)不合要求。下面是maxp表的結(jié)構(gòu)。
typedef struct
{
Fixed Version;//0x00010000 for version 1.0.
USHORT numGlypha; //Number of glyphs in the font .
USHORT maxPoints; //Max points in noncomposite glyph .
RSHORT maxContours; //Max contours in noncomposite glyph.
USHORT maxCompositePoints;//Max points in a composite glyph.
USHORT maxCompositeContours; //Max contours in a composite glyph.
USHORT maxZones;// 1 if not use the twilight zone [Z0],
//or 2 if so use Z0;2 in most cases.
USHORT max TwilightPoints ;/ Maximum points used in Z0.
USHORT maxStorage; //Number of storage area locations.
USHORT maxFunctionDefs; //Number of FDEFs.
USHORT maxStackElements; //Number of depth.
USHORT maxSizeOfInstructions; //Max byte count for glyph inst.
USHORT maxComponentElements; //Max number top components refernced.
USHORT maxComponentDepth; //Max levels of recursion.
}Table_maxp;
numGlyphs字段保存了字體中圖元的總數(shù),這決定了到位置表的圖元索引的數(shù)量,可以用于嚴(yán)正圖元索引的有效性。TrueType字體中的每個(gè)圖元都可以是合成圖元或簡(jiǎn)單圖元。簡(jiǎn)單圖元可以有一條或多大體上輪廓中國(guó),條用一些控制點(diǎn)定義。合成圖元用幾個(gè)其他圖元的組合來(lái)定義。maxPoints\maxCountors\maxCompositePoints maxCompositeContours這幾個(gè)字段說(shuō)明了圖元定義的復(fù)雜度。
除了圖元的定義,TrueType字體還使用了圖元指令用于提示字體掃描器如何對(duì)控制點(diǎn)進(jìn)行調(diào)整以得到更均衡更漂亮的光柵化后的圖元。圖元指令也可以出現(xiàn)在字體程序表(fpgm表)以及控制值程序表(“prep”)的全局字體層中。TrueType圖元指令是一個(gè)偽計(jì)算機(jī)字節(jié)指令,該機(jī)類似于Java的虛擬機(jī),這些指令可以用堆棧計(jì)算機(jī)執(zhí)行。MaxStackElements maxSizeOfInstructions兩個(gè)字段同志堆棧計(jì)算機(jī)這些指令的復(fù)雜度。
以Windings字體為例,該字體有226個(gè)圖元,圖元最多有47條輪廓線,簡(jiǎn)單圖元最多有268個(gè)點(diǎn),合成圖元最多有141個(gè)點(diǎn),合成圖元最多有14條輪廓線,最壞情況下需要492層堆棧,最長(zhǎng)的指令有1119個(gè)字節(jié)。
字符到圖元索引的映射表(cmap表)定義了從不同代碼頁(yè)中的字符 代碼到圖元索引的映射關(guān)系,這是在TrueType字體中存取圖元信息的關(guān)鍵。cmap表包含幾個(gè)了表以支持不同的平臺(tái)和不同的字符編碼方案。
下面是cmap表的結(jié)構(gòu)。
typedef struct
{
USHORT Platform; //platform ID
USHORT EncodingID; //encoding ID
ULONG TableOffset ;//offset to encoding table
typedef struct {
WCHAR wcLow;
USHORT cGlyphs;
}
typedef struct
{
DWORD cbThis; //sizeof (GLYPHSET)+sizeof(WCRANGE)+(cRanges-1)
DWORD flAccel;
DWORD cGlyphsSupported;
DWORD cRanges;
WCRANGE ranges[1]; //ranges[cRanges]
}GLYPHSET;
DWORD GetFontUnicodeRanges(HDC hDC,LPGLYPHSET lpgs);
DWORD GetGlyphIndices(HDC hDC,LPCTSTR lpstr,int c ,LPWORD pgi,DWORD fl);
通常一種字體只提供UNICODE字符集中的字符的一個(gè)子集。這些字符可以被分組為多個(gè)區(qū)域,cmap映射表中就是這么做的。GetFontUnicodeRanges函數(shù)在一個(gè)GLYPHSET結(jié)構(gòu)中返回支持的圖元的數(shù)量、支持的UNICODE區(qū)域的數(shù)量以及設(shè)備上下文中字體的這些區(qū)域的詳細(xì)信息。GLYPHSET是一個(gè)可變長(zhǎng)的結(jié)構(gòu) ,其大小取決于所支持的UNICODE區(qū)域的數(shù)量。因此,和Win32 API中支持可變長(zhǎng)結(jié)構(gòu)一樣, GetFontUnicodeRanges函數(shù)通常需要調(diào)用兩 次。第一次調(diào)用時(shí)得到以NULL指針作為最后一莜參數(shù),GDI會(huì)返回所需窨的大小。調(diào)用者然后分配所需的內(nèi)存,再次調(diào)用以得到真正的數(shù)據(jù)。這兩 種情況下,GetFontUnicodeRanges函數(shù)都會(huì)返回保存整個(gè)結(jié)構(gòu)所需的數(shù)據(jù)大小。MSDN文檔可能還是錯(cuò)誤地描述成了如果第二個(gè)參數(shù)是NULL,GetFontUnicodeRanges函數(shù)返回指向GLYPHSET結(jié)構(gòu)的指針。
下面是用于查詢上下文中當(dāng)前字體GLYPHSET結(jié)構(gòu)的一個(gè)簡(jiǎn)單函數(shù)。
GLYPHSET *QueryUnicodeRanges(HDC hDC)
{
//query for size
DWORD size=GetFontUnicodeRanges(hDC,NULL);
if (size==0) return NULL;
GLYPHSET *pGlyphSet=(GLYPHSET *)new BYTE(size);
//get real data
pGlyphSet->cbThis=size;
size=GetFontUnicodeRanges(hDC,pGlyphSet);
return pGlyphSet;
}
如果在一些Windows TrueType字體上試著調(diào)用GetFontUnicodeRanges函數(shù),你會(huì)發(fā)現(xiàn)這些字體通常支持1000個(gè)以上的圖元,這些圖元被分成幾百個(gè)UNICODE區(qū)域。比如,“Times New Roman”有我143個(gè)圖元,分布在145個(gè)區(qū)域中,和一個(gè)區(qū)域是0x20到0x7f,即可打印的7位ASCII代碼區(qū)域。
GetFontUnicodeRanges函數(shù)只使用了TrueType字體“cmap”表的一部分部分信息,即從UNICODE到圖元索引的映射域。GetGlyphIndices函數(shù)則能真正使用這些映射關(guān)系把一個(gè)字符串轉(zhuǎn)換為一個(gè)圖元索引的數(shù)組。它接收一個(gè)設(shè)備上下文句柄、一個(gè)字符串指針、字符串長(zhǎng)度、一個(gè)WORD數(shù)組的指針和一個(gè)標(biāo)志。生成的圖元索引將保存在WORD數(shù)組中。如果標(biāo)志為GGI_MASK_NONEXISTING_GLYPHS,找不到的字符的圖元索引會(huì)被標(biāo)注成0xFFFF。此函數(shù)得到的圖元索引可以傳給其他GDI函數(shù),如ExtTextOut函數(shù)。
2.位置索引
TrueType字體中最有用的信息是glyf表中的圖元數(shù)據(jù)。有了圖元索引,要找到相應(yīng)的圖元,需要表(loca表)索引以把圖元索引轉(zhuǎn)換為圖元數(shù)據(jù)表內(nèi)的偏移量。
位置索引表中保存了n+1個(gè)圖元數(shù)據(jù)表的索引,其中n是保存在最大需求表中的圖元數(shù)量。最后一個(gè)額外 的偏移量并不指向一個(gè)新圖元,而是指向最后一個(gè)圖元的偏移量和當(dāng)前圖元的偏移量和當(dāng)前圖元的偏移量間的差值得到圖元的長(zhǎng)度。
位置索引表中的每一個(gè)索引以無(wú)符號(hào)短整數(shù)對(duì)齊的,如果使用了短整數(shù)格式,索引表實(shí)際存儲(chǔ)的是WORD偏移量,而不是BYTE偏移量。這合得短整數(shù)格式的位置索引表能 支持128KB大小的圖元數(shù)據(jù)表。
3.圖元數(shù)據(jù)
圖元數(shù)據(jù)(glyf表)是TrueType字體的核心信息,因此通常它是最大的表。因?yàn)榈奈恢盟饕且粡垎为?dú)的表,圖元數(shù)據(jù)表就完全只是圖元的序列而已,每個(gè)圖元以圖元頭結(jié)構(gòu)開始:
typedef struct
{
WORD numberOfContours; //contor number,negative if composite
FWord xMin; //Minimum x for coordinate data.
FWord yMin; //Minimum y for coordinate data.
FWord xMax; //Maximum x for coordinate data.
FWord yMax; //Maximum y for coordinate data.
}GlyphHeader;
對(duì)于簡(jiǎn)單圖元,numberOfContours字段中保存的是當(dāng)前圖元的輪廓線的樹木;對(duì)于合成圖元,numberOfContours字段是一個(gè)負(fù)值。后者的輪廓線的總數(shù)必須基于組成該合成圖元的所有圖元的數(shù)據(jù)計(jì)算得到。GlyphHeader結(jié)構(gòu)中后四個(gè)字段記錄了圖元的邊界框。
對(duì)于簡(jiǎn)單圖元,圖元的描述緊跟在GlyphHeader結(jié)構(gòu)之后。圖元的描述由幾部分信息組成:所有輪廓線結(jié)束點(diǎn)的索引、圖元指令和一系列的控制點(diǎn)。每個(gè)控制點(diǎn)包括一個(gè)標(biāo)志以x和y坐標(biāo)。概念上而言,控制所需的信息和GDI函數(shù)PolyDraw函數(shù)所需的信息相同:一組標(biāo)志和一組點(diǎn)的坐標(biāo)。但TrueType字體中的控制點(diǎn)的編碼要復(fù)雜得多。下面是圖元描述信息的概述:
USHORT endPtsOfContours[n]; //n=number of contours
USHORT instructionlength;
BYTE instruction[i]; //i = instruction length
BYTE flags[]; //variable size
BYTE xCoordinates[]; //variable size
BYTE yCoordinates[]; //variable size
圖元可以包含一條或多條輪廓線。比如,字母"O"有兩 條輪廓線,一條是內(nèi)部的輪廓,另一條是外部的輪廓。對(duì)于每一條輪廓線,endPtsOfContours數(shù)組保存了其終點(diǎn)的索引,從該索引中可以計(jì)算出輪廓線中點(diǎn)的數(shù)量。比如,endPtsOfContours[0]是第一休輪廓線上點(diǎn)的數(shù)量,endPtsOfContours[1]-endPtsOfContours[0]是第二條輪廓線上點(diǎn)的數(shù)量。
終點(diǎn)數(shù)組后是圖元指令通知度和圖元指令數(shù)組。我們先跳過(guò)它們,先來(lái)討論冬至點(diǎn)。圖元的控制點(diǎn)保存在三個(gè)數(shù)組中:標(biāo)志獲得組、x坐標(biāo)數(shù)組和y坐標(biāo)數(shù)組。找到標(biāo)志數(shù)組的起始點(diǎn)很簡(jiǎn)單,但是標(biāo)志數(shù)組沒(méi)有相應(yīng)的長(zhǎng)度字,也沒(méi)有直接其他兩個(gè)數(shù)組的方法,你必須先解碼標(biāo)志數(shù)組才能解釋x和y坐標(biāo)數(shù)組。
我們提到棕em-square被限制為最大為16384個(gè)網(wǎng)格,因此通常情況下需要各兩個(gè)字節(jié)來(lái)表示x坐標(biāo)和y坐標(biāo)。為了節(jié)省空間,圖元中保存的是相對(duì)坐標(biāo)。第一個(gè)點(diǎn)的坐標(biāo)是相對(duì)(0,0)記錄的,所有隨后的點(diǎn)記錄者是和上一個(gè)點(diǎn)的坐標(biāo)差值。有些差值可以用一個(gè)字節(jié)表示,有些差值為0,另外一些差值則無(wú)法用耽擱字節(jié)表示。標(biāo)志數(shù)組保存了每個(gè)坐標(biāo)的編碼信息以及其他一些信息。下面是標(biāo)志中各個(gè)位的含義的總結(jié):
typedef enum
{
G_ONCURVE = 0x01, // on curve ,off curve
G_REPEAT =0x08, //next byte is flag repeat count
G_XMASK =0x12,
G_XADDBYTE =0x12, //X is positive byte
G_XSUBBYTE =0x12, //X is negative byte
G_XSAME =0x10, //X is same
G_XADDINT =0x00, //X is signed word
G_YMASK =0x24,
G_YADDBYTE =0x24, //Y is positive byte
G_YSUBBYTE =0x04, //Y is negative byte
G_YSAME =0x20 , //Y is same
G_YADDINT =0x00, //Y is signed word
};
在第8章中我們討論了直線和曲線,我們提到了一段三階Bezier曲線有四個(gè)控制點(diǎn)定義:位于曲線上(on-curve)的起始點(diǎn)、兩個(gè)不在曲線上(off-curve)的控制點(diǎn)和一個(gè)曲線上的結(jié)束點(diǎn)。TureType字體中的圖元輪廓是用二階Bezier曲線定義的,有三個(gè)點(diǎn):一個(gè)曲線上的點(diǎn),一個(gè)曲線外的點(diǎn)和另一個(gè)曲線上的點(diǎn)。多個(gè)連續(xù)的不在曲線上的點(diǎn)是允許的,但不是用來(lái)定義三階或更高階的Bezier曲線,而是為了減少控制點(diǎn)的數(shù)目。比如,對(duì)于on-off-off-on模式的四個(gè)點(diǎn),會(huì)加入一個(gè)隱含的點(diǎn)使之成為on-off-on-off-on,因此定義的是兩段二階Bezier曲線。
如果設(shè)置了G_ONCURVE位,那么控制點(diǎn)在曲線上,否則不在曲線上。如果設(shè)置了G_REPEAT,標(biāo)志數(shù)組中的下一字節(jié)表示重復(fù)次數(shù),當(dāng)前標(biāo)志應(yīng)該重復(fù)指定的次數(shù)。因此,標(biāo)志數(shù)組中實(shí)際使用了某種類型的行程編碼。標(biāo)志中的其他位用于描述相應(yīng) 的x坐標(biāo)和y坐標(biāo)的編碼方式,它們可以表示當(dāng)前相尋坐標(biāo)是否和上一個(gè)相同、正的單字節(jié)值、負(fù)的單字節(jié)值或有符號(hào)兩字節(jié)值。
解碼圖元的描述是一個(gè)兩次掃描的起始點(diǎn)。然后再遍歷圖元定義中的每一個(gè)點(diǎn)把它轉(zhuǎn)換為更容易管理的格式。程序清單14-2列出了解碼TrueType圖元的函數(shù),它是KTrueType類的一個(gè)方法。
int KTrueType::DecodeGlyph(int index, KCurve & curve, XFORM * xm) const
{
const GlyphHeader * pHeader = GetGlyph(index);
if ( pHeader==NULL )
{
// assert(false);
return 0;
}
int nContour = (short) reverse(pHeader->numberOfContours);
if ( nContour<0 )
{
return DecodeCompositeGlyph(pHeader+1, curve); // after the header
}
if ( nContour==0 )
return 0;
curve.SetBound(reverse((WORD)pHeader->xMin), reverse((WORD)pHeader->yMin),
reverse((WORD)pHeader->xMax), reverse((WORD)pHeader->yMax));
const USHORT * pEndPoint = (const USHORT *) (pHeader+1);
int nPoints = reverse(pEndPoint[nContour-1]) + 1; // endpoint of last contour + 1
int nInst = reverse(pEndPoint[nContour]); // instructon length
curve.m_glyphindex = index;
curve.m_glyphsize = (int) GetGlyph(index+1) - (int) GetGlyph(index);
curve.m_Ascender = m_Ascender;
curve.m_Descender = m_Descender;
curve.m_LineGap = m_LineGap;
GetMetrics(index, curve.m_advancewidth, curve.m_lsb);
if ( curve.m_glyphsize==0 )
return 0;
curve.m_instrsize = nInst;
const BYTE * pFlag = (const BYTE *) & pEndPoint[nContour] + 2 + nInst; // first byte in flag
const BYTE * pX = pFlag;
int xlen = 0;
for (int i=0; i<nPoints; i++, pX++)
{
int unit = 0;
switch ( pX[0] & G_XMASK )
{
case G_XADDBYTE:
case G_XSUBBYTE:
unit = 1;
break;
case G_XADDINT:
unit = 2;
}
if ( pX[0] & G_REPEAT )
{
xlen += unit * (pX[1]+1);
i += pX[1];
pX ++;
}
else
xlen += unit;
}
const BYTE * pY = pX + xlen;
int x = 0;
KTrueType類處理TrueType字體的裝入和解碼,隨書光盤中有它的完整源代碼。DecodeGlyph給出圖元索引和可選的變換矩陣,處理的是單個(gè)圖元的解碼。參數(shù)curve是KCurve類,用于把TrueType圖元定義保存為32位的點(diǎn)的贖罪以及一個(gè)標(biāo)志數(shù)組,以梗用GDI進(jìn)行顯示。這些代碼可以作為簡(jiǎn)單TrueType字體編輯器的基礎(chǔ)。
代碼中調(diào)用了GetGlyph方法,該方法用位置表索引找到該圖元的GlyphHeader結(jié)構(gòu)。從中得到圖元的輪廓線數(shù)目。注意必須反轉(zhuǎn)該值的字節(jié)序,因?yàn)門rueType字體用的是Big-Endian字節(jié)序。如果該值為負(fù)值,說(shuō)明這是一個(gè)合成圖元,應(yīng)該轉(zhuǎn)而調(diào)用DecodeCompositeGlyph方法。接下支的代碼定位了endPtsOfContours數(shù)組,找出點(diǎn)的總數(shù),然后跳過(guò)指令找到標(biāo)志數(shù)組的起始位置。
接下去需要長(zhǎng)到的是x坐標(biāo)數(shù)組的始位置和長(zhǎng)度,這需要遍歷標(biāo)志數(shù)組一次。對(duì)于每一個(gè)控制點(diǎn),它在x坐標(biāo)數(shù)組中所占空間可能為0到2個(gè)字節(jié),這取決于它的相對(duì)坐標(biāo)是0、單個(gè)字節(jié)還是兩個(gè)字節(jié)。
根據(jù)x坐標(biāo)數(shù)組的地址和長(zhǎng)度可以得到y(tǒng)坐標(biāo)的地址。接下去的代碼遍歷所有的輪廓線,解碼其中的控制點(diǎn),把相對(duì)坐標(biāo)轉(zhuǎn)換為絕對(duì)坐標(biāo),然后把它加入到曲線對(duì)象中。如果需要的話,會(huì)對(duì)每個(gè)控制點(diǎn)做變換。
回想一下,TrueType使用的是二階Bezier曲線,允許在兩個(gè)曲線上的點(diǎn)之間有多個(gè)不在曲線上的點(diǎn)。為了簡(jiǎn)化曲線繪制算法,KCurve::Add方法在每?jī)蓚€(gè)不在曲線上的點(diǎn)之間加入一個(gè)額外的在曲線上的點(diǎn)。
處理了簡(jiǎn)單圖元之后,我們來(lái)看看合成圖元。合成圖元用一個(gè)經(jīng)變換的圖元序列定義。每個(gè)經(jīng)變換的圖元的定義包括三個(gè)部分:一個(gè)標(biāo)志、一個(gè)圖元索引和一個(gè)變換矩陣。標(biāo)志字段決定了變換矩陣的編碼方式。編碼的目的也是為了節(jié)省一些空間,加外還說(shuō)明了是否已到達(dá)序列的終點(diǎn)。一個(gè)完整的2D affine變換需要6個(gè)值。但如果只是平移的話,只需要兩個(gè)值(dx,dy),這兩個(gè)值可以保存為兩個(gè)字節(jié)或兩個(gè)字。如果x和y以相同的值縮放,加外還需要一個(gè)縮放值。取一般的情況下仍然需要6個(gè)值,但是很多時(shí)候可以節(jié)省幾個(gè)字節(jié)。用于變換的值以2.14的有符號(hào)定點(diǎn)格式保存,dx和dy值除外,這兩個(gè)值以整數(shù)形式保存。得到合成圖元的過(guò)程實(shí)際上是變換和組合幾個(gè)圖元的過(guò)程。比如,如果字體中的一個(gè)圖元是另一個(gè)圖元的精確鏡像,它只需定義為一個(gè)合成圖元,可以通過(guò)對(duì)另一個(gè)圖像做鏡像變換即可。程序清單14-3列出了解碼合成圖元的代碼。
int KTrueType::DecodeCompositeGlyph(const void * pGlyph, KCurve & curve) const
{
KDataStream str(pGlyph);
unsigned flags;
int len = 0;
do
{
flags = str.GetWord();
unsigned glyphIndex = str.GetWord();
// Argument1 and argument2 can be either x and y offsets to be added to the glyph or two point numbers.
// In the latter case, the first point number indicates the point that is to be matched to the new glyph.
// The second number indicates the new glyph's "matched" point. Once a glyph is added, its point numbers
// begin directly after the last glyphs (endpoint of first glyph + 1).
// When arguments 1 and 2 are an x and a y offset instead of points and the bit ROUND_XY_TO_GRID is set to 1,
// the values are rounded to those of the closest grid lines before they are added to the glyph.
// X and Y offsets are described in FUnits.
signed short argument1;
signed short argument2;
if ( flags & ARG_1_AND_2_ARE_WORDS )
{
argument1 = str.GetWord(); // (SHORT or FWord) argument1;
argument2 = str.GetWord(); // (SHORT or FWord) argument2;
}
else
{
argument1 = (signed char) str.GetByte();
argument2 = (signed char) str.GetByte();
}
signed short xscale, yscale, scale01, scale10;
xscale = 1;
yscale = 1;
scale01 = 0;
scale10 = 0;
if ( flags & WE_HAVE_A_SCALE )
{
xscale = str.GetWord();
yscale = xscale; // Format 2.14
}
else if ( flags & WE_HAVE_AN_X_AND_Y_SCALE )
{
xscale = str.GetWord();
yscale = str.GetWord();
}
else if ( flags & WE_HAVE_A_TWO_BY_TWO )
{
xscale = str.GetWord();
scale01 = str.GetWord();
scale10 = str.GetWord();
yscale = str.GetWord();
}
if ( flags & ARGS_ARE_XY_VALUES )
{
XFORM xm;
xm.eDx = (float) argument1;
xm.eDy = (float) argument2;
xm.eM11 = xscale / (float) 16384.0;
xm.eM12 = scale01 / (float) 16384.0;
xm.eM21 = scale10 / (float) 16384.0;
xm.eM22 = yscale / (float) 16384.0;
len += DecodeGlyph(glyphIndex, curve, & xm);
}
else
assert(false);
}
while ( flags & MORE_COMPONENTS );
if ( flags & WE_HAVE_INSTRUCTIONS )
{
unsigned numInstr = str.GetWord();
for (unsigned i=0; i<numInstr; i++)
str.GetByte();
}
// The purpose of USE_MY_METRICS is to force the lsb and rsb to take on a desired value.
// For example, an i-circumflex (Unicode 00ef) is often composed of the circumflex and a dotless-i.
// In order to force the composite to have the same metrics as the dotless-i,
// set USE_MY_METRICS for the dotless-i component of the composite. Without this bit,
// the rsb and lsb would be calculated from the HMTX entry for the composite (or would need to be
// explicitly set with TrueType instructions).
// Note that the behavior of the USE_MY_METRICS operation is undefined for rotated composite components.
return len;
}
DecodeCompositeGlyph方法解碼每個(gè)圖元的標(biāo)志、圖元索引和變換矩陣,然后調(diào)用DecodeGlypgh方法進(jìn)行解碼。注意,對(duì)DecodeGlyph方法的調(diào)用包含一個(gè)有效的變換矩陣參數(shù)。當(dāng)MORE_COMPONENTS標(biāo)志結(jié)束時(shí),該方法隨之結(jié)束。隨書光盤中有該方法完整的源代碼。
解碼后的TrueType字體的圖元要用GDI繪制還有一個(gè)小問(wèn)題需要處理。GDI只繪制三階Bezier曲線,因此從圖元表解碼所得的二階Bezier曲線的控制點(diǎn)需要轉(zhuǎn)換為三階Bezier曲線的控制點(diǎn)。通過(guò)對(duì)Bezier曲線原始數(shù)學(xué)定義的研究,可以得到如下用GDI繪制二階Bezier曲線的簡(jiǎn)單例程。
//draw a 2nd-degree Bezier curve segment
BOOL Bezier2(HDC hDC,int & x0,int & y0, int x1, int y1, int x2 ,int y2)
{
// p0 p1 p2 - > p0 (p0 + 2p1)/3 (2p1+p2)/3, p2
POINT P[3] = { { (x0+2*x1)/3,(y0+2*y1)/3},
{(2*x1+x2)/3,(2*y1+y2)/3},
{x2,y2} };
x0=x2;y0=y2;
return PolyBezierTo(hDC,P,3);
}
對(duì)于用三個(gè)控制點(diǎn)(p0,p1,p2)定義的二階Bezier曲線,相應(yīng)的三階Bezier曲線的控制點(diǎn)為(p0,(p0+2*p1)/3,(2*p1+p2)/3,p2)。
4.圖元指令
程序清單14-2和14-3給人的印象是TrueType字體的柵格器可以通過(guò)掃描和轉(zhuǎn)換圖元的輪廓來(lái)輕松地實(shí)現(xiàn),比如,用GDI和StrokeAndFillPath函數(shù)來(lái)填充圖元輪廓繪制出來(lái)的路徑。這種簡(jiǎn)單的字體柵格器的實(shí)現(xiàn)并不是很有用,除非它只用于高分辨詣的設(shè)備如打印機(jī)等。
簡(jiǎn)單柵格器得到的圖像筆畫粗細(xì)不一,有信息的遺漏,有字符特征的損失以及不對(duì)稱等缺陷。當(dāng)點(diǎn)陣變小是,情況不會(huì)更糟。總之,簡(jiǎn)單字體柵格器在小尺寸時(shí)會(huì)產(chǎn)生字跡模糊的結(jié)果。在大尺寸時(shí)會(huì)產(chǎn)生不好看的結(jié)果,只有在點(diǎn)陣增大時(shí)結(jié)果才會(huì)改善。
當(dāng)在大的em-square(典型的是2048)中定義的圖元輪廓縮小為小得多的網(wǎng)格時(shí)(如32*32),不可避免會(huì)損失精度并引入誤差。
TrueType解決這個(gè)問(wèn)題的方法是控制圖元輪廓從em-square到柵格網(wǎng)格的縮放過(guò)程,使得到的結(jié)果看起來(lái)效果更好,和原始圖元的設(shè)計(jì)盡量接近。這種技術(shù)被稱為網(wǎng)格調(diào)整(grid fitting),它想達(dá)到的目標(biāo)有:
消除網(wǎng)格位置的可能影響,保證筆畫的粗細(xì)和網(wǎng)格的相對(duì)位置無(wú)關(guān)。
控制圖元中關(guān)鍵位置的尺寸
保持對(duì)稱性和襯線等 重要的圖元設(shè)計(jì)細(xì)節(jié)。
TrueType字體中網(wǎng)格調(diào)整的需求在兩個(gè)地方中編碼:控制值表(control value table)和每個(gè)圖元的網(wǎng)格調(diào)整指令。
控制值表("cvt"表)用于保存一個(gè)數(shù)組,這些值被稱為網(wǎng)格調(diào)整指令。比如,對(duì)于有襯線的字體,基線、襯線高度、大寫字母筆劃的寬度等值都或以是被控制的值。它們可以以字體設(shè)計(jì)者已知的次序保存在控制值表中,然后用它們的索引來(lái)引用。在字體光柵化過(guò)程中,控制值表中的值根據(jù)點(diǎn)陣的大小縮放。在網(wǎng)絡(luò)調(diào)整指令中引用這些值 可以保證使用的值與網(wǎng)枸的位置無(wú)關(guān)。比如,如果水平線[14,0,25,200]可以用CVT表中的兩個(gè)值定義為[14,0,14+CVT[stem_width],0+CVT[cap_height]],那 么該線的寬度和高度會(huì)和所在網(wǎng)格的相對(duì)位置無(wú)關(guān),保持不變。
每一個(gè)圖元的定義中附加有一個(gè)指令序列,該指令序列被稱為圖元指令,該背景令序列用于控制此圖元的網(wǎng)格高速。圖元指令線用控制值表中的值,以保證在索引圖元中這些值相同。
圖元指令是一種基于堆棧的偽計(jì)算機(jī)的指令。堆棧計(jì)算機(jī)常用于計(jì)算機(jī)語(yǔ)言的解釋性實(shí)現(xiàn)。比如,F(xiàn)orth(用于嵌入式系統(tǒng)的一種強(qiáng)大而簡(jiǎn)潔的語(yǔ)言)、RPL(HP計(jì)算器使用的語(yǔ)言)和Java虛擬機(jī)都是堆棧計(jì)算機(jī)。
堆棧計(jì)算機(jī)通常沒(méi)有寄存器,所有的計(jì)算都在堆棧上進(jìn)行(有些堆棧計(jì)算機(jī)使用分開的控制堆棧和數(shù)據(jù)堆棧)。比如,壓入指令把一個(gè)值壓入堆棧,彈出指令從堆棧中彈出上面的值,二元加法指令彈出上面的兩 個(gè)值 ,然后把它們的和壓入堆棧。
本文來(lái)自CSDN博客,轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/clq271520093/archive/2008/01/17/2048257.aspx