Steve develops Windows CAD applications and teaches object-oriented programming. He received an MS in computer science from Cal State Fullerton with an emphasis in graphics. Steve can be reached at xsreiche@aunix. fullerton.edu.
Although Windows 3.1 has been out for some time, a few of its features are only now moving into many programmers' field of view. One such feature is the built-in support for outline fonts, which uses the TrueType format defined by Microsoft and Apple.
In this article, I'll describe the TrueType format and how fonts are rendered, then show how a new function in the Windows API, GetGlyphOutline(), can be used to create a simple font-viewing program. But first, some background on digital fonts.
The early versions of Windows only supported two font formats: bitmap and vector. As you know, bitmap fonts represent a character shape simply by selectively coloring a grid of pixels. Bitmap fonts can be displayed quickly, and, if properly designed, they look very good at the size at which they were created. But when scaled to larger sizes, the resulting "jaggies" look terrible and have given bitmap fonts a bad name. Nevertheless, for high-quality results at low screen resolutions, nothing beats a hand-tuned bitmap font in readability.
Vector fonts are more scalable than bitmaps, but don't look as good--either at the original size or enlarged. Vector fonts represent character shapes via straight-line segments. They have a "CAD look" to them, because the space between the lines doesn't get filled in. When scaled large enough, the characters look very thin. Also, since curved features are rendered via straight lines, at a certain size these straight edges and corners are glaringly visible.
Outline-based fonts such as TrueType combine the best features of both vector and raster fonts without the disadvantages of either, by representing characters with mathematical outlines instead of simple strokes or a raster grid of pixels. Outline fonts are not new with TrueType; they have been used in electronic publishing systems for over 20 years, initially in imaging or typesetting systems, and more recently in interactive desktop systems. The approach used by TrueType has much in common with these older systems, but has also pushed the technology further in the areas of font rendering and hinting (discussed later).
In TrueType, a character's outline is defined by combinations of lines and curves. An outline can be scaled to fit a wide range of sizes, and then filled in to result in a high-quality bitmap font at the desired size. The scaling and rendering process happens at run time. Once in bitmap form, TrueType fonts can be displayed as quickly as raster fonts. Conversion of an entire font at a given size usually takes only a second or two, depending on the sizes of the bitmaps and the speed of the computer. Once converted, Windows stores the bitmaps in a font cache where they are used over and over.
TrueType brings with it the specialized terminology of typography and digital fonts, as well as introducing its own terms. Character figures are called "glyphs." The outlines that describe glyphs are collections of closed curves called "contours." For example, the glyph outline for the lowercase "i" consists of two contours: one for the dot and one for the stem. The lowercase "b" also has two contours: an outer one and an inner one. Contours are defined by ordered sequences of points, sometimes called "control points." Each point is specified to be either on or off of the contour. If two consecutive points are on the contour, a straight line connects them, otherwise a smooth curve is tangent to them. The points may range from --16,384 to 16,383 in units known as "font units" or "FUnits." Points specify locations relative to a grid called the "EM square." The fonts supplied with Windows happen to use 2048 FUnits per EM.
TrueType font files have the TTF extension and are stored in the Windows System directory. For example, the file ARIAL.TTF contains the normal (i.e. not bold or italic) font for the Arial typeface. These files consist of a series of tables. One table (glyf) contains the points and "hints" that describe the outlines of the character figures. Another table (cmap) indexes the characters in the glyf table. TTF files also contain a table called head that provides scaling information. There are 16 other defined tables that may appear--but these three tell us the most about the inner workings of the rendering process.
As shown in Figure 1, each glyph outline goes through three transformations before emerging as a bitmap. The transformations are accomplished by three TrueType modules known as the Scaler, Interpreter, and Rasterizer. First, the Scaler shrinks (or stretches) the outline to the requested size. Then the Interpreter grid fits the scaled outline by executing instructions ("hints") attached to the glyph. The resulting outline goes to the Rasterizer to generate a bitmap.
As an example, consider the rendering process applied to the letter "b" of the Arial font, displayed at 14 points in EGA resolution. The Scaler converts coordinates from font units into device units (pixels). Most EGA monitors display 96 pixels per inch horizontally and 72 pixels per inch vertically. In the TTF file, the glyph for the letter "b" is 921 FUnits wide and 1490 FUnits high on a scale of 2048 FUnits per EM. The resulting scaled dimensions are 8.39 pixels wide and 10.19 pixels high on the EGA screen. Figure 2(a) shows the scaled outline mapped to a pixel grid.
In scaling glyphs down to small sizes, it often becomes unclear whether a given pixel belongs to the glyph or not. This decision is critical if a glyph is scaled so small that a typographic feature occupies only a single pixel; because if even one pixel is missing or out of place, the glyph may become illegible. In a process called "grid fitting," the Interpreter uses the "hints" associated with the glyph to distort the scaled outline so that it improves the appearance of the bitmap.
One advance of TrueType over older outline-font technology is the sophistication of its hinting mechanism. Hints are not passive data structures, as their name implies, but active software programs that literally take control of the Interpreter. The TrueType instruction set resembles assembly language, complete with opcodes and mnemonics for If/Then constructs, loops, subroutines, and a full complement of arithmetic and logical operations. For example, the MD instruction measures the distance between two outline points and pushes the result on the Interpreter's stack--to possibly serve as part of a further calculation.
A similar instruction, MPS, makes it possible to measure the current point size, perhaps as a basis for choosing an alternate path through the instruction stream. Grid-fitting is also aided by the RTG instruction that aligns points to the nearest grid line. There are over 120 different instructions. Fortunately, they are generated automatically by font editors. Figure 2(b) shows the outlines after hints are applied.
After the Interpreter makes the necessary adjustments, it sends the grid-
fitted outline to the Rasterizer to produce a bitmap. The Rasterizer fills in the outline by following a simple rule: It turns on only those pixels whose center lies either inside or exactly on the outline of the glyph. The grids in Figures 2(a) and 2(b) indicate the center of a pixel with a dot. By analyzing the direction traveled between any two points, the Rasterizer can always determine where the inside of the glyph is. The points are ordered such that as it follows along the outline in the direction from one point to another, the inside is always to the right.
If you want to experiment with TrueType glyph outlines, the Windows 3.1 API provides the GetGlyphOutline function and a few specialized data structures. This function retrieves the same fully scaled and hinted outline that the Rasterizer gets. The function takes a device context, a character in the current font, and the address of a buffer where it will store the glyph data. We usually need to call this function with a NULL buffer the first time so that it can return the required buffer size. GetGlyphOutline returns information about the dimensions of the glyph in a GLYPHMETRICS structure.
The points that describe a glyph outline use fixed-point numbers, which can carry 16 bits of fractional precision. Fractional precision is not only necessary for accurately scaling and rotating the points, but should be maintained when computing the curves. Both popular techniques for rendering polynomial curves--forward differencing and subdivision--require some degree of fractional precision, although subdivision usually requires less. The Windows header file defines FIXED as a structure with two 16-bit components: an integer part and a fractional part. It also defines fixed-point coordinates using a POINTFX structure that contains two FIXED structures.
It's easy to carry out any necessary math if we treat FIXED structures as signed longs, noting that the nth bit starting at 0 has a value of 2n--16. For example, the long number 65,536 corresponds to the real number 1; the long number 32,768 corresponds to the real number 0.5, and so on. When dealing with POINTFX structures, it's convenient to define a structure called LONGPOINT and use a type cast. The structure is:
struct LONGPOINT
{
long x, y;
};
The glyph data from GetGlyphOutline is returned in a buffer containing each contour of the outline. Parts of a contour can be a mixture of straight lines or curves. Each contour begins with a 16-byte TTPOLYGONHEADER structure. The first data member, cb, specifies the number of bytes in the contour--in other words, the next contour, if any, starts exactly cb bytes from the beginning of the current one. This structure also contains the pfxStart member which denotes the first (and last) point on the contour.
One or more TTPOLYCURVE records immediately follow the header. Each contains a variably sized array called apfx, which holds the actual points (POINTFX structures) that define a curve or polyline on the contour. The wType member indicates whether the points represent polylines (with the value TT_PRIM_LINE) or quadratic B-splines (with the value TT_PRIM_QSPLINE). Naturally, since the array can contain any number of points, this record has a member cpfx that specifies how many.
Here's the rule for connecting sequences of curves: Every curve automatically begins where the last point on the previous curve ends--unless it's the first curve, which begins at pfxStart. This way each point is specified only once. When the last point on the last curve is different from pfxStart, a straight line should be drawn between the two points, closing the contour.
GetGlyphOutline also requires, as a parameter, a two-dimensional transformation matrix of type MAT2. Since this structure contains four FIXED numbers, we can play the same kind of trick that we used with POINTFX: typecast the MAT2 variable and manipulate it as an array of longs. Be careful, however, with the values you put in this matrix because GetGlyphOutline can overflow--causing an unrecoverable application error (UAE). Although the identity matrix works well, there may be times when you want a glyph rotated. If you plan to rotate a glyph, be prepared to do a suitable translation because all rotations are about the origin of the glyph's coordinate system. Naturally, we can forego transformations within the required matrix and provide our own transformations. The demonstration program for this article shows how to add some special effects to font renderings.
To render a filled character from the outline data, we can either write our own rasterizer (a lot of work) or use the PolyPolygon function in the Windows API. Although PolyPolygon uses a different algorithm from the TrueType Rasterizer to determine the interior of a figure, the results are usually excellent for characters greater than 25 points or so. Smaller characters, however, suffer in quality due to the rounding that occurs when converting from FIXED points to POINT points. One advantage to using this function is that we can get textured renderings by using a pattern brush. Since PolyPolygon requires a complete array of points, we must make sure to allocate enough memory to store all of the points.
My TrueType Font Demo program decodes and displays the outline data from GetGlyphOutline for any character entered at the keyboard. As indicated in the title bar of the main window, the current font may be changed from the File menu by choosing New. An Options menu also lets the user change the fill style, display control points, and apply special effects.
The code is in Listing One (page 60). At startup, the program does the usual Windows initialization: registering a window class, creating a main window, and cycling through the message loop. At this point, it selects the default font, Arial, and the character "a." The corresponding screen display is shown in Figure 3. To select a different font, the program calls ChooseFont, which brings up the Font dialog box. The selection of fonts here is restricted to TrueType fonts only. If the user chooses OK, the function returns True and stores the information about the font in a global LOGFONT structure.
When the user changes the font, chooses an option, or types a character, the program stores that information and then forces a repaint of the window. Repainting involves creating a font and a brush with the current settings, and then calling draw_glyph_outline to render the current character.
The draw_glyph_outline function takes, as arguments, the device context, the location of the upper corner of the character, and the desired character to be displayed. It first calls GetGlyphOutline to fill a buffer with the outline data for the character. It then calls compute_memory_requirement and allocates enough memory to hold the array of contour points that eventually get passed to PolyPolygon. Next, it walks through the data in a doubly nested loop. The outer loop finds each closed outline and sends it to the inner loop, which in turn steps through the individual pieces of the outline. The inner loop passes the groups of control points to draw_polyline and draw_quadratic_bspline as directed by the wType member of the TTPOLYCURVE structure. Before drawing an individual curve, it first modifies the point array (apfx) by inserting the last point of the previous curve at the beginning so that each group of control points is independent from its predecessor. Then it calls the transform function.
The compute_memory_requirement function steps through the outline data in the same manner as draw_glyph_outline. When it encounters a polyline, it advances the count variable by the number of vertices. When it encounters a B-spline, it advances the count by the number of Bézier curve segments multiplied by the maximum number of points in each segment. At the end, it returns the maximum number of bytes required to hold the array of contour points.
The transform function produces special effects by moving the control points of the outline. If the user has chosen Pinch from the options menu, then this function will pull control points nearing the center of the character even closer. This results in a cartoon-like effect. If you choose Punch from the menu, then the points near the center are pushed farther away--resulting in a bloating effect.
When draw_polyline receives the address and size of an array of POINTFX structures representing vertices, it stores each point at the end of the array of contour points. Likewise, draw_quadratic_bspline takes the address and size of an array of POINTFX structures. If a curve has three control points, these are sent unmodified to draw_Bezier_curve. Otherwise, the Bézier conversion method is applied to each consecutive three-point grouping and the results sent individually to draw_Bezier_curve.
Producing a smooth curve depends on draw_Bezier_curve. Since it uses recursive subdivision, the quality of the shape depends on the depth of the recursion. There is a trade-off here since the more points it stores, the slower the curve will draw. The depth is set at 8 for this program, which means only three bits of fractional precision are required for accuracy. POINTFX structures keep 16 bits of fractional precision so there are no round-off problems when computing the curve. With a depth of 8, a maximum of 257 points along a curve can be stored. But this storage requirement grows exponentially as the depth increases.
Conversion of a POINTFX value to a window coordinate takes place in fixed_to_int, which selects the closest pixel by rounding the FIXED values to the nearest whole number. Rounding points to the nearest pixel produces good results when the glyphs are large, but achieving higher quality at small sizes requires an algorithm based on the method used by the Rasterizer.
Foley, James, Andries van Dam, Steven K. Feiner, and John F. Hughes. Computer Graphics: Principles and Practice, second edition. Reading, MA: Addison-Wesley, 1990.
Rubinstein, Richard. Digital Typography. Reading, MA: Addison-Wesley, 1988.
TrueType Font Files. Microsoft Corporation, 1991.
The Mathematics of Quadratic B-splines
The implementors of TrueType chose splines as the means for representing curves. You can think of splines as a series of simple polynomial curves smoothly spliced together. For a quadratic spline, each piece (or segment) is described by a quadratic polynomial function of the form y(x)=ax2+bx+c, where a, b, and c are constant coefficients. Remember from algebra that this function produces a parabola. It has only one point of inflection and depending on the coefficient a, opens either upward or downward. But, for free-form quadratic splines like the ones in TrueType, we need parabolas that can open not only up and down, but in any direction. That's why curves are typically represented parametrically. In two dimensions we plot the functions x(t)=axt2+bxt+cx and y(t)=ayt2+ byt+cy.
By restricting the variable t to the closed interval between 0 and 1, a curve segment has definite starting and ending coordinate, and can open in any direction. Each function requires three coefficients.
In the early '70s Pierre Bézier, a mathematician working for the French automaker Renault, devised a clever way of blending three points, called "control points," to obtain the coefficients such that a curve segment connects to the two endpoints and pulls toward the other point. The function for a quadratic Bézier curve is: Q(t)=t2(B0-2B1+B2)+t(-2B0+2B1)+B where each Bi is a control point. Figure 4 shows what a quadratic Bézier curve looks like. It's easy to verify mathematically that it connects to the endpoints by evaluating the function for t equal to 0, and then 1. For simplicity, we can also describe the curve as function of its control points. For instance, Bezier (B0, B1, B2) equals the drawing in Figure 4.
What makes Bézier curves attractive is that your program can render them without fully evaluating the polynomials, using a fast algorithm known as the deCasteljau algorithm. Given the three control points for a Bézier curve, the algorithm uses a series of recursive subdivision operations to render the curve. Each subdivision splits a Bézier curve into two smaller Bézier curves and generates a point on the original curve. Figure 4 shows one subdivision. If the points are integer coordinates, then only a few shifts and adds are required at each subdivision step. Once the curve has been divided enough times, you can connect the resulting points on the curve with straight lines.
Quadratic B-splines, which can have three or more control points, can be converted into quadratic Bézier curves by choosing each consecutive point and the two points that follow. For example, a spline with seven points converts to five Bézier curve segments by choosing:
{(P0,P1,P2),
(P1,P2,P3),
(P2,P3,P4),
(P3,P4,P5),
(P4,P5,P6)}
You can then use the following rules to find the Bézier curve segments:
Pi+2)/2), for the interior segments.
--S.R.
Copyright © 1993, Dr. Dobb's Journal
Rule #3 is the uniform conversion applied to the inner curve segments. Rules #1, #2, and #4, are called "end conditions," because uniform B-splines don't ordinarily connect to their endpoints--as we need them to. Figure 4: Quadratic Bézier curve showing one level of subdivision.
[LISTING ONE]
/***************************************************************************
* Glyph viewing program by Steven Reichenthal, 1993. Although this code
* is basically C, it uses certain C++ constructs such as in-place
* declaration of variables that require use of a C++ compiler.
**************************************************************************/
#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <commdlg.h>
#include <math.h>
#define IDM_NEW 101 // File Menu ID's
#define IDM_EXIT 108
#define MARKERSIZE 4
#define IDM_SHOWCONTROLPOINTS 402
#define IDM_FILL 403
#define IDM_OUTLINE 404
#define IDM_NORMAL 405
#define IDM_PINCH 406
#define IDM_PUNCH 407
long FAR PASCAL WndProc (HWND hwnd, unsigned iMessage, WORD wParam,
LONG lParam);
HANDLE hInst;
HWND hwnd;
HDC hdc;
PAINTSTRUCT ps;
HWND hwndFrame;
WORD maxClient;
WORD cxClient,cyClient;
LOGFONT lf;
CHOOSEFONT cf;
int character;
int nEffect;
BOOL bShowControlPoints = TRUE;
BOOL bFill;
BOOL bOutline = TRUE;
float pi;
static char szAppName [] = "TrueType Font Demo";
/*----------------------------------------------------------------------*/
// set the caption for the frame window
void set_frame_caption ()
{
char sz [80];
wsprintf (sz, "%s - %s", (LPSTR) szAppName, (LPSTR) lf.lfFaceName);
SetWindowText (hwndFrame, sz);
}
/*----------------------------------------------------------------------*/
#pragma argsused
int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
HWND hwnd;
MSG msg;
hInst = hInstance;
if (!hPrevInstance)
{ WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW | CS_BYTEALIGNWINDOW;
wc.lpfnWndProc = (WNDPROC) WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = 0;
wc.hCursor = LoadCursor (NULL, IDC_ARROW);
wc.hbrBackground = GetStockObject (WHITE_BRUSH);
wc.lpszMenuName = "MENU_1";
wc.lpszClassName = szAppName;
if (!RegisterClass (&wc)) return FALSE;
}
pi = atan2 (0, -1);
lf.lfHeight = -200;
lf.lfWeight = 400;
strcpy (lf.lfFaceName, "Arial");
character = 'a';
hwnd = CreateWindow (szAppName, szAppName,
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
if(!hwnd) return FALSE;
hwndFrame = hwnd;
set_frame_caption ();
ShowWindow (hwnd, nCmdShow);
UpdateWindow (hwnd);
while (GetMessage (&msg, NULL, NULL, NULL))
{
TranslateMessage (&msg);
DispatchMessage (&msg);
}
return msg.wParam;
}
/*----------------------------------------------------------------------*/
int xOffset,yOffset;
struct LONGPOINT
{
long x, y;
};
/*----------------------------------------------------------------------*/
inline int fixed_to_int (long value) // convert a fixed value to an integer
{
return (value + 32767) >> 16;
}
// draw the array of control points
void draw_control_points (POINTFX pt [], int nPoints)
{ int i;
LONGPOINT *p = (LONGPOINT *) pt;
if (!bShowControlPoints)
return;
for (i = 0; i < nPoints; i++)
{
POINT pt;
pt.x = xOffset + fixed_to_int (p [i].x);
pt.y = yOffset - fixed_to_int (p [i].y);
PatBlt (hdc,
pt.x - MARKERSIZE / 2, pt.y - MARKERSIZE / 2,
MARKERSIZE, MARKERSIZE, BLACKNESS);
}
}
/*----------------------------------------------------------------------*/
#define DEPTH 8 // Bezier recursion depth
#define MAX_BEZIER_POINTS ((1 << DEPTH) + 1)
static POINT huge * Pts; // array of contour points
static POINT huge * pPts; // current point
static POINT huge * pPtsStart; // first contour point
static int * polyCounts; // array of coutour point counts
static int nContours; // number of contours
static GLYPHMETRICS gm;
/*----------------------------------------------------------------------*/
// transform the array of control points
void transform (POINTFX pt [], int nPoints)
{
LONGPOINT *p = (LONGPOINT *) pt;
switch (nEffect)
{
case IDM_NORMAL: return;
case IDM_PINCH:
while (nPoints--)
{
float xx = 2.0 * pi * float ((p->x >> 16) - gm.gmptGlyphOrigin.x)
/ float (gm.gmBlackBoxX);
float yy = 2.0 * pi * float ((p->y >> 16) - gm.gmptGlyphOrigin.y)
/ float (gm.gmBlackBoxY);
p->x = p->x + float (gm.gmBlackBoxX) / 10.0 * 65536.0 * sin (xx);
p->y = p->y + float (gm.gmBlackBoxY) / 10.0 * 65536.0 * sin (yy);
p++;
}
break;
case IDM_PUNCH:
while (nPoints--)
{
float xx = 2 * pi * float ((p->x >> 16) - gm.gmptGlyphOrigin.x)
/ float (gm.gmBlackBoxX);
float yy = 2 * pi * float ((p->y >> 16) - gm.gmptGlyphOrigin.y)
/ float (gm.gmBlackBoxY);
p->x = p->x + float (gm.gmBlackBoxX) / 5.0 * 32768.0
* (1 + cos (yy) * sin (xx));
p->y = p->y + float (gm.gmBlackBoxY) / 5.0 * 32768.0
* (1 + cos (xx) * sin (yy));
p++;
}
break;
}
}
/*----------------------------------------------------------------------*/
// store a point in the array of contour points
void store (LONGPOINT pt)
{
pPts->x = xOffset + fixed_to_int (pt.x);
pPts->y = yOffset - fixed_to_int (pt.y);
pPts++;
}
// sub-divide the quadratic Bezier curve
void near pascal sub_divide (LONGPOINT p [])
{
static int depth = DEPTH;
LONGPOINT q [8];
int x = xOffset + fixed_to_int (p [2].x);
int y = yOffset - fixed_to_int (p [2].y);
if (x == pPts [-1].x && y == pPts [-1].y) return;
if (!depth)
{
store (p [2]);
return;
}
q [0] = p [0];
q [4] = p [2];
q [1].x = (p [0].x + p [1].x) >> 1;
q [3].x = (p [1].x + p [2].x) >> 1;
q [2].x = (q [1].x + q [3].x) >> 1;
q [1].y = (p [0].y + p [1].y) >> 1;
q [3].y = (p [1].y + p [2].y) >> 1;
q [2].y = (q [1].y + q [3].y) >> 1;
depth--;
sub_divide (q);
sub_divide (q + 2);
depth++;
}
/*----------------------------------------------------------------------*/
// draw the quadratic Bezier curve
void draw_bezier_curve (LONGPOINT p [])
{
store (p [0]);
sub_divide (p);
}
/*----------------------------------------------------------------------*/
// draw the quadratic B-spline from the array of points
void draw_quadratic_bspline (POINTFX pt [], int nPoints)
{
LONGPOINT b [3];
LONGPOINT *p = (LONGPOINT *) pt;
if (nPoints == 3)
{
draw_bezier_curve (p);
return;
}
b [0] = p [0];
b [1] = p [1];
b [2].x = (p [1].x + p [2].x) >> 1;
b [2].y = (p [1].y + p [2].y) >> 1;
draw_bezier_curve (b);
for (int i = 1; i < nPoints - 3; i++)
{
b [0].x = (p [i].x + p [i + 1].x) >> 1;
b [0].y = (p [i].y + p [i + 1].y) >> 1;
b [1] = p [i + 1];
b [2].x = (p [i + 1].x + p [i + 2].x) >> 1;
b [2].y = (p [i + 1].y + p [i + 2].y) >> 1;
draw_bezier_curve (b);
}
b [0].x = (p [i].x + p [i + 1].x) >> 1;
b [0].y = (p [i].y + p [i + 1].y) >> 1;
b [1] = p [i + 1];
b [2] = p [i + 2];
draw_bezier_curve (b);
}
/*----------------------------------------------------------------------*/
// draw the polyline from the array of points
void draw_polyline (POINTFX points [], int nPoints)
{
LONGPOINT *p = (LONGPOINT *) points;
for (int i = 0; i < nPoints; i++)
store (p [i]);
}
/*----------------------------------------------------------------------*/
// calculate the number of bytes needed for the array of contour points
DWORD compute_memory_requirement (TTPOLYGONHEADER *header, DWORD cbBuffer)
{
DWORD count = 1;
do
{
TTPOLYGONHEADER *nextHeader =
(TTPOLYGONHEADER *) (header->cb + (char *) header);
TTPOLYCURVE *curve = (TTPOLYCURVE *) (header + 1);
POINTFX pfxStart = header->pfxStart;
while (1)
{
UINT cpfx = curve->cpfx + 1;
POINTFX *ppfx = curve->apfx - 1;
POINTFX pfxEnd = ppfx [cpfx - 1];
if (curve->wType == TT_PRIM_LINE)
count += cpfx;
else
count += (cpfx - 2) * MAX_BEZIER_POINTS;
curve = (TTPOLYCURVE *) (ppfx + cpfx);
if (nextHeader <= (TTPOLYGONHEADER *) curve)
{
if (memcmp (&pfxEnd, &pfxStart, sizeof (pfxEnd)))
count += 2;
break;
}
}
count++;
cbBuffer -= header->cb;
header = nextHeader;
}
while (cbBuffer);
return count * (DWORD) sizeof (POINT);
}
/*----------------------------------------------------------------------*/
// draw the glyph outline of the selected character in the current font
int draw_glyph_outline (HDC hdc, int x, int y, int ch)
{
TEXTMETRIC tm;
MAT2 mat2;
GetTextMetrics (hdc, &tm);
xOffset = x;
yOffset = (y + tm.tmAscent);
memset (&mat2, 0, sizeof (mat2));
mat2.eM11.value = 1;
mat2.eM22.value = 1;
DWORD cbBuffer = GetGlyphOutline (hdc,
ch, GGO_NATIVE, &gm, 0, NULL, &mat2);
if (long (cbBuffer) <= 0 || cbBuffer > 32767)
return 0;
void *buffer = malloc (int (cbBuffer));
if (!buffer)
return 0;
GetGlyphOutline (hdc, ch, GGO_NATIVE, &gm, cbBuffer, buffer, &mat2);
TTPOLYGONHEADER *header = (TTPOLYGONHEADER *) buffer;
DWORD cbPolygons = compute_memory_requirement (header, cbBuffer);
HANDLE hPolygons = GlobalAlloc (GMEM_FIXED, cbPolygons);
if (!hPolygons)
{
free (buffer);
return 0;
}
Pts = (POINT huge *) GlobalLock (hPolygons);
pPts = Pts;
nContours = 0;
polyCounts = ((int *) header)+2; //use the area beyond cb for the counts
do
{
TTPOLYGONHEADER *nextHeader =
(TTPOLYGONHEADER *) (header->cb + (char *) header);
TTPOLYCURVE *curve = (TTPOLYCURVE *) (header + 1);
POINTFX pfxEnd = header->pfxStart;
POINTFX pfxStart = pfxEnd;
pPtsStart = pPts;
while (1)
{
UINT wType = curve->wType;
UINT cpfx = curve->cpfx + 1;
POINTFX *ppfx = curve->apfx - 1;
ppfx [0] = pfxEnd; // this overwrites 8 bytes before apfx,
// but we are done with them at this point.
pfxEnd = ppfx [cpfx - 1];
transform (ppfx, cpfx);
draw_control_points (ppfx, cpfx);
if (wType == TT_PRIM_LINE)
draw_polyline (ppfx, cpfx);
else
draw_quadratic_bspline (ppfx, cpfx);
curve = (TTPOLYCURVE *) (ppfx + cpfx);
if (nextHeader <= (TTPOLYGONHEADER *) curve)
{
if (memcmp (&pfxEnd, &pfxStart, sizeof (pfxEnd)))
{
ppfx [0] = pfxEnd;
ppfx [1] = pfxStart;
transform (ppfx, 2);
draw_polyline (ppfx, 2);
}
break;
}
}
*pPts++ = *pPtsStart;
polyCounts [nContours++] = pPts - pPtsStart;
cbBuffer -= header->cb;
header = nextHeader;
}
while (cbBuffer);
PolyPolygon (hdc, (LPPOINT) Pts, polyCounts, nContours);
GlobalUnlock (hPolygons);
GlobalFree (hPolygons);
free (buffer);
return 1;
}
/*----------------------------------------------------------------------*/
long FAR PASCAL WndProc (HWND hwnd, unsigned iMessage, WORD wParam,
LONG lParam)
{
const MF [2] = { MF_UNCHECKED, MF_CHECKED };
switch (iMessage)
{
case WM_COMMAND:
switch (wParam)
{
case IDM_NEW:
cf.lStructSize = sizeof (cf);
cf.hwndOwner = hwnd;
cf.lpLogFont = &lf;
cf.Flags = CF_SCREENFONTS | CF_TTONLY
| CF_FORCEFONTEXIST | CF_INITTOLOGFONTSTRUCT;
cf.nFontType = SCREEN_FONTTYPE;
if (ChooseFont (&cf))
{
set_frame_caption ();
InvalidateRect (hwnd, NULL, TRUE);
}
break;
case IDM_EXIT:
SendMessage (hwnd, WM_CLOSE, 0, 0L);
break;
case IDM_SHOWCONTROLPOINTS:
bShowControlPoints ^= TRUE;
CheckMenuItem (GetMenu (hwnd), IDM_SHOWCONTROLPOINTS,
MF [bShowControlPoints]);
InvalidateRect (hwnd, NULL, TRUE);
break;
case IDM_FILL:
bFill ^= TRUE;
CheckMenuItem (GetMenu (hwnd), IDM_FILL, MF [bFill]);
InvalidateRect (hwnd, NULL, TRUE);
break;
case IDM_OUTLINE:
bOutline ^= TRUE;
CheckMenuItem (GetMenu (hwnd), IDM_OUTLINE, MF [bOutline]);
InvalidateRect (hwnd, NULL, TRUE);
break;
case IDM_NORMAL:
case IDM_PINCH:
case IDM_PUNCH:
nEffect = wParam;
CheckMenuItem (GetMenu (hwnd),
IDM_NORMAL, MF [wParam == IDM_NORMAL]);
CheckMenuItem (GetMenu (hwnd),
IDM_PINCH, MF [wParam == IDM_PINCH]);
CheckMenuItem (GetMenu (hwnd),
IDM_PUNCH, MF [wParam == IDM_PUNCH]);
InvalidateRect (hwnd, NULL, TRUE);
break;
}
break;
case WM_SIZE:
cxClient = LOWORD (lParam);
cyClient = HIWORD (lParam);
break;
case WM_CHAR:
character = wParam;
InvalidateRect (hwnd, NULL, TRUE);
break;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps);
HFONT hFont = SelectObject (hdc, CreateFontIndirect (&lf));
int PolyFillMode = SetPolyFillMode (hdc, ALTERNATE);
static WORD brushBits [] =
{ 0xf8, 0x74, 0x22, 0x47, 0x8f, 0x17, 0x22, 0x71
};
HANDLE hBitmap = CreateBitmap (8, 8, 1, 1, brushBits);
HBRUSH hBrush = SelectObject (hdc,
bFill ? CreatePatternBrush (hBitmap)
: GetStockObject (NULL_BRUSH));
HPEN hPen = SelectObject (hdc, bOutline ? GetStockObject (BLACK_PEN)
: GetStockObject (NULL_PEN));
draw_glyph_outline (hdc, 100, 0, character);
SelectObject (hdc, hPen);
hBrush = SelectObject (hdc, hBrush);
if (bFill)
{
DeleteObject (hBrush);
DeleteObject (hBitmap);
}
SetPolyFillMode (hdc, PolyFillMode);
DeleteObject (SelectObject (hdc, hFont));
EndPaint (hwnd, &ps);
break;
case WM_QUERYENDSESSION:
case WM_CLOSE:
case WM_DESTROY:
PostQuitMessage (0);
return 1;
default:
return DefWindowProc (hwnd, iMessage, wParam, lParam);
}
return 0;
}
/*----------------------------------------------------------------------*/
End Listing