Advanced TrueType: GetGlyphOutline

Ron Gery
Microsoft Developer Network Technology Group

Created: July 10, 1992

Abstract

This article presents a usable explanation of the GetGlyphOutline function. Rather than being a complete guide, it is intended to complement and complete the definition found in the MicrosoftÒ WindowsÔ version 3.1 Software Development Kit (SDK) Programmer's Reference, Volume 2: Functions. To follow the discussion, the reader should be familiar with the information on GetGlyphOutline in the SDK and the basic theory of the TrueTypeÒ font engine. A sample application, GLYPH, is included on this compact disc to illustrate the basic concepts and mechanisms in this article.

Introduction

The GetGlyphOutline function allows an application to extract the hinted outline or bitmap of the glyphs of a TrueTypeÒ font. (A glyph is the image of a single character in the font.) The outline information is especially useful when an application wants to create special effects based on characters in a font. Applications simply using TextOut for text output do not need this outline data.

The GetGlyphOutline function is usually called in a two-step process. When the lpBuffer parameter is set to NULL, the function returns the size of the buffer needed to retrieve the outline data. This size can then be used by the application to allocate the buffer before calling the function to actually return the information.

The application has a choice of two formats: GGO_BITMAP for a monochrome bitmap containing the glyph and GGO_NATIVE for the native TrueType outline data that is used to draw the glyph. The application also specifies a transformation matrix that is used by the TrueType engine to transform the glyph before returning it to the application.

The application chooses the font for which this data is returned by selecting the font into the hDC parameter. The GetGlyphOutline function fails if a non-TrueType font is selected.

The rest of this article is concerned with the details of setting the transformation matrix and properly interpreting and aligning the returned glyph data. You can find more information about font selection, TrueType, and the specifics of the GetGlyphOutline function interface in the MicrosoftÒ WindowsÔ Software Development Kit (SDK) and other technical articles on this compact disc.

The Matrix

The application must always provide a valid transformation matrix in the lpMat2 parameter. This matrix allows the application to transform the glyph to achieve such effects as scaling, rotating, and shearing. Any decent computer graphics book should contain a good discussion of matrix transformations: how to build them and how to use them. The following is a brief review of the main methodologies.

The transformation performed by the TrueType engine is as follows:

The simplest matrix is the identity matrix, which leaves the coordinates the same and is used to get an unmanipulated glyph: .

Not much more exciting is the scaling matrix, which can be used to scale independently in x and y. This is not especially useful with GetGlyphOutline because a larger or smaller glyph can be generated in a more typographically correct fashion by using a larger or smaller version of the font. The scaling matrix is used to scale as follows: and .

The rotation matrix is used to rotate by the angle . The TrueType rasterizer rotates about each glyph's cell origin, which is located on the left edge of the cell, at the font's baseline (more on the cell origin below). Because the rotation is based on TrueType's coordinate system (with positive y going up), the rotation appears to be a counterclockwise rotation from the vertical in the Windows-based coordinate system.

The final type of transformation is shearing, as defined by the matrix. The transformation defined by this matrix is as follows: and . Notice that if one of g or h is set to 0, the shearing only affects one of the coordinates. Shearing can be used to simulate italics; for example, the matrix roughly approximates the graphics device interface's (GDI's) italic simulation for raster fonts by shearing the glyph by 50 percent of its height. (This shear is a bit severe for more typographically accepted italics.)

GDI passes the TrueType engine a matrix calculated as (M\sgmlansi183D), where M is the application-supplied matrix and D is the matrix used to define the font. The font's defining matrix, which is determined at SelectObject time, is calculated as the matrix product of (rotation)\sgmlansi183(point size)\sgmlansi183(aspect ratio), where the rotation matrix is defined by the font's escapement, the point size (scaling) matrix is defined by the font's height, and the aspect ratio matrix is defined by the destination device's aspect ratio.

Generating FIXED Values

The FIXED structure used by Windows for defining the transformation matrix is not ideal for some uses. One notable problem is representing negative numbers in general and those in the range between 0 and 1 in particular. Numbers in this range are needed for the rotation matrix and for nonextreme shearing matrices. The following C code converts any floating-point (double) number into a FIXED number:

FIXED PASCAL FAR FixedFromDouble(double d)

{

long l;

l = (long) (d * 65536L);

return *(FIXED *)&l;

}

It looks strange, but it works. The key is the way that the FIXED structure actually represents negative values. For example, –0.5 is stored with value = –1 and fract = 32767, and –1.5 is stored with value = –2 and fract = 32767. Using floating-point numbers for all calculations and converting to FIXED only for building the matrix is probably the best way to go.

