關於TrueType字體

分類: C/C++

TrueType字體通常包含在單個TrueType字體文件中,其文件後綴爲.TTF。OpenType字體是以類似   於TrueType字體的格式編碼的POSTSCRIPT字體。OPENTYPE字體使用.OTF文件後綴。OPENTYPE還允許把多個OPENTYPE字體組合在一個文件中以利於數據共享。這些字體被稱爲TrueType字體集(TrueType   collection),其文件後綴爲.TTC。
      TrueType字體用machintosh的輪廓字體資源的格式編碼,有一個唯一的標記名"sfnt"。windows沒有macintosh的位圖字體資源格式,字體目錄   包含了字體格式的版本號和幾個表,每個表都有一個tableentry結構項,tableentry結構包含了資源標記、校驗和、偏移量和每個表的大小。下面是TrueType字體目錄的c語言定義:
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   字體中的所有數據都使用big-endian編碼,最高位字節在最前面(因爲TrueType字體最初是由apple公司定義的,而apple公司的os運行在motorola的cpu上)。如果一人TrueType字體以00   01   00   00   ,00   17開頭,我們就可以知道它的格式是輪廓字體資源("sfnt")版本1.0的格式,有23個表。

        TableDirectory結構的最後一個字段是可變長度的tableentry結構的數組,安體中的每個表對應其中一項。TrueType字體中的每個表都保存了不同的邏輯信息-----如圖元中數據、字符到圖元的映射、字距調整信息等等。有表是必須的,有些是可選的。下表列出了TrueType字體中常見的表。
  
head           字體頭                                           字體的全局信息
cmap           字符代碼到圖元的映射               把字符代碼映射爲圖元索引
glyf           圖元數據                                       圖元輪廓定義以及網格調整指令
maxp           最大需求表                                   字體中所需內存分配情況的彙總數據
mmtx           水平規格                                       圖元水平規格
loca           位置表索引                                   把元索引轉換爲圖元的位置
name           命名錶                                           版權說明、字體名、字體族名、風格名等等
hmtx           水平佈局                                       字體水平佈局星系:上高、下高、行間距、最大前進寬度、最小左支撐、最小右支撐
kerm           字距調整表                                   字距調整對的數組
post           PostScript信息                           所有圖元的PostScript   FontInfo目錄項和PostScript名
PCLT           PCL   5數據                                     HP   PCL   5Printer   Language   的字體信息:字體數、寬度、x高度、風格、記號集等等
OS/2           OS/2和Windows特有的規格         TrueType字體所需的規格集
        在TableDirectory結構中,所有的TableEntry結構都必須根據它們的標記名排序。比如,cmap必須出現在head前,而head必須在glyf前。但是實際的表可以出現在TrueType字體文件中的任意位置。
        Win32API   提供了一個應用程序可用於查詢原始TrueType字體信息的函數:
DWORD   GetFontData(HDC   hDC,DWORD   dwTable   ,DWORD   dwOffset,   LPVOID   lpbBuffer   ,DWORD   cbData);
        GetFontData函數可以用於查詢設備上下文中當前邏輯字體所對應的TrueType字體,因此傳遞的不是邏輯字體句柄,而是設備上下文句柄。你可以查詢整個TrueType文件基是文件中的一個表。要查詢整個文件的話dwTable參數應該爲0;否則,應該傳遞要查詢的表的四字符標記的DWORD格式。參數dwOffset是要查詢的表中的起始偏移,要查詢整個表的話應該爲0;參數;pvBuffer是緩衝區的地址,cbData是緩衝區的大小。如果最後個參數爲NULL和0,GetFontData函數返回字體文件或表的大小;就會把到的數據拷貝到應用程序所提供的緩衝區中。
下面的例和查詢整個TrueType字體的原始數據:
  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使得應用程序能夠在自己的文檔中內嵌TrueType字體,以確保這些文檔能在沒有相應字體的其他機器上顯示。它的做法是允許應用程序查詢字體數據,然後寫入到文檔中作爲文檔的一部分,在文檔被打於時再安裝該字體以確保文檔能以創建時同樣的方式顯示。比如,Windows   NT/2000的假脫機程序在打印到遠端服務器時會在假脫機文件中內嵌入TrueType字體以保證文檔能在另一臺機器上正確地打印。
