Win32 API
GetGlyphOutline 函數可以傳回原生的圖像 (Glyph) 大綱資料從 TrueType 字型。若要繪製 TrueType 圖像 (Glyph) 外框,資料必須轉換從原生的 B 曲線定義成一連串的貝茲行定義。然後
PolyBezier Win32 API 函式可用來繪製外框。
在 Win32 應用程式發展 Interface(API)
GetGlyphOutline 函式可擷取 TrueType 大綱。GGO_NATIVE 格式選項會填滿與 TrueType 大綱的二次方的 B 曲線曲線緩衝區。二次方的 B 曲線是由 TrueType 用來描述圖像 (Glyph) 大綱 TrueType 字型檔案中。若要繪製這些大綱曲線其中一個可以實作 B 曲線繪圖函式,或者使用
PolyBezier 函數從 Win32 API。
二次方曲線 B 曲線是 parametric 定義多個曲線的線段透過幾個控制點路徑的曲線的類別。二次方曲線是第二個的順序 parametric 曲線。API 來直接繪製二次方曲線中沒有任何的功能,但如果 [Quadratic 會轉換成使用 Win32 API 來繪製一個立方繪製貝茲曲線的功能 ; 呼叫
PolyBezier。
在特定和 parametric 曲線的二次方 B 曲線曲線一般而言會 well-researched 主題在電腦科學領域中的圖形。它們也可以是相當複雜。各種文字中已發佈的演算法,可用來實作函式來繪製二次方曲線,但描述這類的演算法已超出本文範圍。
因為貝茲曲線是三次方或第三個順序 parametric 曲線
PolyBezier 函式可以繪製二次方曲線。因為二次方曲線第二個順序方程式可以表示較高的順序立方方程式的角度。雖然表示為一個立方 quadratic 方程式提供範例程式碼中,不是本文中討論其衍生。
範例程式碼,在本文中是如何剖析
GetGlyphOutline 函數所傳回的 GGO_NATIVE 圖像 (Glyph) 緩衝區的示範。
TTPOLYGONHEADER 結構符合 GGO_NATIVE 格式旗標所傳回的緩衝區。
TTPOLYGONHEADER 結構與緊接它的資料會構成一個分佈的 TrueType 圖像 (Glyph)。分佈是一個完整路徑的曲線,如果它不明確地傳回如此一來隱含地關閉。
請在
TTPOLYGONHEADER 和
TTPOLYCURVE 結構上,參閱平台 SDK 的文件。
圖像 (Glyph) 分佈是由多個曲線的線段,由
TTPOLYCURVE 結構代表所組成。在一個分佈
TTPOLYGONHEADER 跟著一或多個
TTPOLYCURVE 結構和座標點的資料。
pfxStart 成員提供起始座標點的作業分佈。
cb 成員會提供遵循
TTPOLYGONHEADER 的曲線記錄計數。起始點是相對於圖像原點。圖像 (Glyph) 的來源是在字元的比較基準字元的左下角。
每個
TTPOLYCURVE 曲線記錄 (曲線的線段) 是由二次方的 B 曲線的控制點或簡單的聚合線條點所組成。由
TTPOLYCURVEcpfx 成員所提供的點計數。曲線] 或 [多線條的曲線類型是由
wType 成員所提供。座標點陣列緊接結構。曲線的起始點是由
apfx 成員所提供。
因為圖像 (Glyph) 可能包含一個以上的作業分佈,緩衝區包含一或多個
TTPOLYGONHEADER 結構與資料群組。後續的作業分佈是緊接在原生緩衝區中前一個封裝中。在下一個位元組超過前一個曲線記錄的最後一個時間點的
TTPOLYGONHEADER 開頭下一個作業分佈。
這個範例程式碼剖析 GGO_NATIVE 緩衝區以建立一份貝茲線段構成每個作業分佈繪製 TrueType 圖像 (Glyph) 的外框。貝茲線段的清單然後使用
PolyBezier 繪製。程式碼剖析緩衝區處於
DrawT2Outline 函式。
若要建立的貝茲行清單第一個步驟是緩衝區的判斷清單大小。四個點定義貝茲線條。
PolyBezier 函式會解譯為連續一行的下一行的開頭是同一與前一行端點的貝茲線段的點的陣列。因此,只有三個點所需說明其他的貝茲線段。
GGO_NATIVE 緩衝區可能包含聚合線條曲線或二次方的 B 曲線曲線。兩個點定義直線線段,但三個點定義 B 曲線。由於每個型別是以使用 PolyBezier 貝茲線條繪製,糟的狀況會發生於一個是完全由直線線段的分佈展開以多個 Beziers。
請注意來表達額外的曲線線段 B 曲線曲線表示中的需要只能有一個額外的點。該點明確定義 「 B 」 或關閉曲線點並隱含定義曲線點中的 [在其他。在曲線點是到下一個"B"點中間點。因此貝茲線條的三個額外的點將會展開額外的線段或額外的曲線線段。
程式碼假設整個的原生緩衝區會包含
POINTFX 結構的它可以判斷數量最多的緩衝區所代表的點。實際的編號是稍微變小,因為結構負荷會在描述分佈和曲線。貝茲清單的緩衝區大小是由點乘以
指向 結構的資料大小和乘以三個最大可能數目所提供。三個是其他所需的每個額外的線條或二次方曲線區段轉換成一個貝茲時的點數目。
一旦貝茲緩衝區配置程式碼會由起始於第一個
TTPOLYGONHEADER 剖析原生的緩衝區。迴圈的反覆項目期間程式碼會處理一個作業分佈,並將它繪製。第二的內部迴圈會剖析每一種曲線類型。
聚合線條曲線 (TT_PRIM_LINE) 的情況下每個線段的多線條是轉換,而且附加至清單 Beziers
AppendPolyLineToBezier 函式。如果該曲線一條二次方曲線 B (TT_PRIM_QSPLINE)
AppendQuadBSplineToBezier 函式轉換,並將附加至二次方的 B 曲線。這些函式的每個使用迴圈來剖析出曲線的每個段落為其獨立的控制點。然後線條或曲線轉換成一個貝茲,附加到 Beziers 清單。
貝茲一行可以輕易地繪製直線。開始和結束的向量只需點的直線線段相反的結尾。這是
MakeBezierFromLine 函式中。
若要將二次方曲線轉換成三次方貝茲曲線,需要表達 quadratic 的控點的角度的立方的控點。平移控點方程式] 位於 [
MakeBezierFromQBSpline 函式。
在前繪圖分佈,程式碼會確認路徑已關閉藉由呼叫
CloseContour 函式。只有如果開始和結束點 Beziers 序列的不同一,會呼叫此函式。然後就會呼叫
PolyBezier 函式。
作業分佈繪製之後是透過前進 lpHeader 指標中目前的作業分佈記錄結尾之外找到下一個分佈
TTPOLYGONHEADER。如果這會導致指標值,原生緩衝區結尾之外,程式碼已處理所有的作業分佈和結束。
/****************************************************************************
* FUNCTION : IntFromFixed
* RETURNS : int value approximating the FIXED value.
****************************************************************************/
int PASCAL NEAR IntFromFixed(FIXED f)
{
if (f.fract >= 0x8000)
return(f.value + 1);
else
return(f.value);
}
/****************************************************************************
* FUNCTION : fxDiv2
* RETURNS : (val1 + val2)/2 for FIXED values
****************************************************************************/
FIXED PASCAL NEAR fxDiv2(FIXED fxVal1, FIXED fxVal2)
{
long l;
l = (*((long far *)&(fxVal1)) + *((long far *)&(fxVal2)))/2;
return(*(FIXED *)&l);
}
/****************************************************************************
* FUNCTION : MakeBezierFromLine
*
* PURPOSE : Converts a line define by two points to a four point Bezier
* spline representation of the line in pPts.
*
*
* RETURNS : number of Bezier points placed into the pPts POINT array.
****************************************************************************/
UINT MakeBezierFromLine( POINT *pPts, POINT startpt, POINT endpt )
{
UINT cTotal = 0;
// starting point of Bezier
pPts[cTotal] = startpt;
cTotal++;
// 1rst Control, pt == endpoint makes Bezier a line
pPts[cTotal].x = endpt.x;
pPts[cTotal].y = endpt.y;
cTotal++;
// 2nd Control, pt == startpoint makes Bezier a line
pPts[cTotal].x = startpt.x;
pPts[cTotal].y = startpt.y;
cTotal++;
// ending point of Bezier
pPts[cTotal] = endpt;
cTotal++;
return cTotal;
}
/****************************************************************************
* FUNCTION : MakeBezierFromQBSpline
*
* PURPOSE : Converts a quadratic spline in pSline to a four point Bezier
* spline in pPts.
*
*
* RETURNS : number of Bezier points placed into the pPts POINT array.
****************************************************************************/
UINT MakeBezierFromQBSpline( POINT *pPts, POINTFX *pSpline )
{
POINT P0, // Quadratic on curve start point
P1, // Quadratic control point
P2; // Quadratic on curve end point
UINT cTotal = 0;
// Convert the Quadratic points to integer
P0.x = IntFromFixed( pSpline[0].x );
P0.y = IntFromFixed( pSpline[0].y );
P1.x = IntFromFixed( pSpline[1].x );
P1.y = IntFromFixed( pSpline[1].y );
P2.x = IntFromFixed( pSpline[2].x );
P2.y = IntFromFixed( pSpline[2].y );
// conversion of a quadratic to a cubic
// Cubic P0 is the on curve start point
pPts[cTotal] = P0;
cTotal++;
// Cubic P1 in terms of Quadratic P0 and P1
pPts[cTotal].x = P0.x + 2*(P1.x - P0.x)/3;
pPts[cTotal].y = P0.y + 2*(P1.y - P0.y)/3;
cTotal++;
// Cubic P2 in terms of Qudartic P1 and P2
pPts[cTotal].x = P1.x + 1*(P2.x - P1.x)/3;
pPts[cTotal].y = P1.y + 1*(P2.y - P1.y)/3;
cTotal++;
// Cubic P3 is the on curve end point
pPts[cTotal] = P2;
cTotal++;
return cTotal;
}
/****************************************************************************
* FUNCTION : AppendPolyLineToBezier
*
* PURPOSE : Converts line segments into their Bezier point
* representation and appends them to a list of Bezier points.
*
* WARNING - The array must have at least one valid
* start point prior to the address of the element passed.
*
* RETURNS : number of Bezier points added to the POINT array.
****************************************************************************/
UINT AppendPolyLineToBezier( LPPOINT pt, POINTFX start, LPTTPOLYCURVE lpCurve )
{
int i;
UINT cTotal = 0;
POINT endpt;
POINT startpt;
POINT bezier[4];
endpt.x = IntFromFixed(start.x);
endpt.y = IntFromFixed(start.y);
for (i = 0; i < lpCurve->cpfx; i++)
{
// define the line segment
startpt = endpt;
endpt.x = IntFromFixed(lpCurve->apfx[i].x);
endpt.y = IntFromFixed(lpCurve->apfx[i].y);
// convert a line to a bezier representation
MakeBezierFromLine( bezier, startpt, endpt );
// append the Bezier to the existing ones
// Point 0 is Point 3 of previous.
pt[cTotal++] = bezier[1]; // Point 1
pt[cTotal++] = bezier[2]; // Point 2
pt[cTotal++] = bezier[3]; // Point 3
}
return cTotal;
}
/****************************************************************************
* FUNCTION : AppendQuadBSplineToBezier
*
* PURPOSE : Converts Quadratic spline segments into their Bezier point
* representation and appends them to a list of Bezier points.
*
* WARNING - The array must have at least one valid
* start point prior to the address of the element passed.
*
* RETURNS : number of Bezier points added to the POINT array.
****************************************************************************/
UINT AppendQuadBSplineToBezier( LPPOINT pt, POINTFX start, LPTTPOLYCURVE lpCurve )
{
WORD i;
UINT cTotal = 0;
POINTFX spline[3]; // a Quadratic is defined by 3 points
POINT bezier[4]; // a Cubic by 4
// The initial A point is on the curve.
spline[0] = start;
for (i = 0; i < lpCurve->cpfx;)
{
// The B point.
spline[1] = lpCurve->apfx[i++];
// Calculate the C point.
if (i == (lpCurve->cpfx - 1))
{
// The last C point is described explicitly
// i.e. it is on the curve.
spline[2] = lpCurve->apfx[i++];
}
else
{
// C is midpoint between B and next B point
// because that is the on curve point of
// a Quadratic B-Spline.
spline[2].x = fxDiv2(
lpCurve->apfx[i-1].x,
lpCurve->apfx[i].x
);
spline[2].y = fxDiv2(
lpCurve->apfx[i-1].y,
lpCurve->apfx[i].y
);
}
// convert the Q Spline to a Bezier
MakeBezierFromQBSpline( bezier, spline );
// append the Bezier to the existing ones
// Point 0 is Point 3 of previous.
pt[cTotal++] = bezier[1]; // Point 1
pt[cTotal++] = bezier[2]; // Point 2
pt[cTotal++] = bezier[3]; // Point 3
// New A point for next slice of spline is the
// on curve C point of this B-Spline
spline[0] = spline[2];
}
return cTotal;
}
/****************************************************************************
* FUNCTION : CloseContour
*
* PURPOSE : Adds a bezier line to close the circuit defined in pt.
*
*
* RETURNS : number of points aded to the pt POINT array.
****************************************************************************/
UINT CloseContour( LPPOINT pt, UINT cTotal )
{
POINT endpt,
startpt; // definition of a line
POINT bezier[4];
// connect the first and last points by a line segment
startpt = pt[cTotal-1];
endpt = pt[0];
// convert a line to a bezier representation
MakeBezierFromLine( bezier, startpt, endpt );
// append the Bezier to the existing ones
// Point 0 is Point 3 of previous.
pt[cTotal++] = bezier[1]; // Point 1
pt[cTotal++] = bezier[2]; // Point 2
pt[cTotal++] = bezier[3]; // Point 3
return 3;
}
/****************************************************************************
* FUNCTION : DrawT2Outline
*
* PURPOSE : Decode the GGO_NATIVE outline, create a sequence of Beziers
* for each contour, draw with PolyBezier. Color and relative
* positioning provided by caller. The coordinates of hDC are
* assumed to have MM_TEXT orientation.
*
* The outline data is not scaled. To draw a glyph unhinted
* the caller should create the font at its EMSquare size
* and retrieve the outline data. Then setup a mapping mode
* prior to calling this function.
*
* RETURNS : none.
****************************************************************************/
void DrawT2Outline(HDC hDC, LPTTPOLYGONHEADER lpHeader, DWORD size)
{
WORD i;
UINT cTotal = 0; // Total points in a contour.
LPTTPOLYGONHEADER lpStart; // the start of the buffer
LPTTPOLYCURVE lpCurve; // the current curve of a contour
LPPOINT pt; // the bezier buffer
POINTFX ptStart; // The starting point of a curve
DWORD dwMaxPts = size/size of(POINTFX); // max possible pts.
DWORD dwBuffSize;
dwBuffSize = dwMaxPts * // Maximum possible # of contour points.
sizeof(POINT) * // sizeof buffer element
3; // Worst case multiplier of one additional point
// of line expanding to three points of a bezier
lpStart = lpHeader;
pt = (LPPOINT)malloc( dwBuffSize );
// Loop until we have processed the entire buffer of contours.
// The buffer may contain one or more contours that begin with
// a TTPOLYGONHEADER. We have them all when we the end of the buffer.
while ((DWORD)lpHeader < (DWORD)(((LPSTR)lpStart) + size) && pt != NULL)
{
if (lpHeader->dwType == TT_POLYGON_TYPE)
// Draw each coutour, currently this is the only valid
// type of contour.
{
// Convert the starting point. It is an on curve point.
// All other points are continuous from the "last"
// point of the contour. Thus the start point the next
// bezier is always pt[cTotal-1] - the last point of the
// previous bezier. See PolyBezier.
cTotal = 1;
pt[0].x = IntFromFixed(lpHeader->pfxStart.x);
pt[0].y = IntFromFixed(lpHeader->pfxStart.y);
// Get to first curve of contour -
// it starts at the next byte beyond header
lpCurve = (LPTTPOLYCURVE) (lpHeader + 1);
// Walk this contour and process each curve( or line ) segment
// and add it to the Beziers
while ((DWORD)lpCurve < (DWORD)(((LPSTR)lpHeader) + lpHeader->cb))
{
//**********************************************
// Format assumption:
// The bytes immediately preceding a POLYCURVE
// structure contain a valid POINTFX.
//
// If this is first curve, this points to the
// pfxStart of the POLYGONHEADER.
// Otherwise, this points to the last point of
// the previous POLYCURVE.
//
// In either case, this is representative of the
// previous curve's last point.
//**********************************************
ptStart = *(LPPOINTFX)((LPSTR)lpCurve - sizeof(POINTFX));
if (lpCurve->wType == TT_PRIM_LINE)
{
// convert the line segments to Bezier segments
cTotal += AppendPolyLineToBezier( &pt[cTotal], ptStart, lpCurve );
i = lpCurve->cpfx;
}
else if (lpCurve->wType == TT_PRIM_QSPLINE)
{
// Decode each Quadratic B-Spline segment, convert to bezier,
// and append to the Bezier segments
cTotal += AppendQuadBSplineToBezier( &pt[cTotal], ptStart, lpCurve );
i = lpCurve->cpfx;
}
else
// Oops! A POLYCURVE format we don't understand.
; // error, error, error
// Move on to next curve in the contour.
lpCurve = (LPTTPOLYCURVE)&(lpCurve->apfx[i]);
}
// Add points to close the contour.
// All contours are implied closed by TrueType definition.
// Depending on the specific font and glyph being used, these
// may not always be needed.
if ( pt[cTotal-1].x != pt[0].x || pt[cTotal-1].y != pt[0].y )
{
cTotal += CloseContour( pt, cTotal );
}
// flip coordinates to get glyph right side up (Windows coordinates)
// TT native coordiantes are zero originate at lower-left.
// Windows MM_TEXT are zero originate at upper-left.
for (i = 0; i < cTotal; i++)
pt[i].y = 0 - pt[i].y;
// Draw the contour
PolyBezier( hDC, pt, cTotal );
}
else
// Bad, bail, must have a bogus buffer.
break; // error, error, error
// Move on to next Contour.
// Its header starts immediate after this contour
lpHeader = (LPTTPOLYGONHEADER)(((LPSTR)lpHeader) + lpHeader->cb);
}
free( pt );
}