GGO_BITMAP

Before delving into the bitmap format returned by GetGlyphOutline, it should be noted that the simplest way to get a glyph's bitmap is to use TextOut or ExtTextOut to output the character (or a whole string, if needed) to a bitmap destination. This involves no conversions, no memory management, and no new function hassles.

That having been said, why even have the GGO_BITMAP format in the first place? Because with the GetGlyphOutline function, the application can use the transformation matrix to control the rasterization of the character. This type of control is not available with the text output functions and is the only way that an application can affect the formation of the glyph bitmap. (The rasterizer transforms the glyph's outline before building the bitmap.)

The dimensions of the returned bitmap are found in the gmBlackBoxX (for width) and gmBlackBoxY (for height) fields of the lpgm parameter to the function. The bitmap defines the pure black box of the glyph, and the size of the character cell is given by gmCellIncX and gmCellIncY. More on glyph alignment below in "Glyph Alignment."

The GGO_BITMAP format is a DWORD-aligned monochrome bitmap that is almost, but not exactly, compatible with known Windows-based bitmap formats. Three methods are available for converting this format into a standard Windows-based bitmap:

The DWORD alignment of the bitmap matches the format used by two-color DIBs, so you can create a header for the bits and then use SetDIBits to convert it to a bitmap. The problem with this method is that the bits are upside down from the DIB perspective. The bits are defined with (0,0) in the upper-left corner, but the DIB format has (0,0) in the lower-left. So, for this method to work, the bits must be flipped at some point in the process.

All that really separates the GGO_BITMAP format and the standard Windows-based monochrome bitmap format is that the scanlines are DWORD-aligned instead of following the Windows convention of being WORD-aligned. By compressing the bits into WORD alignment when appropriate (half of the time WORD alignment and DWORD alignment are equivalent), the bits can be made to look exactly like a standard Windows-based bitmap. The CreateBitmap function can then be used to create and initialize a bitmap object. Sample code that demonstrates this conversion can be found in the BitmapFromT2Bitmap function of the GLYPH sample application.

Another way to make the bits palatable to Windows is to create a DWORD-aligned bitmap object. The CreateBitmapIndirect function accepts a full BITMAP structure as a parameter, and the bmWidthBytes field of this structure essentially defines the alignment of the bitmap. Because drivers use the bmWidthBytes value for the width in bytes of a scanline, it provides a simple mechanism for overriding the Windows definition of bitmaps as WORD-aligned. The code below (also found in the BitmapFromT2Bitmap function of the GLYPH sample application) demonstrates this approach:

BITMAP bm;

bm.bmType = 0;

bm.bmWidth = width;

bm.bmHeight = height;

bm.bmWidthBytes = ((width + 31) >> 5) << 2; // DWORD-aligned

// width in bytes.

bm.bmPlanes = bm.bmBitsPixel = 1; // Monochrome bitmap.

bm.bmBits = lpBits; // Set pointer to

// GGO's bits.

hbmGlyphBitmap = CreateBitmapIndirect(&bm);

Because GetGlyphOutline returns a bitmap that is the size of the glyph's black box (the inked part of the glyph), the bitmaps returned vary in height and width between different characters in a font. It is up to the application to manage the size differences.

The returned bitmap has a white glyph (1s) on a black background (0s). The GLYPH sample application simply blts this to the screen to show the entire rectangle. By setting the text and background colors of the destination device context (DC), an application can control the actual display color and can emulate transparent text using simple masking techniques.

GGO_NATIVE

The GGO_NATIVE, or outline, format is quite a bit harder to decode than the bitmap format. The description consists of a set of polygon definitions (TTPOLYGONHEADER), each comprised of a set of curves (TTPOLYCURVE). Each curve is either a polyline or a quadratic spline. The SDK has a pretty good definition of the format, but there are some holes in the description.

Completely omitted from the SDK is the actual definition of the TT_PRIM_QSPLINE record structure and of TrueType's quadratic splines in general. TrueType's quadratic splines are defined by three points (A, B, C), where points A and C are on the curve and point B is off the curve. The equation for the spline itself is:

where t is a real number in the range between 0.0 to 1.0.

Similar to a TT_PRIM_LINE record, a TT_PRIM_QSPLINE record contains an array of two or more points that are read sequentially. Instead of simply drawing lines, though, these points become the A, B, and C points in the above equation. Point A is the current drawing position; this is either the end point of the previous curve or, if this is the first curve, the first point of the polygon as defined by the polygon's pfxStart field. The structure definition permits a simple mechanism for finding this point:

POINTFX pointA;

// Get the current position. This is either the polygon's pfxStart

// or the last point in the previous curve.

pointA = *(LPPOINTFX)((LPSTR)lpCurCurve - sizeof(POINTFX));

Point B is the current point in the POLYCURVE record.

Point C is the next point in the curve record if that point is the last point of the POLYCURVE record. Otherwise, point C is the (calculated) midpoint between point B and the next point in the record. For example, consider the following two cases:

wType = TT_PRIM_QSPLINE

cpfx = 2;

apfx = {2.0,10.0, 3.0,11.0}

This generates one spline:

pointA = previous point, pointB = (2.0,10.0),

pointC = (3.0,11.0)

wType = TT_PRIM_QSPLINE

cpfx = 3;

apfx = {2.0,10.0, 3.0,11.0, 4.0,12.0}

This generates two splines:

pointA = previous point, pointB = (2.0,10.0),

pointC = (2.5,10.5)

and pointA = (2.5,10.5), pointB = (3.0,11.0), pointC = (4.0, 12.0)

As the second case demonstrates, when point C is not the last point in the curve, the curve is broken into a series of splines with connecting endpoints. One spline's point C is the next spline's point A until the endpoint of the curve is reached. The basic logic can be written as follows:

POINTFX pointA, pointB, pointC;

pointA = *(LPPOINTFX)((LPSTR)lpCurve - sizeof(POINTFX));

for (i = 0; i < lpCurve->cpfx;)

{

pointB = lpCurve->apfx[i++]; // Current point in spline

// If last point in curve, it is point C.

if (i == (lpCurve->cpfx - 1))

pointC = lpCurve->apfx[i++];

// Else, calculate point C as the midpoint.

else

{

pointC.x = fxDiv2(pointB.x, lpCurve->apfx[i].x);

pointC.y = fxDiv2(pointB.y, lpCurve->apfx[i].y);

}

DoTheSplineThing(pointA, pointB, pointC, ...);

// Set up start point for next curve piece.

pointA = pointC;

}

Because the numbers used in the outline format use the Windows-defined FIXED format, simple C-based math cannot be used to calculate the midpoint. The following code uses multiple casts to achieve the desired effect:

FIXED fxDiv2(FIXED fxVa1, FIXED fxVal2)

{

long l;

l = (*((long far *)&(fxVal1)) + *((long far *)&(fxVal2)))/2;

return(*(FIXED *)&l);

}

The heart of the matter, of course, is cracking the spline because the final digital representation of the glyph is only as good as the digitization of the curves. This article does not provide the perfect algorithm or, in fact, any real algorithm. (The GLYPH sample application accumulates the A, B, and C points into a polyline.) A workable algorithm is left to the reader to devise. There are some general principles, though, that should be kept in mind:

The TrueType rasterizer uses 10.6 fixed-point math for its computations, so the least significant 10 bits of the fract field of the FIXED structure can be ignored during calculations.

All computations should be maintained in fixed-point format until the last possible moment before being converted to 16-bit integers for use by Windows. This is necessary for better accuracy and to ensure smoothness. Another alternative is to carry over the error term between one curve and the next so that the overall polygon maintains its flow.

With smaller sizes of fonts (read at screen resolution), the font outlines do not always look very good. (That's what TrueType's dropout control is all about.)

The polygon-filling convention used by TrueType differs from the one used by Windows in that it is based on the center of a pixel instead of the upper-left corner of the pixel. To align the two, the polygon must be shifted up and to the left by half a pixel. (This is possible when the points are still in fixed-point notation.)

For most purposes, all of the points of the polygon are accumulated for later use, possibly with one of GDI's polygon-filling functions, Polygon or PolyPolygon. Because glyphs may be described with more than one polygon (take, for example, the letter i ), PolyPolygon is a good choice for generalized use. (This is the approach used in the GLYPH sample application.) Before either of these functions can be used, however, each polygon in the glyph must first be closed with two additional points: the pfxStart point to get to the designated start of the polygon, and the start point of the first curve to close the polygon. Often these two points are the same, but they do not have to be. (The exact ordering of the curves depends on the font's author and does not necessarily start at pfxStart.) The resulting polygon is ready for use by Polygon or PolyPolygon.

