|
|
#include "precomp.h"
#pragma hdrstop
#include <math.h>
#include <GL\glu.h>
#include "batchinf.h"
#include "glteb.h"
#include "glapi.h"
#include "glsbcltu.h"
#include "fontoutl.h"
static OFContext* CreateOFContext( HDC hdc, FLOAT chordalDeviation, FLOAT extrusion, int type, BOOL bUnicode );
static BOOL ScaleFont( HDC hdc, OFContext* ofc, BOOL bUnicode );
static void DestroyOFContext( HDC hdc, OFContext* ofc );
static BOOL DrawGlyph( OFContext* ofc );
static BOOL MakeDisplayListFromGlyph( OFContext* ofc, DWORD listName, LPGLYPHMETRICS glyphMetrics );
static BOOL MakeLinesFromArc( OFContext* ofc, LOOP* pLoop, PRIM* pPrim, POINT2D p0, POINT2D p1, POINT2D p2, FLOAT chordalDeviationSquared);
static LOOP_LIST* MakeLinesFromGlyph( OFContext* ofc );
static BOOL MakeLinesFromTTLine( OFContext* ofc, LOOP* pLoop, PRIM* pPrim, UCHAR** pp, WORD pointCount );
static BOOL MakeLinesFromTTPolycurve( OFContext* ofc, LOOP* pLoop, UCHAR** pp );
static BOOL MakeLinesFromTTPolygon( OFContext* ofc, LOOP_LIST* pLoopList, UCHAR** pp );
static BOOL MakeLinesFromTTQSpline( OFContext* ofc, LOOP* pLoop, PRIM* pPrim, UCHAR** pp, WORD pointCount );
static void CALLBACK TessError( GLenum error, void *data);
static void CALLBACK TessCombine( GLdouble coord[3], POINT2D* data[4], GLfloat w[4], POINT2D** dataOut, void *userData);
static void FreeCombinePool( MEM_POOL *combinePool );
static void ApplyVertexFilter( LOOP_LIST *pLoopList );
static void CheckRedundantVertices( LOOP* pLoop );
static BOOL PointsColinear( POINT2D *p1, POINT2D *p2, POINT2D *p3 );
static FLOAT GetFixed( UCHAR** p );
static LOOP_LIST* InitLoopBuf( void );
static LOOP* NewLoop( LOOP_LIST *Loops, POINT2D *pFirstPoint );
static void FreeLoopList( LOOP_LIST *pLoopList );
static PRIM* NewPrim( LOOP *pLoop, DWORD primType );
static void CalcVertPtrs( LOOP *pLoop );
static BOOL AppendToVertBuf( LOOP* pLoop, PRIM* pPrim, POINT2D *p );
// macros to access data from byte streams:
// get WORD from byte stream, increment stream ptr by WORD
#define GetWord( p ) \
( *( ((UNALIGNED WORD *) *p)++ ) )
// get DWORD from byte stream, increment stream ptr by DWORD
#define GetDWord( p ) \
( *( ((UNALIGNED DWORD *) *p)++ ) )
// get signed word (SHORT) from byte stream, increment stream ptr by SHORT
#define GetSignedWord( p ) \
( *( ((UNALIGNED SHORT *) *p)++ ) )
#define POINT2DEQUAL( p1, p2 ) \
( (p1->x == p2->x) && (p1->y == p2->y) )
/******************************Public*Routine******************************\
* wglUseFontOutlinesA * wglUseFontOutlinesW * * Stubs that call wglUseFontOutlinesAW with the bUnicode flag set * appropriately. * \**************************************************************************/
BOOL WINAPI wglUseFontOutlinesAW( HDC hDC, DWORD first, DWORD count, DWORD listBase, FLOAT chordalDeviation, FLOAT extrusion, int format, LPGLYPHMETRICSFLOAT lpgmf, BOOL bUnicode );
BOOL WINAPI wglUseFontOutlinesA( HDC hDC, DWORD first, DWORD count, DWORD listBase, FLOAT chordalDeviation, FLOAT extrusion, int format, LPGLYPHMETRICSFLOAT lpgmf ) { return wglUseFontOutlinesAW( hDC, first, count, listBase, chordalDeviation, extrusion, format, lpgmf, FALSE ); }
BOOL WINAPI wglUseFontOutlinesW( HDC hDC, DWORD first, DWORD count, DWORD listBase, FLOAT chordalDeviation, FLOAT extrusion, int format, LPGLYPHMETRICSFLOAT lpgmf ) { return wglUseFontOutlinesAW( hDC, first, count, listBase, chordalDeviation, extrusion, format, lpgmf, TRUE ); }
/*****************************************************************************
* wglUseFontOutlinesAW * * Converts a subrange of the glyphs in a TrueType font to OpenGL display * lists. * * History: * 15-Dec-1994 -by- Marc Fortier [marcfo] * Wrote it. *****************************************************************************/
BOOL WINAPI wglUseFontOutlinesAW( HDC hDC, DWORD first, DWORD count, DWORD listBase, FLOAT chordalDeviation, FLOAT extrusion, int format, LPGLYPHMETRICSFLOAT lpgmf, BOOL bUnicode ) { DWORD glyphIndex; DWORD listIndex = listBase; UCHAR* glyphBuf; DWORD glyphBufSize, error; OFContext* ofc; BOOL status=WFO_FAILURE;
// Return error if there is no current RC.
if (!GLTEB_CLTCURRENTRC()) { WARNING("wglUseFontOutlines: no current RC\n"); SetLastError(ERROR_INVALID_HANDLE); return status; }
/*
* Flush any previous OpenGL errors. This allows us to check for * new errors so they can be reported. */ while (glGetError() != GL_NO_ERROR) ;
/*
* Preallocate a buffer for the outline data, and track its size: */ // XXX: do we need to start with such a big size for this buffer ?
glyphBuf = (UCHAR*) ALLOC(glyphBufSize = 10240); if (!glyphBuf) { WARNING("Alloc of glyphBuf failed\n"); return status; }
/*
* Create font outline context */ ofc = CreateOFContext( hDC, chordalDeviation, extrusion, format, bUnicode ); if( !ofc ) { WARNING("CreateOFContext failed\n"); goto exit; }
/*
* Process each glyph in the given range: */ for (glyphIndex = first; glyphIndex - first < count; ++glyphIndex) { GLYPHMETRICS glyphMetrics; DWORD glyphSize; static MAT2 matrix = { {0, 1}, {0, 0}, {0, 0}, {0, 1} };
/*
* Determine how much space is needed to store the glyph's * outlines. If our glyph buffer isn't large enough, * resize it. */ if( bUnicode ) glyphSize = GetGlyphOutlineW( hDC, glyphIndex, GGO_NATIVE, &glyphMetrics, 0, NULL, &matrix ); else glyphSize = GetGlyphOutlineA( hDC, glyphIndex, GGO_NATIVE, &glyphMetrics, 0, NULL, &matrix );
if( glyphSize == GDI_ERROR ) { WARNING("GetGlyphOutline() failed\n"); goto exit; }
if (glyphSize > glyphBufSize) { FREE(glyphBuf); glyphBuf = (UCHAR*) ALLOC(glyphBufSize = glyphSize); if (!glyphBuf) { WARNING("Alloc of glyphBuf failed\n"); goto exit; } }
/*
* Get the glyph's outlines. */ if( bUnicode ) error = GetGlyphOutlineW( hDC, glyphIndex, GGO_NATIVE, &glyphMetrics, glyphBufSize, glyphBuf, &matrix ); else error = GetGlyphOutlineA( hDC, glyphIndex, GGO_NATIVE, &glyphMetrics, glyphBufSize, glyphBuf, &matrix );
if( error == GDI_ERROR ) { WARNING("GetGlyphOutline() failed\n"); goto exit; }
/*
* Turn the glyph into a display list: */ ofc->glyphBuf = glyphBuf; ofc->glyphSize = glyphSize;
if (!MakeDisplayListFromGlyph( ofc, listIndex, &glyphMetrics)) { WARNING("MakeDisplayListFromGlyph() failed\n"); listIndex++; // so it will be deleted
goto exit; }
/*
* Supply scaled glyphMetrics if requested */ if( lpgmf ) { lpgmf->gmfBlackBoxX = ofc->scale * (FLOAT) glyphMetrics.gmBlackBoxX; lpgmf->gmfBlackBoxY = ofc->scale * (FLOAT) glyphMetrics.gmBlackBoxY; lpgmf->gmfptGlyphOrigin.x = ofc->scale * (FLOAT) glyphMetrics.gmptGlyphOrigin.x; lpgmf->gmfptGlyphOrigin.y = ofc->scale * (FLOAT) glyphMetrics.gmptGlyphOrigin.y; lpgmf->gmfCellIncX = ofc->scale * (FLOAT) glyphMetrics.gmCellIncX; lpgmf->gmfCellIncY = ofc->scale * (FLOAT) glyphMetrics.gmCellIncY;
lpgmf++; }
listIndex++; }
// Set status to SUCCESS if we get this far
status = WFO_SUCCESS;
/*
* Clean up temporary storage and return. If an error occurred, * set error flags and return FAILURE status; * otherwise just return SUCCESS. */
exit: if( glyphBuf ) FREE(glyphBuf);
if( ofc ) DestroyOFContext( hDC, ofc);
if( !status ) { // assume memory error
WARNING("wglUseFontOutlines: not enough memory\n"); SetLastError(ERROR_NOT_ENOUGH_MEMORY);
// free up display lists
glDeleteLists( listBase, listIndex-listBase ); }
return status; }
/*****************************************************************************
* MakeDisplayListFromGlyph * * Converts the outline of a glyph to an OpenGL display list. * * Return value is nonzero for success, zero for failure. * * Does not check for OpenGL errors, so if the caller needs to know about them, * it should call glGetError().
*****************************************************************************/
static BOOL MakeDisplayListFromGlyph( IN OFContext* ofc, IN DWORD listName, IN LPGLYPHMETRICS glyphMetrics ) { BOOL status;
glNewList(listName, GL_COMPILE); /*
* Set normal and orientation for front face of glyph */ glNormal3f( 0.0f, 0.0f, 1.0f ); glFrontFace( GL_CCW );
status = DrawGlyph( ofc );
/*
* Translate by gmCellIncX, gmCellIncY */ glTranslatef( ofc->scale * (FLOAT) glyphMetrics->gmCellIncX, ofc->scale * (FLOAT) glyphMetrics->gmCellIncY, 0.0f ); glEndList();
// Check for GL errors occuring during processing of the glyph
while( glGetError() != GL_NO_ERROR ) status = WFO_FAILURE;
return status; }
/*****************************************************************************
* DrawGlyph * * Converts the outline of a glyph to OpenGL drawing primitives, tessellating * as needed, and then draws the glyph. Tessellation of the quadratic splines * in the outline is controlled by "chordalDeviation", and the drawing * primitives (lines or polygons) are selected by "format". * * Return value is nonzero for success, zero for failure. * * Does not check for OpenGL errors, so if the caller needs to know about them, * it should call glGetError().
* History: * 26-Sep-1995 -by- Marc Fortier [marcfo] * Use extrusioniser to draw polygonal faces with extrusion=0
*****************************************************************************/
static BOOL DrawGlyph( IN OFContext *ofc ) { BOOL status = WFO_FAILURE; DWORD nLoops; DWORD point; DWORD nVerts; LOOP_LIST *pLoopList; LOOP *pLoop; POINT2D *p; MEM_POOL *mp = NULL;
/*
* Convert the glyph outlines to a set of polyline loops. * (See MakeLinesFromGlyph() for the format of the loop data * structure.) */ if( !(pLoopList = MakeLinesFromGlyph(ofc)) ) goto exit;
/*
* Filter out unnecessary vertices */ ApplyVertexFilter( pLoopList );
/*
* Now draw the loops in the appropriate format: */ if( ofc->format == WGL_FONT_LINES ) { /*
* This is the easy case. Just draw the outlines. */ nLoops = pLoopList->nLoops; pLoop = pLoopList->LoopBuf; #ifndef FONT_DEBUG
for( ; nLoops; nLoops--, pLoop++ ) { glBegin(GL_LINE_LOOP);
nVerts = pLoop->nVerts; p = pLoop->VertBuf; for( ; nVerts; nVerts--, p++ ) { glVertex2fv( (FLOAT*) p ); }
glEnd();
} #else
// color code the primitives
for( ; nLoops; nLoops--, pLoop++ ) { DrawColorCodedLineLoop( pLoop, 0.0f ); } #endif
if( ofc->ec ) extr_DrawLines( ofc->ec, pLoopList ); status = WFO_SUCCESS; }
else if (ofc->format == WGL_FONT_POLYGONS) { GLdouble v[3];
/*
* This is the hard case. We have to set up a tessellator * to convert the outlines into a set of polygonal * primitives, which the tessellator passes to some * auxiliary routines for drawing. */
/* Initialize polygon extrusion for the glyph.
* This prepares for tracking of the tesselation in order to * build the Back-facing polygons. */
mp = &ofc->combinePool; ofc->curCombinePool = mp; mp->index = 0; mp->next = NULL;
if( ofc->ec ) { if( !extr_PolyInit( ofc->ec ) ) goto exit;
}
ofc->TessErrorOccurred = 0; v[2] = 0.0; gluTessBeginPolygon( ofc->tess, ofc );
/*
* Each loop returned from MakeLinesFromGlyph is closed (first and * last points are the same). The old tesselator had trouble with * this. Since the tesselator automatically closes all loops, * we skip the last point to be on the safe side. */
nLoops = pLoopList->nLoops; pLoop = pLoopList->LoopBuf; for( ; nLoops; nLoops--, pLoop++ ) { gluTessBeginContour( ofc->tess ); nVerts = pLoop->nVerts - 1; // skip last point
p = pLoop->VertBuf; for( ; nVerts; nVerts--, p++ ) { v[0] = p->x; v[1] = p->y; gluTessVertex(ofc->tess, v, p); } gluTessEndContour( ofc->tess ); }
gluTessEndPolygon( ofc->tess );
if (ofc->TessErrorOccurred) goto exit;
if( ofc->ec ) { /* check for OUT_OF_MEMORY_ERROR in extrusion lib, that might
* have occured during tesselation tracking. */ if( ofc->ec->TessErrorOccurred ) goto exit; #ifdef VARRAY
if( ofc->ec->zExtrusion == 0.0f ) DrawFacePolygons( ofc->ec, 0.0f ); else if( !extr_DrawPolygons( ofc->ec, pLoopList ) ) goto exit; #else
if( !extr_DrawPolygons( ofc->ec, pLoopList ) ) goto exit; #endif
} status = WFO_SUCCESS; }
exit: /*
* Putting PolyFinish here means PolyInit may not have been called. * This is ok. */ if( mp ) FreeCombinePool( mp ); if( pLoopList ) FreeLoopList( pLoopList ); if( ofc->ec ) extr_PolyFinish( ofc->ec );
return status; }
/*****************************************************************************
* TessCombine * * Tesselation callback for loop intersection. We have to allocate a vertex * and return it to tesselator. Allocation is from the context's static pool. * If this runs dry, then a linked list of MEM_POOL blocks is used.
*****************************************************************************/ static void CALLBACK TessCombine( GLdouble coord[3], POINT2D *data[4], GLfloat w[4], POINT2D **dataOut, void *userData ) { OFContext *ofc = (OFContext *) userData; MEM_POOL *mp = ofc->curCombinePool; POINT2D *p;
// make sure there's room available in the current pool block
if( mp->index >= POOL_SIZE ) { // we need to allocate another MEM_POOL block
MEM_POOL *newPool;
newPool = (MEM_POOL *) ALLOC( sizeof(MEM_POOL) ); if( !newPool ) // tesselator will handle any problem with this
return;
newPool->index = 0; newPool->next = NULL; mp->next = newPool; mp = newPool; ofc->curCombinePool = mp; // new pool becomes the current pool
}
p = mp->pool + mp->index; p->x = (GLfloat) coord[0]; p->y = (GLfloat) coord[1]; mp->index ++;
*dataOut = p; }
/*****************************************************************************
* FreeCombinePool * * Frees any pools of memory allocated by TessCombine callback
*****************************************************************************/ static void FreeCombinePool( MEM_POOL *memPool ) { MEM_POOL *nextPool;
memPool = memPool->next; // first pool in list is static part of context
while( memPool ) { nextPool = memPool->next; FREE( memPool ); memPool = nextPool; } }
/*****************************************************************************
* TessError * * Saves the last tessellator error code in ofc->TessErrorOccurred.
*****************************************************************************/ static void CALLBACK TessError(GLenum error, void *data) { OFContext *ofc = (OFContext *) data;
// Only some of these errors are fatal:
switch( error ) { case GLU_TESS_COORD_TOO_LARGE: case GLU_TESS_NEED_COMBINE_CALLBACK: ofc->TessErrorOccurred = error; break; default: break; } }
/*****************************************************************************
* MakeLinesFromGlyph * * Converts the outline of a glyph from the TTPOLYGON format into * structures of Loops, Primitives and Vertices. * * Line segments from the TTPOLYGON are transferred to the output array in * the obvious way. Quadratic splines in the TTPOLYGON are converted to * collections of line segments
*****************************************************************************/
static LOOP_LIST* MakeLinesFromGlyph( IN OFContext* ofc ) { UCHAR* p; BOOL status = WFO_FAILURE; LOOP_LIST *pLoopList;
/*
* Initialize the buffer into which we place the loop data: */ if( !(pLoopList = InitLoopBuf()) ) return NULL;
p = ofc->glyphBuf; while (p < ofc->glyphBuf + ofc->glyphSize) { if( !MakeLinesFromTTPolygon( ofc, pLoopList, &p) ) goto exit; }
status = WFO_SUCCESS;
exit: if (!status) { FreeLoopList( pLoopList ); pLoopList = (LOOP_LIST *) NULL; } return pLoopList; }
/*****************************************************************************
* MakeLinesFromTTPolygon * * Converts a TTPOLYGONHEADER and its associated curve structures into a * LOOP structure.
*****************************************************************************/
static BOOL MakeLinesFromTTPolygon( IN OFContext* ofc, IN LOOP_LIST* pLoopList, IN OUT UCHAR** pp) { DWORD polySize; UCHAR* polyStart; POINT2D *pFirstP, *pLastP, firstPoint; LOOP *pLoop; PRIM *pPrim;
/*
* Record where the polygon data begins. */ polyStart = *pp;
/*
* Extract relevant data from the TTPOLYGONHEADER: */ polySize = GetDWord(pp); if( GetDWord(pp) != TT_POLYGON_TYPE ) /* polygon type */ return WFO_FAILURE; firstPoint.x = ofc->scale * GetFixed(pp); // 1st X coord
firstPoint.y = ofc->scale * GetFixed(pp); // 1st Y coord
/*
* Initialize a new LOOP struct in the LoopBuf, with the first point */ if( !(pLoop = NewLoop( pLoopList, &firstPoint )) ) return WFO_FAILURE; /*
* Process each of the TTPOLYCURVE structures in the polygon: */
while (*pp < polyStart + polySize) { if( !MakeLinesFromTTPolycurve( ofc, pLoop, pp ) ) return WFO_FAILURE; }
/* Now have to fix up end of loop : after studying the chars, it
* was determined that if a curve started with a line, and ended with * a qspline, AND the first and last point were not the same, then there * is an implied line joining the two. * In any case, we also make sure here that first and last points are * coincident. */ pLastP = (POINT2D *) (pLoop->VertBuf+pLoop->nVerts-1); pFirstP = &firstPoint;
if( !POINT2DEQUAL( pLastP, pFirstP ) ) { // add 1-vertex line prim at the end
if( !(pPrim = NewPrim( pLoop, TT_PRIM_LINE)) ) return WFO_FAILURE;
if ( !AppendToVertBuf( pLoop, pPrim, pFirstP) ) return WFO_FAILURE; }
/* At end of each loop, calculate pVert for each PRIM from its
* VertIndex value (for convenience later). */ CalcVertPtrs( pLoop );
return WFO_SUCCESS; }
/*****************************************************************************
* MakeLinesFromTTPolyCurve * * Converts the lines and splines in a single TTPOLYCURVE structure to points * in the Loop.
*****************************************************************************/
static BOOL MakeLinesFromTTPolycurve( IN OFContext* ofc, IN LOOP* pLoop, IN OUT UCHAR** pp ) { WORD type; WORD pointCount; PRIM *pPrim;
/*
* Pick up the relevant fields of the TTPOLYCURVE structure: */ type = GetWord(pp); pointCount = GetWord(pp);
if( !(pPrim = NewPrim( pLoop, type )) ) return WFO_FAILURE;
/*
* Convert the "curve" to line segments: */ if (type == TT_PRIM_LINE) { return MakeLinesFromTTLine( ofc, pLoop, pPrim, pp, pointCount);
} else if (type == TT_PRIM_QSPLINE) { return MakeLinesFromTTQSpline( ofc, pLoop, pPrim, pp, pointCount );
} else return WFO_FAILURE; }
/*****************************************************************************
* MakeLinesFromTTLine * * Converts points from the polyline in a TT_PRIM_LINE structure to * equivalent points in the Loop.
*****************************************************************************/ static BOOL MakeLinesFromTTLine( IN OFContext* ofc, IN LOOP* pLoop, IN PRIM* pPrim, IN OUT UCHAR** pp, IN WORD pointCount) { POINT2D p;
/*
* Just copy the line segments into the vertex buffer (converting * type as we go): */
while (pointCount--) { p.x = ofc->scale * GetFixed(pp); // X coord
p.y = ofc->scale * GetFixed(pp); // Y coord
if( !AppendToVertBuf( pLoop, pPrim, &p ) ) return WFO_FAILURE; }
return WFO_SUCCESS; }
/*****************************************************************************
* MakeLinesFromTTQSpline * * Converts points from the poly quadratic spline in a TT_PRIM_QSPLINE * structure to polyline points in the Loop.
*****************************************************************************/
static BOOL MakeLinesFromTTQSpline( IN OFContext* ofc, IN LOOP* pLoop, IN PRIM* pPrim, IN OUT UCHAR** pp, IN WORD pointCount ) { POINT2D p0, p1, p2; WORD point; POINT2D p, *pLastP;
/*
* Process each of the non-interpolated points in the outline. * To do this, we need to generate two interpolated points (the * start and end of the arc) for each non-interpolated point. * The first interpolated point is always the one most recently * stored in VertBuf, so we just extract it from there. The * second interpolated point is either the average of the next * two points in the QSpline, or the last point in the QSpline * if only one remains. */
// Start with last generated point in VertBuf
p0 = *(pLoop->VertBuf + pLoop->nVerts - 1);
// pointCount should be >=2, but in case it's not...
p1 = p2 = p0;
for (point = 0; point < pointCount - 1; ++point) { p1.x = ofc->scale * GetFixed(pp); p1.y = ofc->scale * GetFixed(pp);
if (point == pointCount - 2) { /*
* This is the last arc in the QSpline. The final * point is the end of the arc. */ p2.x = ofc->scale * GetFixed(pp); p2.y = ofc->scale * GetFixed(pp); } else { /*
* Peek at the next point in the input to compute * the end of the arc: */ p.x = ofc->scale * GetFixed(pp); p.y = ofc->scale * GetFixed(pp); p2.x = 0.5f * (p1.x + p.x); p2.y = 0.5f * (p1.y + p.y); /*
* Push the point back onto the input so it will * be reused as the next off-curve point: */ *pp -= 2*sizeof(FIXED); // x and y
}
if( !MakeLinesFromArc( ofc, pLoop, pPrim, p0, p1, p2, ofc->chordalDeviation * ofc->chordalDeviation)) return WFO_FAILURE;
// p0 is now the last interpolated point (p2)
p0 = p2; }
// put in last point in arc
if( !AppendToVertBuf( pLoop, pPrim, &p2 ) ) return WFO_FAILURE;
return WFO_SUCCESS; }
/*****************************************************************************
* MakeLinesFromArc * * Subdivides one arc of a quadratic spline until the chordal deviation * tolerance requirement is met, then places the resulting set of line * segments in the Loop.
*****************************************************************************/
static BOOL MakeLinesFromArc( IN OFContext *ofc, IN LOOP* pLoop, IN PRIM* pPrim, IN POINT2D p0, IN POINT2D p1, IN POINT2D p2, IN FLOAT chordalDeviationSquared) { POINT2D p01; POINT2D p12; POINT2D midPoint; FLOAT deltaX; FLOAT deltaY;
/*
* Calculate midpoint of the curve by de Casteljau: */ p01.x = 0.5f * (p0.x + p1.x); p01.y = 0.5f * (p0.y + p1.y); p12.x = 0.5f * (p1.x + p2.x); p12.y = 0.5f * (p1.y + p2.y); midPoint.x = 0.5f * (p01.x + p12.x); midPoint.y = 0.5f * (p01.y + p12.y);
/*
* Estimate chordal deviation by the distance from the midpoint * of the curve to its non-interpolated control point. If this * distance is greater than the specified chordal deviation * constraint, then subdivide. Otherwise, generate polylines * from the three control points. */ deltaX = midPoint.x - p1.x; deltaY = midPoint.y - p1.y; if (deltaX * deltaX + deltaY * deltaY > chordalDeviationSquared) { if( !MakeLinesFromArc( ofc, pLoop, pPrim, p0, p01, midPoint, chordalDeviationSquared) ) return WFO_FAILURE;
if( !MakeLinesFromArc( ofc, pLoop, pPrim, midPoint, p12, p2, chordalDeviationSquared) ) return WFO_FAILURE; } else { /*
* The "pen" is already at (x0, y0), so we don't need to * add that point to the LineBuf. */ if( !AppendToVertBuf( pLoop, pPrim, &p1 ) ) return WFO_FAILURE; }
return WFO_SUCCESS; }
/*****************************************************************************
* ApplyVertexFilter * * Filter the vertex buffer to get rid of redundant vertices. * These can occur on Primitive boundaries.
*****************************************************************************/ static void ApplyVertexFilter( LOOP_LIST *pLoopList ) { DWORD nLoops; LOOP *pLoop;
nLoops = pLoopList->nLoops; pLoop = pLoopList->LoopBuf;
for( ; nLoops; nLoops--, pLoop++ ) { CheckRedundantVertices( pLoop ); } }
/*****************************************************************************
* CheckRedundantVertices * * Check for redundant vertices on Curve-Curve boundaries (including loop * closure), and get rid of them, using in-place algorithm.
*****************************************************************************/
static void CheckRedundantVertices( LOOP *pLoop ) { PRIM *pPrim, *pNextPrim; DWORD primType, nextPrimType, nVerts; BOOL bEliminate, bLastEliminate; DWORD nEliminated=0, nPrims; POINT2D *pVert, *pVert2ndToLast; nPrims = pLoop->nPrims; if( nPrims < 2 ) return;
pPrim = pLoop->PrimBuf; pNextPrim = pPrim + 1; nPrims--; // the last prim is dealt with afterwards
for( ; nPrims; nPrims--, pPrim = pNextPrim++ ) { bEliminate = FALSE; nVerts = pPrim->nVerts;
// check spline<->* boundaries
if( (pPrim->nVerts >= 2) && ((pPrim->primType == PRIM_CURVE ) || (pNextPrim->primType == PRIM_CURVE )) ) {
/* get ptr to 2nd-to-last vertex in current prim
* !! Note that last vertex in current prim and first vertex in * next prim are the same. */ pVert2ndToLast = pPrim->pVert + pPrim->nVerts - 2; if( PointsColinear( pVert2ndToLast, pVert2ndToLast+1, pNextPrim->pVert+1 ) ) { // we eliminate last vertex in current prim
bEliminate = TRUE; pPrim->nVerts--; nVerts--; } }
/* move vertices up in vertBuf if necessary (if any vertices
* were PREVIOUSLY eliminated) */ if( nEliminated ) { pVert = pPrim->pVert - nEliminated; // new pVert
memcpy( pVert+1, pPrim->pVert+1, (nVerts-1)*sizeof(POINT2D)); pPrim->pVert = pVert; } if( bEliminate ) { nEliminated += 1; } }
/* also check for redundancy at closure:
* - replace firstPrim's first vertex with 2nd-to-last of last prim * - eliminate last vertex in last prim */ bLastEliminate = bEliminate; bEliminate = FALSE; nVerts = pPrim->nVerts; pNextPrim = pLoop->PrimBuf; // first prim in loop
if( (pPrim->nVerts >= 2) && ((pPrim->primType == PRIM_CURVE ) || (pNextPrim->primType == PRIM_CURVE )) ) {
POINT2D *pVertLast;
pVert2ndToLast = pPrim->pVert + pPrim->nVerts - 2; // always >=2 verts
pVertLast = pVert2ndToLast + 1;
if( (pPrim->nVerts == 2) && bLastEliminate ) /* 2ndToLast vert (same as first vert) of this prim has
* been eliminated. Deal with it by backing up the ptr. * This didn't matter in above loop, because there wasn't the * possibility of munging the first vertex in the loop */ pVert2ndToLast--;
// point to 2nd-to-last vertex in prim
if( PointsColinear( pVert2ndToLast, pVertLast, pNextPrim->pVert+1 ) ) { bEliminate = TRUE; pPrim->nVerts--; // munge first prim's first vertex
/* problem here if have 2 eliminations in a row, and pPrim was
* a 2 vertex prim - then pVert2ndToLast is pointing to an * eliminated vertex */ *(pNextPrim->pVert) = *(pVert2ndToLast); nVerts--; } }
// move up last prim's vertices if necessary
if( nEliminated ) { pVert = pPrim->pVert - nEliminated; // new pVert
memcpy( pVert+1, pPrim->pVert+1, (nVerts-1)*sizeof(POINT2D) ); // This misses copying one vertex
pPrim->pVert = pVert; }
if( bEliminate ) { nEliminated += 1; }
// now update vertex count in Loop
pLoop->nVerts -= nEliminated;
// Check for prims with nVerts=1 (invalidated), and remove them
nPrims = pLoop->nPrims; pPrim = pLoop->PrimBuf; nEliminated = 0; for( ; nPrims; nPrims--, pPrim++ ) { if( pPrim->nVerts == 1 ) { nEliminated++; continue; } *(pPrim-nEliminated) = *pPrim; } pLoop->nPrims -= nEliminated; }
/*****************************************************************************
* PointsColinear * * Returns TRUE if the 3 points are colinear enough.
*****************************************************************************/
static BOOL PointsColinear( POINT2D *p1, POINT2D *p2, POINT2D *p3 ) { POINT2D v1, v2;
// compare slopes of the 2 vectors? - optimize later
if( POINT2DEQUAL( p1, p2 ) || POINT2DEQUAL( p2, p3 ) ) // avoid sending 0 vector to CalcAngle (generates FPE)
return TRUE;
v1.x = p2->x - p1->x; v1.y = p2->y - p1->y; v2.x = p3->x - p2->x; v2.y = p3->y - p2->y; if( fabs(CalcAngle( &v1, &v2 )) < CoplanarThresholdAngle ) return TRUE;
return FALSE; }
/*****************************************************************************
* CreateOFContext * * Create and initialize the outline font context. * * History: * 26-Sep-1995 -by- Marc Fortier [marcfo] * Use extrusioniser to draw polygonal faces with extrusion=0
*****************************************************************************/
static OFContext* CreateOFContext( HDC hdc, FLOAT chordalDeviation, FLOAT extrusion, INT format, BOOL bUnicode ) { OFContext *ofc = (OFContext *) NULL; BOOL status = WFO_FAILURE;
// validate parameters
if( (format != WGL_FONT_LINES) && (format != WGL_FONT_POLYGONS) ) { WARNING("wglUseFontOutlines: invalid format parameter\n"); SetLastError(ERROR_INVALID_PARAMETER); return NULL; }
if( chordalDeviation < 0.0f ) { WARNING("wglUseFontOutlines: invalid deviation parameter\n"); SetLastError(ERROR_INVALID_PARAMETER); return NULL; }
if( extrusion < 0.0f ) { WARNING("wglUseFontOutlines: invalid extrusion parameter\n"); SetLastError(ERROR_INVALID_PARAMETER); return NULL; }
ofc = (OFContext *) ALLOCZ( sizeof(OFContext) );
if( !ofc ) return NULL;
ofc->format = format; ofc->chordalDeviation = chordalDeviation;
if( !ScaleFont( hdc, ofc, bUnicode ) ) goto exit;
// handle extrusion
#ifdef VARRAY
if( !((format == WGL_FONT_LINES) && (extrusion == 0.0f)) ) { #else
if( extrusion != 0.0f ) { #endif
ofc->ec = extr_Init( extrusion, format ); if( !ofc->ec ) { goto exit; } } else { ofc->ec = (EXTRContext *) NULL; }
// init a tess obj
ofc->tess = NULL; if( ofc->format == WGL_FONT_POLYGONS ) { GLUtesselator *tess;
if (!(tess = gluNewTess())) goto exit;
if( ofc->ec ) { gluTessCallback(tess, GLU_TESS_BEGIN_DATA, (void(CALLBACK*)()) extr_glBegin); gluTessCallback(tess, GLU_TESS_END, (void(CALLBACK*)()) extr_glEnd); gluTessCallback(tess, GLU_TESS_VERTEX_DATA, (void(CALLBACK*)()) extr_glVertex); } else { gluTessCallback(tess, GLU_BEGIN, (void(CALLBACK*)()) glBegin); gluTessCallback(tess, GLU_END, (void(CALLBACK*)()) glEnd); gluTessCallback(tess, GLU_VERTEX, (void(CALLBACK*)()) glVertex2fv); } gluTessCallback(tess, GLU_TESS_ERROR_DATA, (void(CALLBACK*)()) TessError); gluTessCallback(tess, GLU_TESS_COMBINE_DATA, (void(CALLBACK*)()) TessCombine);
// set tesselator normal and winding rule
gluTessNormal( tess, 0.0, 0.0, 1.0 ); gluTessProperty( tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO);
ofc->tess = tess; }
status = WFO_SUCCESS;
exit: if( !status ) { DestroyOFContext( hdc, ofc ); return NULL; } return ofc; }
/*****************************************************************************
* ScaleFont * * To get the best representation of the font, we use its design height, or * the emSquare size. We then scale emSquare to 1.0. * A maxChordTolerance value is set, otherwise it was found that some * glyphs displayed ugly loop intersections. The value .035f was chosen * after cursory examination of the glyphs. * * History: * 31-Jul-1995 -by- [marcfo] * Get rid of unicode functions - since we're just accessing text metrics, * the default 'string' functions should work on all platforms. *****************************************************************************/
static BOOL ScaleFont( HDC hdc, OFContext *ofc, BOOL bUnicode ) { OUTLINETEXTMETRIC otm; HFONT hfont; LOGFONT lf; DWORD textMetricsSize; FLOAT scale, maxChordTolerance=0.035f; UINT otmEMSquare;
// Query font metrics
if( GetOutlineTextMetrics( hdc, sizeof(otm), &otm) <= 0 ) // cmd failed, or buffer size=0
return WFO_FAILURE;
otmEMSquare = otm.otmEMSquare;
/*
* The font data is scaled, so that 1.0 maps to the font's em square * size. Note that it is still possible for glyphs to extend beyond * this square. */ scale = 1.0f / (FLOAT) otmEMSquare;
// create new font object, using largest size
hfont = GetCurrentObject( hdc, OBJ_FONT ); GetObject( hfont, sizeof(LOGFONT), &lf ); lf.lfHeight = otmEMSquare; lf.lfWidth = 0; // this will choose default width for the height
hfont = CreateFontIndirect(&lf);
// select new font into DC, and save current font
ofc->hfontOld = SelectObject( hdc, hfont );
// set ofc values
ofc->scale = scale;
/* check chord tolerance: in design space, minimum chord tolerance is
* ~1 logical unit, = ofc->scale. */ if( ofc->chordalDeviation == 0.0f ) { // select minimum tolerance in this case
ofc->chordalDeviation = ofc->scale; } /* also impose a maximum, or things can get ugly */ else if( ofc->chordalDeviation > maxChordTolerance ) { // XXX might want to change maxChordTolerance based on scale ?
ofc->chordalDeviation = maxChordTolerance; }
return WFO_SUCCESS; }
/*****************************************************************************
* DestroyOFContext * *****************************************************************************/
static void DestroyOFContext( HDC hdc, OFContext* ofc ) { HFONT hfont;
if( ofc->ec ) { extr_Finish( ofc->ec ); }
// put back original font object
if( ofc->hfontOld ) { hfont = SelectObject( hdc, ofc->hfontOld ); DeleteObject( hfont ); }
if( ofc->format == WGL_FONT_POLYGONS ) { if( ofc->tess ) gluDeleteTess( ofc->tess ); }
FREE( ofc ); }
/*****************************************************************************
* InitLoopBuf * * Initializes a LOOP_LIST structure for the Loops of each glyph.
*****************************************************************************/
static LOOP_LIST* InitLoopBuf( void ) { LOOP *pLoop; LOOP_LIST *pLoopList; DWORD initSize = 10;
pLoopList = (LOOP_LIST*) ALLOC( sizeof(LOOP_LIST) ); if( !pLoopList ) return( (LOOP_LIST *) NULL );
pLoop = (LOOP*) ALLOC( initSize * sizeof(LOOP) ); if( !pLoop ) { FREE( pLoopList ); return( (LOOP_LIST *) NULL ); }
pLoopList->LoopBuf = pLoop; pLoopList->nLoops = 0; pLoopList->LoopBufSize = initSize;
return pLoopList; }
/*****************************************************************************
* NewLoop * * Create a new LOOP structure. The first point in the loop is supplied.
*****************************************************************************/
static LOOP* NewLoop( LOOP_LIST *pLoopList, POINT2D *pFirstPoint ) { LOOP *pNewLoop; PRIM *pPrim; POINT2D *pVert; DWORD size = 50;
if( pLoopList->nLoops >= pLoopList->LoopBufSize) { // need to increase size of LoopBuf
LOOP *pLoop;
pLoop = (LOOP*) REALLOC(pLoopList->LoopBuf, (pLoopList->LoopBufSize += size) * sizeof(LOOP)); if( !pLoop ) return (LOOP *) NULL; pLoopList->LoopBuf = pLoop; }
pNewLoop = pLoopList->LoopBuf + pLoopList->nLoops;
// give the loop a block of prims to work with
pPrim = (PRIM *) ALLOC( size * sizeof(PRIM) ); if( !pPrim ) return (LOOP *) NULL; pNewLoop->PrimBuf = pPrim; pNewLoop->nPrims = 0; pNewLoop->PrimBufSize = size;
// give the loop a block of vertices to work with
pVert = (POINT2D*) ALLOC( size * sizeof(POINT2D) ); if( !pVert ) { FREE( pPrim ); return (LOOP *) NULL; } pNewLoop->VertBuf = pVert; pNewLoop->nVerts = 0; pNewLoop->VertBufSize = size;
// stick that first point in
pVert->x = pFirstPoint->x; pVert->y = pFirstPoint->y; pNewLoop->nVerts++;
// normal buffers - used by extrusion
pNewLoop->FNormBuf = (POINT3D *) NULL; pNewLoop->VNormBuf = (POINT3D *) NULL;
pLoopList->nLoops++; // increment loop count
return pNewLoop; }
/*****************************************************************************
* NewPrim * * Create a new PRIM structure. The primType is supplied.
*****************************************************************************/
static PRIM* NewPrim( LOOP *pLoop, DWORD primType ) { PRIM *pNewPrim; POINT2D *pVert; DWORD size = 50;
if( pLoop->nPrims >= pLoop->PrimBufSize) { // need to increase size of PrimBuf
PRIM *pPrim;
pPrim = (PRIM *) REALLOC(pLoop->PrimBuf, (pLoop->PrimBufSize += size) * sizeof(PRIM)); if( !pPrim ) return (PRIM *) NULL; pLoop->PrimBuf = pPrim; }
pNewPrim = pLoop->PrimBuf + pLoop->nPrims; // translate primType to extrusion prim type
primType = (primType == TT_PRIM_LINE) ? PRIM_LINE : PRIM_CURVE; pNewPrim->primType = primType; pNewPrim->nVerts = 1; // since we include last point:
/*
* VertIndex must point to the last point of the previous prim */ pNewPrim->VertIndex = pLoop->nVerts - 1; // normal pointers - used by extrusion
pNewPrim->pFNorm = (POINT3D *) NULL; pNewPrim->pVNorm = (POINT3D *) NULL;
pLoop->nPrims++; // increment prim count
return pNewPrim; }
/*****************************************************************************
* FreeLoopList * * Free up all memory associated with processing a glyph. * *****************************************************************************/
static void FreeLoopList( LOOP_LIST *pLoopList ) { DWORD nLoops;
if( !pLoopList ) return;
if( pLoopList->LoopBuf ) { // free up each loop
LOOP *pLoop = pLoopList->LoopBuf;
nLoops = pLoopList->nLoops; for( ; nLoops; nLoops--, pLoop++ ) { if( pLoop->PrimBuf ) FREE( pLoop->PrimBuf ); if( pLoop->VertBuf ) FREE( pLoop->VertBuf ); } FREE( pLoopList->LoopBuf ); } FREE( pLoopList ); }
/*****************************************************************************
* AppendToVertBuf * * Append a vertex to the Loop's VertBuf
*****************************************************************************/
static BOOL AppendToVertBuf( LOOP *pLoop, PRIM *pPrim, POINT2D *p ) { if( pLoop->nVerts >= pLoop->VertBufSize) { POINT2D *vertBuf; DWORD size = 100;
vertBuf = (POINT2D *) REALLOC(pLoop->VertBuf, (pLoop->VertBufSize += size) * sizeof(POINT2D)); if( !vertBuf ) return WFO_FAILURE; pLoop->VertBuf = vertBuf; } pLoop->VertBuf[pLoop->nVerts] = *p; pLoop->nVerts++; pPrim->nVerts++; return WFO_SUCCESS; }
/*****************************************************************************
* CalcVertPtrs * * Calculate vertex ptrs from index values for the prims in a loop.
*****************************************************************************/
static void CalcVertPtrs( LOOP *pLoop ) { DWORD nPrims; PRIM *pPrim;
nPrims = pLoop->nPrims; pPrim = pLoop->PrimBuf;
for( ; nPrims; pPrim++, nPrims-- ) { pPrim->pVert = pLoop->VertBuf + pPrim->VertIndex; } }
/*****************************************************************************
* GetFixed * * Fetch the next 32-bit fixed-point value from a little-endian byte stream, * convert it to floating-point, and increment the stream pointer to the next * unscanned byte.
*****************************************************************************/
static FLOAT GetFixed(UCHAR** p) { FLOAT value; FLOAT fraction;
fraction = ((FLOAT) (UINT) GetWord(p)) / 65536.0f; value = (FLOAT) GetSignedWord(p);
return value+fraction; }
#ifdef FONT_DEBUG
void DrawColorCodedLineLoop( LOOP *pLoop, FLOAT zextrusion ) { POINT2D *p; DWORD nPrims; DWORD nVerts; PRIM *pPrim;
nPrims = pLoop->nPrims; pPrim = pLoop->PrimBuf; for( ; nPrims; nPrims--, pPrim++ ) {
if( pPrim->primType == PRIM_LINE ) { if( nPrims == pLoop->nPrims ) // first prim
glColor3d( 0.5, 0.0, 0.0 ); else glColor3d( 1.0, 0.0, 0.0 ); } else { if( nPrims == pLoop->nPrims ) // first prim
glColor3d( 0.5, 0.5, 0.0 ); else glColor3d( 1.0, 1.0, 0.0 ); } nVerts = pPrim->nVerts; p = pPrim->pVert; glBegin(GL_LINE_STRIP); for( ; nVerts; nVerts--, p++ ) { glVertex3f( p->x, p->y, zextrusion ); } glEnd(); #define DRAW_POINTS 1
#ifdef DRAW_POINTS
glColor3d( 0.0, 0.5, 0.0 ); nVerts = pPrim->nVerts; p = pPrim->pVert; glPointSize( 4.0f ); glBegin( GL_POINTS ); for( ; nVerts; nVerts--, p++ ) { glVertex3f( p->x, p->y, zextrusion ); } glEnd(); #endif
}
// Draw bright green point at start of loop
if( pLoop->nVerts ) { glColor3d( 0.0, 1.0, 0.0 ); glPointSize( 4.0f ); glBegin( GL_POINTS ); p = pLoop->VertBuf; glVertex3f( p->x, p->y, zextrusion ); glEnd(); glPointSize( 1.0f ); }
} #endif
|