一旦接受到TrueType字體的原始數據,它的頭中的TableDirectory結構很容易分析。需要檢查的只有版本號和表的數目,然後就可以檢查單個的表。我們來看一些重要的和有趣的表。
1.字體頭
    字體頭表(head表)中包含了TrueType字體的全局信息。下面是字體頭表的結構。
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;

        字體的歷史記錄在三個字段中:字全版本號、字體最初創建時間和字體最後修改時間。有8   個字節用於記錄時間戳,記錄的是從1904年1月1日午夜12:00開始的秒數,因此我們不用擔心y2k問題,或是什麼y2m問題。
        字體設計時是針對一個參考網格設計的,該網格被稱爲em-square,字體中的圖元用網格中的座標表示。因此em-squrare的大小決定胃該字體的圖元被縮放的方式,同時也反映胃該字體的質量。字體頭中保存了每個em-square的格數和能   包含所有圖元的邊界框。Em-square的有效值是從16到16384,常見的值是2048、4096和8192。比如,Windings字體的em-square的格數是2048,圖元的邊界框是[0,-432,2783,1841]。
        字體頭表中的其他信息包括最小可讀像素大小、字體方向、在位置表中圖元索引的格式和圖元數據格式等等。
最大需求表
        TrueType字體是一種非常靈活的數據結構,它可以包含可變數目的圖元,每個圖元可以有不同數目的控制點,甚至還可以有數量可變的圖元指令。最大需求表的目的是告知字體柵格器(rasterizer)對內存的需求,以便   在出來字體前分配合適大小的內存。因爲性能對字體柵格器非常重要,像MFC的CAarray那樣需要頻繁進行數據拷貝操作的動態增長的數據結構不合要求。下面是maxp表的結構。
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字段保存了字體中圖元的總數,這決定了到位置表的圖元索引的數量,可以用於嚴正圖元索引的有效性。TrueType字體中的每個圖元都可以是合成圖元或簡單圖元。簡單圖元可以有一條或多大體上輪廓中國,條用一些控制點定義。合成圖元用幾個其他圖元的組合來定義。maxPoints\maxCountors\maxCompositePoints   maxCompositeContours這幾個字段說明了圖元定義的複雜度。
        除了圖元的定義,TrueType字體還使用了圖元指令用於提示字體掃描器如何對控制點進行調整以得到更均衡更漂亮的光柵化後的圖元。圖元指令也可以出現在字體程序表(fpgm表)以及控制值程序表(“prep”)的全局字體層中。TrueType圖元指令是一個僞計算機字節指令,該機類似於Java的虛擬機,這些指令可以用堆棧計算機執行。MaxStackElements   maxSizeOfInstructions兩個字段同志堆棧計算機這些指令的複雜度。
        以Windings字體爲例,該字體有226個圖元,圖元最多有47條輪廓線,簡單圖元最多有268個點,合成圖元最多有141個點,合成圖元最多有14條輪廓線,最壞情況下需要492層堆棧,最長的指令有1119個字節。
字符到圖元索引的映射表(cmap表)定義了從不同代碼頁中的字符   代碼到圖元索引的映射關係,這是在TrueType字體中存取圖元信息的關鍵。cmap表包含幾個了表以支持不同的平臺和不同的字符編碼方案。

下面是cmap表的結構。

  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字符集中的字符的一個子集。這些字符可以被分組爲多個區域,cmap映射表中就是這麼做的。GetFontUnicodeRanges函數在一個GLYPHSET結構中返回支持的圖元的數量、支持的UNICODE區域的數量以及設備上下文中字體的這些區域的詳細信息。GLYPHSET是一個可變長的結構   ,其大小取決於所支持的UNICODE區域的數量。因此,和Win32   API中支持可變長結構一樣,       GetFontUnicodeRanges函數通常需要調用兩   次。第一次調用時得到以NULL指針作爲最後一莜參數,GDI會返回所需窨的大小。調用者然後分配所需的內存,再次調用以得到真正的數據。這兩   種情況下,GetFontUnicodeRanges函數都會返回保存整個結構所需的數據大小。MSDN文檔可能還是錯誤地描述成了如果第二個參數是NULL,GetFontUnicodeRanges函數返回指向GLYPHSET結構的指針。