Glyph Alignment

The alignment of the returned glyph varies slightly between the two formats, but they share some principles. First, the glyphs are defined based on a coordinate system that has positive y going up, which is upside down from the Windows-based system. Second, each glyph's cell origin, its (0,0), is located on the baseline. This also differs from the Windows-based system that places the cell origin at the upper-left corner of the cell. As a result, there are no negative values in Windows-based glyph definitions, but with TrueType it is possible to have negative x values for glyphs with underhang and negative y values for glyphs that descend below the baseline. The gmptGlyphOrigin field of the GLYPHMETRICS structure (the lpgm parameter) defines how the returned glyph lines up with the cell origin. The following discussion assumes MM_TEXT mapping mode and a text alignment of TA_LEFT | TA_TOP (the default).

A critical component to the vertical computation is the font's ascent. For alignment purposes, it defines the distance from the baseline to the top of the cell. This font-specific (not glyph-specific) value is found in the tmAscent field of the TEXTMETRIC structure returned by the GetTextMetrics and GetOutlineTextMetrics functions.

Depending on what the application is attempting to accomplish, you can position a glyph horizontally either by the black box or by the cell origin. The black box method does not account for overhang and, instead, positions the character based strictly on the bounding rectangle of the glyph. Using the cell origin, on the other hand, positions the glyph as it is intended to be positioned in a text string, with the underhang to the left of the starting point. The standard vertical placement is relative to the font's baseline.

