TrueType Font Secrets
by Michael Bertrand and Dave Grundgeiger
Figure 1: Using the CGlyph class in an SDK application.
// Declare pointer to main glyph object.
static CGlyph* pGlyph;
.
.
// Create glyph object (once only, at start).
pGlyph = new CGlyph();
.
.
// Realize 'B' (assumes font defined by LOGFONT lf).
pGlyph->Realize('B', hDC, &lf);
.
.
// Draw the glyph with (100,100) as top left.
pGlyph->Draw(hDC, 100, 100);
.
.
// Delete glyph object (once only, at end).
delete pGlyph;
- End of Figure -
Return to Article
Figure 2: Using the CGlyph class in an MFC SDI (Single
Document Interface) application, where m_glyph
is a member variable in the document class and pDoc
is a pointer to the document.
// m_glyph is a member variable in the document class.
CGlyph m_glyph;
.
.
// Realize 'B' (assumes font defined by LOGFONT lf).
pDoc->m_glyph.Realize('B', *pDC, &lf);
.
.
// Draw the glyph with (100,100) as top left.
pDoc->m_glyph.Draw(*pDC, 100, 100);
- End of Figure -
Return to Article
Figure 3: The Draw method of class CGlyph.
void CGlyph::Draw(HDC hDC, const int xOffset, const int yOffset) const
{
LPTTPOLYGONHEADER lpttph; // pointer to current polygon in glyph
LPTTPOLYCURVE lpttpc; // pointer to current polycurve in polygon
DWORD dwHeaderOffset; // byte offset of current polygon header
// from start of buffer
DWORD dwCurveOffset; // byte offset of current polycurve header
// from start of polygon header
DWORD dwStructSize; // size of current polycurve (depends on
// the number of points in the polycurve)
POINT ptPolyStart; // polygon start point
POINT ptCurveLast; // last point on previous curve
POINT pt; // temporary holder of point coordinates
int i; // to loop thru lines or splines
POINTFX p1, p2, p3; // three points defining spline
if (!IsRealized())
return;
assert(m_lpvBuffer != NULL);
// Outer while loops for all polygon headers (can be more than one).
dwHeaderOffset = 0;
while (m_cbBuffer >= (dwHeaderOffset + sizeof(TTPOLYGONHEADER)))
{
// Get pointer to start of the polygon.
lpttph = (LPTTPOLYGONHEADER)(((char *)m_lpvBuffer) + dwHeaderOffset);
assert(lpttph->dwType == TT_POLYGON_TYPE);
// Convert polygon's start point to pixels.
FromGGOPoint(&ptPolyStart, lpttph->pfxStart, xOffset, yOffset);
// Init last point of last record to polygon start point.
ptCurveLast = ptPolyStart;
// Position graphics current pointer to start of polygon.
MoveToEx(hDC, ptPolyStart.x, ptPolyStart.y, NULL);
// Inner while loops for all polycurves in one polygon.
// (A polycurve is one or more polyline and/or QSpline records.)
dwCurveOffset = sizeof(TTPOLYGONHEADER);
while (lpttph->cb >= (dwCurveOffset + sizeof(TTPOLYCURVE)))
{
// Get pointer to start of polycurve.
lpttpc = (LPTTPOLYCURVE)(((char *)m_lpvBuffer) + dwHeaderOffset
+ dwCurveOffset);
// Test record type, draw polyline or series of Beziers accordingly.
switch (lpttpc->wType)
{
case TT_PRIM_LINE:
// Draw polyline connecting GGO points (there are lpttpc->cpfx).
for (i = 0; i < lpttpc->cpfx; i++)
{
FromGGOPoint(&pt, lpttpc->apfx[i], xOffset, yOffset);
LineTo(hDC, pt.x, pt.y);
}
ptCurveLast = pt;
break;
case TT_PRIM_QSPLINE:
// Draw series of Beziers connecting GGO points.
// But for initial Bezier, grab last point on previous curve.
p3.x.value = (short)ptCurveLast.x;
p3.x.fract = 0;
p3.y.value = (short)ptCurveLast.y;
p3.y.fract = 0;
// Draw QSplines based on GGO points (there are lpttpc->cpfx-1).
for (i = 0; i < lpttpc->cpfx - 1; i++)
{
// p1 is 1st control -- last point in this or previous contour.
p1 = p3;
// p2 is handle -- a point in the record.
FromGGOPoint(&pt, lpttpc->apfx[i], xOffset, yOffset);
p2.x.value = (short)pt.x;
p2.x.fract = 0;
p2.y.value = (short)pt.y;
p2.y.fract = 0;
// p3 is 2nd control -- the midpoint of 2 spline points
// except for the last spline, when it is the second to
// the last point in the spline record.
p3 = (i == (lpttpc->cpfx-2)) ? lpttpc->apfx[i+1] :
AvgPointsFX(lpttpc->apfx[i], lpttpc->apfx[i+1]);
FromGGOPoint(&pt, p3, xOffset, yOffset);
p3.x.value = (short)pt.x;
p3.x.fract = 0;
p3.y.value = (short)pt.y;
p3.y.fract = 0;
// Call deCasteljau to draw quadratic Bezier curve.
DrawQSpline(hDC, p1, p2, p3);
} // for
ptCurveLast = pt;
break;
default:
assert(false); // must be one of the two cases above
break;
} // switch
// Increment curve offset so point to next polycurve.
dwStructSize = sizeof(TTPOLYCURVE) + ((lpttpc->cpfx - 1)
* sizeof(POINTFX));
dwCurveOffset += dwStructSize;
} // inner while (polycurve)
// Close polygon by drawing a line back to the start point.
LineTo(hDC, ptPolyStart.x, ptPolyStart.y);
// Increment polygon offset so point to next polygon.
dwHeaderOffset += lpttph->cb;
} // outer while (polygon)
} // Draw()
- End of Figure -
Return to Article (towards start)
Return to Article (towards end)
Figure 4: The signature of GetGlyphOutline.
DWORD GetGlyphOutline(
HDC hdc, // handle of device context
UINT uChar, // character to query
UINT uFormat, // format of data to return
LPGLYPHMETRICS lpgm, // pointer to metrics struct
DWORD cbBuffer, // size of buffer for data
LPVOID lpvBuffer, // address of buffer for data
CONST MAT2 *lpmat2 // pointer to transform matrix
);
- End of Figure -
Return to Article
Figure 5: Transformation matrix for a rotation
through angle A (counter-clockwise rotation for a
positive angle), where the eMs are the fields of
GetGlyphOutline's lpmat2 parameter.
*- -* *- -* *- -*
| x' | | eM11=cos(A) eM21=-sin(A) | | x |
| | = | | * | |
| y' | | eM12=sin(A) eM22= cos(A) | | y |
*- -* *- -* *- -*
- End of Figure -
Return to Article
Figure 6: Quadratic Bezier curve determined by control
points p1 and p3 and handle point p2. Points q1, q2,
and r1 are the deCasteljau construction points, where
r1 is the midpoint of the curve.
Return to Article
Figure 7: Stitching two quadratic Bezier curves together
smoothly in a TrueType QSpline record. Points p2 and p2'
in the record are handles off the curve, but their
midpoint (average) is a common control.
Return to Article
Figure 8: Outer contour of a TrueType Times New Roman 'A' with
intermingled Line (blue) and QSpline (red) records.
Return to Article
Figure 9: Detail of left foot of 'A' showing two adjacent
QSpline records. QSpline3 consists of three points
defining two smoothly joined Bezier curves.
Return to Article
Figure 10: Steps to create an MFC Single Document Interface (SDI)
application using class CGlyph to put character outlines on the screen.
1) Use MFC AppWizard to create SDI app 'GlyphSDI'.
2) Copy glyph.cpp and glyph.h into the GlyphSDI directory and add
glyph.cpp to the project.
3) Towards the top of GlyphSDIDoc.h, just before the definition of
class CGlyphSDIDoc, add:
#include "glyph.h"
Also add a public CGlyph member variable inside the class:
// Attributes
public:
CGlyph m_glyph;
4) Add a public LOGFONT member variable inside the definition of
class CGlyphSDIView in GlyphSDIView.h:
// Attributes
public:
LOGFONT m_lf; // keeps track of current font properties
5) Add this code to the CGlyphSDIView constructor in GlyphSDIView.cpp:
CGlyphSDIView::CGlyphSDIView()
{
// TODO: add construction code here
// Initial font information.
memset(&m_lf, 0, sizeof(m_lf));
m_lf.lfPitchAndFamily = FF_ROMAN;
m_lf.lfHeight = 576;
m_lf.lfCharSet = ANSI_CHARSET;
m_lf.lfOutPrecision = OUT_TT_PRECIS;
strcpy(m_lf.lfFaceName, "Times New Roman");
}
6) Add this code to CGlyphSDIView's OnDraw() in GlyphSDIView.cpp:
void CGlyphSDIView::OnDraw(CDC* pDC)
{
CGlyphSDIDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
// First time through, realize 'B'.
if (!pDoc->m_glyph.IsRealized())
pDoc->m_glyph.Realize('B', *pDC, &m_lf);
// Draw the glyph.
pDoc->m_glyph.Draw(*pDC, 20, 20);
}
7) Use the class wizard to add an event handler to CGlyphSDIView
for the WM_CHAR message (call it OnChar()):
void CGlyphSDIView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: Add your message handler code here and/or call default
CGlyphSDIDoc* pDoc;
pDoc = GetDocument();
ASSERT_VALID(pDoc);
CDC *pDC = GetDC(); // need a DC to realize the glyph
// Must realize the glyph for change to take effect.
pDoc->m_glyph.Realize(nChar, *pDC, &m_lf);
// Redraw.
Invalidate();
}
- End of Figure -
Return to Article