下面是用於查詢上下文中當前字體GLYPHSET結構的一個簡單函數。
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字體上試着調用GetFontUnicodeRanges函數,你會發現這些字體通常支持1000個以上的圖元,這些圖元被分成幾百個UNICODE區域。比如,“Times   New   Roman”有我143個圖元,分佈在145個區域中,和一個區域是0x20到0x7f,即可打印的7位ASCII代碼區域。
GetFontUnicodeRanges函數只使用了TrueType字體“cmap”表的一部分部分信息,即從UNICODE到圖元索引的映射域。GetGlyphIndices函數則能真正使用這些映射關係把一個字符串轉換爲一個圖元索引的數組。它接收一個設備上下文句柄、一個字符串指針、字符串長度、一個WORD數組的指針和一個標誌。生成的圖元索引將保存在WORD數組中。如果標誌爲GGI_MASK_NONEXISTING_GLYPHS,找不到的字符的圖元索引會被標註成0xFFFF。此函數得到的圖元索引可以傳給其他GDI函數,如ExtTextOut函數。

2.位置索引

        TrueType字體中最有用的信息是glyf表中的圖元數據。有了圖元索引,要找到相應的圖元,需要表(loca表)索引以把圖元索引轉換爲圖元數據表內的偏移量。
位置索引表中保存了n+1個圖元數據表的索引,其中n是保存在最大需求表中的圖元數量。最後一個額外   的偏移量並不指向一個新圖元,而是指向最後一個圖元的偏移量和當前圖元的偏移量和當前圖元的偏移量間的差值得到圖元的長度。
位置索引表中的每一個索引以無符號短整數對齊的,如果使用了短整數格式,索引表實際存儲的是WORD偏移量,而不是BYTE偏移量。這合得短整數格式的位置索引表能   支持128KB大小的圖元數據表。