When a GGO_BITMAP format is returned, the bitmap contains only the glyph's black box, so the actual cell must be "recreated" to position the glyph by using the gmptGlyphOrigin point, which defines where the upper-left corner of the bitmap resides relative to the cell origin. To emulate a call to TextOut with an origin of (xSrc, ySrc), do the following:

// Align left of black box in relation to left of cell.

xActual = xSrc + gm.gmptGlyphOrigin.x;

// Calculate upper left of black box in relation to cell.

yActual = ySrc + (ascent - gm.gmptGlyphOrigin.y);

// The glyph bitmap is selected into the hdcGlyph memory DC.

BitBlt(hDC, xActual, yActual, gm.gmBlackBoxX, gm.gmBlackBoxY,

hdcGlyph, 0, 0, SRCCOPY);

Because the left edge of the bitmap is by definition the left edge of the black box, no correction is needed in the x direction to align the glyph based on the black box. For a complete emulation, the current text and background colors, as well as the transparency mode, need to be accounted for as well.

The GGO_NATIVE format is a bit different in its alignment. The coordinates returned are based on a coordinate system with positive y going up, and they need to be flipped to properly work with GDI drawing functions like PolyPolygon. This can be accomplished by setting a mapping mode that has the same coordinate orientation as the GGO_NATIVE format or by manually flipping the y coordinates, which is the approach used in the GLYPH sample application and the rest of this article.

Once flipped, the outline is defined with (0,0) at the cell origin (intersection of left of cell and baseline). Using PolyPolygon to draw the outline, emulating TextOut behavior can be done as follows:

oldOrg = GetViewportOrg(hDC);

// Offset the output by the needed amount.

OffsetViewportOrg(hDC, xSrc, ySrc + ascent);

// Output the glyph.

PolyPolygon(hDC, lpPts, lpPolyCount, cPoly);

// Restore original viewport origin.

SetViewportOrg(hDC, LOWORD(oldOrg), HIWORD(oldOrg);

The OffsetViewportOrg function is used to conveniently translate the output without actually adjusting the individual points of the outline. (The effect is the same as adding the given values to each point in the polypolygon.) Because the outline was flipped vertically about 0, the glyph's baseline (y=0) is now at the upper limit of visible space in Windows (the ascender of the glyph is in negative space), and the entire glyph must be translated down to place the baseline in the proper position. This translation distance is the font's ascent, which is how far away the baseline is from the top of the cell.

To horizontally align an outline based on the black box, the outline needs to be translated to the left by the gmptGlyphOrigin.x value, which defines how far the black box is from the cell origin. In the code above, this can be accomplished with the following change:

// Offset the output by the needed amount.

OffsetViewportOrg(hDC, xSrc - gm.gmptGlyphOrigin.x, ySrc + ascent);

If the glyph has an underhang, the gmptGlyphOrigin.x value is negative, so the outline is actually translated to the right, exposing the underhang.

All of the above principles apply in situations where the glyph is rotated and/or sheared, but the actual placement becomes much more application-dependent because "correctness" is based on what the application is doing with the transformed glyph. The glyph metrics are calculated after the glyph has been transformed, and the black box is always a nonrotated rectangle enclosing the glyph.