windows-nt/Source/XPSP1/NT/multimedia/opengl/client/fontoutl.c

1718 lines
49 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
#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