3.圖元數據
      圖元數據(glyf表)是TrueType字體的核心信息,因此通常它是最大的表。因爲的位置索引是一張單獨的表,圖元數據表就完全只是圖元的序列而已,每個圖元以圖元頭結構開始:
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;

        對於簡單圖元,numberOfContours字段中保存的是當前圖元的輪廓線的樹木;對於合成圖元,numberOfContours字段是一個負值。後者的輪廓線的總數必須基於組成該合成圖元的所有圖元的數據計算得到。GlyphHeader結構中後四個字段記錄了圖元的邊界框。
      對於簡單圖元,圖元的描述緊跟在GlyphHeader結構之後。圖元的描述由幾部分信息組成:所有輪廓線結束點的索引、圖元指令和一系列的控制點。每個控制點包括一個標誌以x和y座標。概念上而言,控制所需的信息和GDI函數PolyDraw函數所需的信息相同:一組標誌和一組點的座標。但TrueType字體中的控制點的編碼要複雜得多。下面是圖元描述信息的概述:
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"有兩   條輪廓線,一條是內部的輪廓,另一條是外部的輪廓。對於每一條輪廓線,endPtsOfContours數組保存了其終點的索引,從該索引中可以計算出輪廓線中點的數量。比如,endPtsOfContours[0]是第一休輪廓線上點的數量,endPtsOfContours[1]-endPtsOfContours[0]是第二條輪廓線上點的數量。
        終點數組後是圖元指令通知度和圖元指令數組。我們先跳過它們,先來討論冬至點。圖元的控制點保存在三個數組中:標誌獲得組、x座標數組和y座標數組。找到標誌數組的起始點很簡單,但是標誌數組沒有相應的長度字,也沒有直接其他兩個數組的方法,你必須先解碼標誌數組才能解釋x和y座標數組。
        我們提到棕em-square被限制爲最大爲16384個網格,因此通常情況下需要各兩個字節來表示x座標和y座標。爲了節省空間,圖元中保存的是相對座標。第一個點的座標是相對(0,0)記錄的,所有隨後的點記錄者是和上一個點的座標差值。有些差值可以用一個字節表示,有些差值爲0,另外一些差值則無法用耽擱字節表示。標誌數組保存了每個座標的編碼信息以及其他一些信息。下面是標誌中各個位的含義的總結:

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曲線有四個控制點定義:位於曲線上(on-curve)的起始點、兩個不在曲線上(off-curve)的控制點和一個曲線上的結束點。TureType字體中的圖元輪廓是用二階Bezier曲線定義的,有三個點:一個曲線上的點,一個曲線外的點和另一個曲線上的點。多個連續的不在曲線上的點是允許的,但不是用來定義三階或更高階的Bezier曲線,而是爲了減少控制點的數目。比如,對於on-off-off-on模式的四個點,會加入一個隱含的點使之成爲on-off-on-off-on,因此定義的是兩段二階Bezier曲線。
        如果設置了G_ONCURVE位,那麼控制點在曲線上,否則不在曲線上。如果設置了G_REPEAT,標誌數組中的下一字節表示重複次數,當前標誌應該重複指定的次數。因此,標誌數組中實際使用了某種類型的行程編碼。標誌中的其他位用於描述相應   的x座標和y座標的編碼方式,它們可以表示當前相尋座標是否和上一個相同、正的單字節值、負的單字節值或有符號兩字節值。
        解碼圖元的描述是一個兩次掃描的起始點。然後再遍歷圖元定義中的每一個點把它轉換爲更容易管理的格式。程序清單14-2列出瞭解碼TrueType圖元的函數,它是KTrueType類的一個方法。

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給出圖元索引和可選的變換矩陣,處理的是單個圖元的解碼。參數curve是KCurve類,用於把TrueType圖元定義保存爲32位的點的贖罪以及一個標誌數組,以梗用GDI進行顯示。這些代碼可以作爲簡單TrueType字體編輯器的基礎。
        代碼中調用了GetGlyph方法,該方法用位置表索引找到該圖元的GlyphHeader結構。從中得到圖元的輪廓線數目。注意必須反轉該值的字節序,因爲TrueType字體用的是Big-Endian字節序。如果該值爲負值,說明這是一個合成圖元,應該轉而調用DecodeCompositeGlyph方法。接下支的代碼定位了endPtsOfContours數組,找出點的總數,然後跳過指令找到標誌數組的起始位置。
        接下去需要長到的是x座標數組的始位置和長度,這需要遍歷標誌數組一次。對於每一個控制點,它在x座標數組中所佔空間可能爲0到2個字節,這取決於它的相對座標是0、單個字節還是兩個字節。
        根據x座標數組的地址和長度可以得到y座標的地址。接下去的代碼遍歷所有的輪廓線,解碼其中的控制點,把相對座標轉換爲絕對座標,然後把它加入到曲線對象中。如果需要的話,會對每個控制點做變換。
回想一下,TrueType使用的是二階Bezier曲線,允許在兩個曲線上的點之間有多個不在曲線上的點。爲了簡化曲線繪製算法,KCurve::Add方法在每兩個不在曲線上的點之間加入一個額外的在曲線上的點。

        處理了簡單圖元之後,我們來看看合成圖元。合成圖元用一個經變換的圖元序列定義。每個經變換的圖元的定義包括三個部分:一個標誌、一個圖元索引和一個變換矩陣。標誌字段決定了變換矩陣的編碼方式。編碼的目的也是爲了節省一些空間,加外還說明了是否已到達序列的終點。一個完整的2D   affine變換需要6個值。但如果只是平移的話,只需要兩個值(dx,dy),這兩個值可以保存爲兩個字節或兩個字。如果x和y以相同的值縮放,加外還需要一個縮放值。取一般的情況下仍然需要6個值,但是很多時候可以節省幾個字節。用於變換的值以2.14的有符號定點格式保存,dx和dy值除外,這兩個值以整數形式保存。得到合成圖元的過程實際上是變換和組合幾個圖元的過程。比如,如果字體中的一個圖元是另一個圖元的精確鏡像,它只需定義爲一個合成圖元,可以通過對另一個圖像做鏡像變換即可。程序清單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方法解碼每個圖元的標誌、圖元索引和變換矩陣,然後調用DecodeGlypgh方法進行解碼。注意,對DecodeGlyph方法的調用包含一個有效的變換矩陣參數。當MORE_COMPONENTS標誌結束時,該方法隨之結束。隨書光盤中有該方法完整的源代碼。
        解碼後的TrueType字體的圖元要用GDI繪製還有一個小問題需要處理。GDI只繪製三階Bezier曲線,因此從圖元表解碼所得的二階Bezier曲線的控制點需要轉換爲三階Bezier曲線的控制點。通過對Bezier曲線原始數學定義的研究,可以得到如下用GDI繪製二階Bezier曲線的簡單例程。

