Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

How to use a TrueType structure to render user-defined geometric shapes

0.00/5 (No votes)
3 Jan 2009 1  
To learn the usages of TrueType structures, TTPOLYGONHEADER and TTPOLYCURVE.

Introduction

The TrueType structure TTPOLYGONHEADER can be utilized to generate user-defined geometric shapes. The topic here is to demonstrate how a TrueType font is formatted with this structure.

As you may already know, a glyph contour consists of multiple curve segments represented by TTPOLYCURVE structures which are actually followed by TTPOLYGONHEADER. Therefore, we may just simply add each curve segment behind a polygon header in sequence without using the TTPOLYCURVE structure implicitly.

Background

The original idea is to study how TTPOLYGONHEADER and TTPOLYCURVE are used in TrueType font rendering. Furthermore, we can think about how to combine these geometric shapes with TrueType font characters. We will talk about this advanced topic in the next section.

TrueType Polygon Structure

Let's see the members of the TTPOLYGONHEADER and TTPOLYCURVE structures.

typedef struct _TTPOLYGONHEADER 
{ 
  DWORD   cb; 
  DWORD   dwType; 
  POINTFX pfxStart; 
} TTPOLYGONHEADER, *LPTTPOLYGONHEADER;
  • cb - Specifies the number of bytes required by the TTPOLYGONHEADER structure and TTPOLYCURVE structure or structures required to describe the contour of the character.
  • dwType - Specifies the type of character outline returned. Currently, this value must be TT_POLYGON_TYPE.
  • pfxStart - Specifies the starting point of the contour in the character outline.
typedef struct tagTTPOLYCURVE 
{ 
  WORD    wType; 
  WORD    cpfx; 
  POINTFX apfx[1]; 
} TTPOLYCURVE, *LPTTPOLYCURVE;
  • wType - Specifies the type of curve described by the structure.
  • cpfx - Specifies the number of POINTFX structures in the array.
  • apfx - Specifies an array of POINTFX structures that define the polyline or Bézier spline.

Getting started

We will only focus on how to generate customized geometric shapes with TTPOLYGONHEADER. Here is a code snippet to explain the ways to create a user-defined geometric shape.

LPTTPOLYGONHEADER CTTPolygonDlg::DemoPolygon(const PPLOYCURVE pPolycurve, int nCurveCount)
{
    LPTTPOLYGONHEADER lpPolygonHeader = NULL;
    LPBYTE lpPolyCurve = NULL;
    LPPOINTFX lpPointsFx = NULL;
    LPPOINTFX lpPointsInc = NULL;
    LPPOINTFX lpPointsEnd = NULL;
    DWORD dwOffsetAddr = 0L;
    UINT nSize = 0;
    UINT nPtBufSize = 0;
    UINT nPtCounts = 0;
    WORD wType = 0;
    WORD cpfx = 0;
    LPBYTE lpBytes = NULL;
    int i = 0;
    double* pDblVertex = NULL;
    double* pDblInc = NULL;

    if (0 >= nCurveCount || NULL == pPolycurve)
    {
        return( NULL );
    }

    for (i = 0; i < nCurveCount; i++)
    {
        if (1 >= (*(pPolycurve + i)).nVertexCount || 
            NULL == (*(pPolycurve + i)).pDblValues)
        {
            return( NULL );
        }

        if ((*(pPolycurve + i)).wCurveType != TT_PRIM_LINE && 
            (*(pPolycurve + i)).wCurveType != TT_PRIM_QSPLINE && 
            (*(pPolycurve + i)).wCurveType != TT_PRIM_CSPLINE)
        {
            return( NULL );
        }

        nPtBufSize += (sizeof( POINTFX ) * (*(pPolycurve + i)).nVertexCount);
    }

    //--- allocate buffer for polygon header ---
    nSize = 
    (
    sizeof( TTPOLYGONHEADER ) + 
    sizeof( TTPOLYCURVE ) +
    ((sizeof( WORD ) * 2) + nPtBufSize)
    );

    lpBytes = ::new BYTE[nSize];
    lpPolygonHeader = (LPTTPOLYGONHEADER)lpBytes;

    //--- setup header of polygon and its starting point ---
    lpPolygonHeader->cb = nSize;
    lpPolygonHeader->dwType = TT_POLYGON_TYPE;
    pDblVertex = (*pPolycurve).pDblValues;
    lpPolygonHeader->pfxStart.x = FixedFromDouble(*pDblVertex);
    lpPolygonHeader->pfxStart.y = FixedFromDouble(*(pDblVertex + 1));

    //--- get poly curve start address ---
    dwOffsetAddr = (DWORD)((LPBYTE)lpPolygonHeader + sizeof( TTPOLYGONHEADER ));

    //--- traverse all polycurves ---
    for (i = 0; i < nCurveCount; i++)
    {
        wType = (*(pPolycurve + i)).wCurveType;
        cpfx = (*(pPolycurve + i)).nVertexCount;

        ::CopyMemory((LPBYTE)dwOffsetAddr, &wType, sizeof( WORD ));
        dwOffsetAddr += sizeof( WORD );
        ::CopyMemory((LPBYTE)dwOffsetAddr, &cpfx, sizeof( WORD ));
        dwOffsetAddr += sizeof( WORD );

        //--- allocate buffer for current curve ---
        nPtBufSize = (sizeof( POINTFX ) * (*(pPolycurve + i)).nVertexCount);

        lpBytes = ::new BYTE[nPtBufSize];
        lpPointsFx = (LPPOINTFX)lpBytes;

        lpPointsInc = lpPointsFx;
        lpPointsEnd = (lpPointsInc + cpfx);
        pDblInc = (*(pPolycurve + i)).pDblValues;

        do
        {
            lpPointsInc->x = FixedFromDouble(*pDblInc++);
            lpPointsInc->y = FixedFromDouble(*pDblInc++);
        }
        while(++lpPointsInc < lpPointsEnd);

        //--- copy points to original structure ---
        ::CopyMemory((LPBYTE)dwOffsetAddr, lpPointsFx, nPtBufSize);
        dwOffsetAddr += nPtBufSize;

        //--- free temporary points ---
        delete []lpBytes;
        lpBytes = NULL;
    }

    //--- return polygon header ---
    return( lpPolygonHeader );
}