//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);
}

        對於用三個控制點(p0,p1,p2)定義的二階Bezier曲線,相應的三階Bezier曲線的控制點爲(p0,(p0+2*p1)/3,(2*p1+p2)/3,p2)。


4.圖元指令
        程序清單14-2和14-3給人的印象是TrueType字體的柵格器可以通過掃描和轉換圖元的輪廓來輕鬆地實現,比如,用GDI和StrokeAndFillPath函數來填充圖元輪廓繪製出來的路徑。這種簡單的字體柵格器的實現並不是很有用,除非它只用於高分辨詣的設備如打印機等。
        簡單柵格器得到的圖像筆畫粗細不一,有信息的遺漏,有字符特徵的損失以及不對稱等缺陷。當點陣變小是,情況不會更糟。總之,簡單字體柵格器在小尺寸時會產生字跡模糊的結果。在大尺寸時會產生不好看的結果,只有在點陣增大時結果纔會改善。
        當在大的em-square(典型的是2048)中定義的圖元輪廓縮小爲小得多的網格時(如32*32),不可避免會損失精度並引入誤差。

        TrueType解決這個問題的方法是控制圖元輪廓從em-square到柵格網格的縮放過程,使得到的結果看起來效果更好,和原始圖元的設計儘量接近。這種技術被稱爲網格調整(grid   fitting),它想達到的目標有:
        消除網格位置的可能影響,保證筆畫的粗細和網格的相對位置無關。
        控制圖元中關鍵位置的尺寸
        保持對稱性和襯線等   重要的圖元設計細節。
    
          TrueType字體中網格調整的需求在兩個地方中編碼:控制值表(control   value   table)和每個圖元的網格調整指令。
        控制值表("cvt"表)用於保存一個數組,這些值被稱爲網格調整指令。比如,對於有襯線的字體,基線、襯線高度、大寫字母筆劃的寬度等值都或以是被控制的值。它們可以以字體設計者已知的次序保存在控制值表中,然後用它們的索引來引用。在字體光柵化過程中,控制值表中的值根據點陣的大小縮放。在網絡調整指令中引用這些值   可以保證使用的值與網枸的位置無關。比如,如果水平線[14,0,25,200]可以用CVT表中的兩個值定義爲[14,0,14+CVT[stem_width],0+CVT[cap_height]],那     麼該線的寬度和高度會和所在網格的相對位置無關,保持不變。
        每一個圖元的定義中附加有一個指令序列,該指令序列被稱爲圖元指令,該背景令序列用於控制此圖元的網格高速。圖元指令線用控制值表中的值,以保證在索引圖元中這些值相同。
        圖元指令是一種基於堆棧的僞計算機的指令。堆棧計算機常用於計算機語言的解釋性實現。比如,Forth(用於嵌入式系統的一種強大而簡潔的語言)、RPL(HP計算器使用的語言)和Java虛擬機都是堆棧計算機。
        堆棧計算機通常沒有寄存器,所有的計算都在堆棧上進行(有些堆棧計算機使用分開的控制堆棧和數據堆棧)。比如,壓入指令把一個值壓入堆棧,彈出指令從堆棧中彈出上面的值,二元加法指令彈出上面的兩   個值   ,然後把它們的和壓入堆棧。
TrueType虛擬機不 


http://bbs.csdn.net/topics/210058429

http://bbs.csdn.net/topics/100190506

http://bbs.csdn.net/topics/60467577

http://blog.csdn.net/zixue_pudn/article/details/5202259

http://bbs.csdn.net/topics/300012380

http://bbs.csdn.net/topics/370067374

http://blog.csdn.net/avan_lau/article/details/6958497

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章