Here is the detailed explanation:

  1. We have to give the size to the polyline structure which depends on how many vertices the polyline has. And then, allocate a space for this structure.
  2. for (i = 0; i < nCurveCount; i++)
    {
        if (1 >= (*(pPolycurve + i)).nVertexCount || 
            NULL == (*(pPolycurve + i)).pDblValues)
        {
            return( NULL );
        }
    
        if ((*(pPolycurve + i)).wCurveType != TT_PRIM_LINE && 
            (*(pPolycurve + i)).wCurveType != TT_PRIM_QSPLINE && 
            (*(pPolycurve + i)).wCurveType != TT_PRIM_CSPLINE)
        {
            return( NULL );
        }
    
        nPtBufSize += (sizeof( POINTFX ) * (*(pPolycurve + i)).nVertexCount);
    }
    
    //--- allocate buffer for polygon header ---
    nSize = 
    (
    sizeof( TTPOLYGONHEADER ) + 
    sizeof( TTPOLYCURVE ) +
    ((sizeof( WORD ) * 2) + nPtBufSize)
    );
    
    lpBytes = ::new BYTE[nSize];
    lpPolygonHeader = (LPTTPOLYGONHEADER)lpBytes;
  3. Start the first vertex of the polyline, as shown below.
  4. //--- setup header of polygon and its starting point ---
    lpPolygonHeader->cb = nSize;
    lpPolygonHeader->dwType = TT_POLYGON_TYPE;
    pDblVertex = (*pPolycurve).pDblValues;
    lpPolygonHeader->pfxStart.x = FixedFromDouble(*pDblVertex);
    lpPolygonHeader->pfxStart.y = FixedFromDouble(*(pDblVertex + 1));
    
    //--- get poly curve start address ---
    dwOffsetAddr = (DWORD)((LPBYTE)lpPolygonHeader + sizeof( TTPOLYGONHEADER ));
  5. Give each curve the vertices. To define the TTPOLYCURVE structure:
    1. Set the polygon type.
    2. Give the vertex count to the polygon.
    3. Give the vertices to the polygon.
    //--- traverse all polycurves ---
    for (i = 0; i < nCurveCount; i++)
    {
        wType = (*(pPolycurve + i)).wCurveType;
        cpfx = (*(pPolycurve + i)).nVertexCount;
    
        ::CopyMemory((LPBYTE)dwOffsetAddr, &wType, sizeof( WORD ));
        dwOffsetAddr += sizeof( WORD );
        ::CopyMemory((LPBYTE)dwOffsetAddr, &cpfx, sizeof( WORD ));
        dwOffsetAddr += sizeof( WORD );
    
        //--- allocate buffer for current curve ---
        nPtBufSize = (sizeof( POINTFX ) * (*(pPolycurve + i)).nVertexCount);
    
        lpBytes = ::new BYTE[nPtBufSize];
        lpPointsFx = (LPPOINTFX)lpBytes;
    
        lpPointsInc = lpPointsFx;
        lpPointsEnd = (lpPointsInc + cpfx);
        pDblInc = (*(pPolycurve + i)).pDblValues;
    
        do
        {
            lpPointsInc->x = FixedFromDouble(*pDblInc++);
            lpPointsInc->y = FixedFromDouble(*pDblInc++);
        }
        while(++lpPointsInc < lpPointsEnd);
    
        //--- copy points to original structure ---
        ::CopyMemory((LPBYTE)dwOffsetAddr, lpPointsFx, nPtBufSize);
        dwOffsetAddr += nPtBufSize;
    
        //--- free temporary points ---
        delete []lpBytes;
        lpBytes = NULL;
    }
  6. We now can render the geometric object with this customized polygon header.
  7. //--- render geometry shape ---
    RenderPolygon(lpPolygonHeader, nSize);

Revisions

  • Version 1.0: Initial release.
  • Version 1.1: Multiple-shape support.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here