Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3531 lines
101 KiB

/******************************Module*Header*******************************\
* Module Name: pathwide.cxx
*
* This module generates the outlines for geometric wide-lines. Given
* a path for a line, it generates a new path that, when filled using
* a winding mode fill, yields the resulting widened line.
*
* Geometric wide-lines may have different end-cap and line-join
* attributes. They may also be styled.
*
* THINGS TO NOTE
*
* The path is already in device coordinates, so the resulting wide-line
* is also calculated in device coordinates.
*
* The key thing to note is that the pen's shape is defined in world space, so
* its shape in device space depends on the current world-to-device
* transform (which may be an arbitrary 2 by 2 affine transform). Pens
* are circular in world space, with their diameter there defined by the
* pen width. However, in device space a pen shape is only guaranteed to be
* an ellipse symmetrical about some arbitrary axis. And vectors that are
* perpendicular in world space are not necessarily perpendicular in device
* space (which is important for calculating flat and square end-caps, and
* bevel joins).
*
* HOW WE DO IT
*
* We implement wide-lines by first generating in device space a convex
* polygonal approximation to the pen's shape. We 'drag' this polygon
* along the given path; the outline of a widened line is generated by
* the point on the pen polygon that is furthest from the line's ray
* (called the 'draw' vertex). This point may be found by a simple
* binary search using entirely integer arithmetic.
*
* This approach makes round round joins and round caps very efficient:
* the points for the resulting round part is simply the points between
* draw vertices.
*
* To compute true perpendiculars (for flat and square end-caps, and
* bevel joins) we interpolate between vertices on the pen polygon
* (once again using only integer math).
*
* The lengths of the dashes and gaps for styled lines are defined in
* world space; to do styling we computed every line segment's length
* in world space, and divide it accordingly.
*
* HOBBY WIDE-LINES
*
* Rendering the wide-lines using a polygonized pen allows us to take
* advantage of Hobby wide-lines. Hobby's wide-line algorithm
* generates 'optimally shaped' pens that will yield lines of constant-
* looking width, independent of the direction of the lines. (OS/2's
* PM suffers from having its thin wide-lines appear not to be of
* constant width).
*
* Unfortunately, Hobby's algorithm is fairly expensive to generate an
* optimal pen shape polygon given its width and the current transform;
* it is significantly faster for us to generate a quick-and-dirty
* polygon approximation by creating the ellipse out of Bezier curves and
* flattening it.
*
* But Hobby pens are most important for small, near circular pens,
* where the eye can readily detect imperfections in the line. So we
* support Hobby wide-lines only for small, near circular pens, and we
* keep these pen shapes in a pre-computed table.
*
* See:
*
* John Hobby, "Digitized Brush Trajectories", PhD Dissertation,
* Stanford University, August 1985
*
* GENERATING BOTH SIDES AT THE SAME TIME
*
* We also generate both sides of the wide-line outline as we traverse
* the path, keeping track of both sides in separate paths (the outlines
* of the 'left' and 'right' sides of the wide-line). When the end of a
* figure (that is, the end of a sub-path) is reached, we reverse all the
* points in the 'left' path and append it to the 'right', and close the
* result.
*
* This could have been implemented by traversing the path once to generate
* one side, then traversing it again in the reverse direction to generate
* the other side. However, most of the hard computations for perpendiculars
* and such would have to be done twice. Our method is faster, although
* there is more book-keeping.
*
* BEZIERS
*
* We really only know how to widen lines, so we convert Beziers as we
* read them to their polyline approximations. We 'flatten' Beziers to
* constant error, independent of pen width, and make sure we apply 'round'
* joins to the interior polylines comprising the Bezier. The round join
* handles extreme cases such as cusps, so there really is no need to
* flatten to less error for larger pens.
*
* (This is in contrast to OS/2's PM which pre-flattens its curves prior
* to widening, and applies the regular joins to the interior of the curve.
* For wider pens, the allowed error in the flattening must be decreased
* because sharp curves would not look 'round' at cusp-points when using
* bevel or miter joins.)
*
* We flatten Bezier curves as we traverse path (instead of only widening
* paths that are already flattened) because it is useful to know the
* Bezier data. We use the control points to calculate true perpendiculars
* at the end-points of curves, and we have to know when we're in the
* interior of the Bezier to always apply round joins.
*
* Advantages: o We're much faster
*
* o Produce better looking curves
*
* Disadvantages: o More book-keeping
*
* o Have to be careful when mixing perpendiculars (such
* as from miter joins or flat caps) with round joins
*
* o Perpendiculars on styled curves may not be as accurate
* for wide-lines -- we use the approximating lines to
* calculate the perp, and more lines would give better
* perps.
*
* However, it can also be argued that due to the
* snapping effect of the device-space grid, approximating
* the curve into more small lines would actually make
* the perps _less_ accurate.
*
* LAZY EVALUATION
*
* Computing draw vectors and perpendiculars are very expensive operations,
* so we lazily evaluate them -- we compute them only when we need them.
* With styling, it may happen we completely skip over some lines (so we
* never do any evaluation), or that we break a line into many small pieces,
* and so re-use perps and draw-vectors (saving us time and ensuring that
* style ends are always perpendicular for styled segments).
*
* Created: 23-Sep-1991
* Author: J. Andrew Goossen [andrewgo]
*
* Copyright (c) 1991-1999 Microsoft Corporation
*
\**************************************************************************/
#include "precomp.hxx"
#include "flhack.hxx"
#include "pathwide.hxx"
#ifdef DEBUG_WIDE
#define INLINE
#else
#define INLINE inline
#endif
#ifdef DEBUG_WIDE
LONG lConv(EFLOAT ef);
#endif
POINTFIX WIDEPENOBJ::aptfxHobby1[] = {
{0, 8}, {8, 0}, {0, -8}, {-8, 0}
};
POINTFIX WIDEPENOBJ::aptfxHobby2[] = {
{8, 16}, {16, 0}, {8, -16}, {-8, -16},
{-16, 0}
};
POINTFIX WIDEPENOBJ::aptfxHobby3[] = {
{24, 8}, {24, -8}, {8, -24}, {-8, -24},
{-24, -8}, {-24, 8}
};
POINTFIX WIDEPENOBJ::aptfxHobby4[] = {
{32, 8}, {32, -8}, {24, -24}, {8, -32},
{-8, -32}, {-24, -24}, {-32, -8}, {-32, 8}
};
POINTFIX WIDEPENOBJ::aptfxHobby5[] = {
{40, 8}, {40, -8}, {32, -24}, {24, -32},
{8, -40}, {-8, -40}, {-24, -32}, {-32, -24},
{-40, -8}, {-40, 8}
};
POINTFIX WIDEPENOBJ::aptfxHobby6[] = {
{48, 8}, {48, -8}, {40, -24}, {24, -40},
{8, -48}, {-8, -48}, {-24, -40}, {-40, -24},
{-48, -8}, {-48, 8}
};
HOBBY WIDEPENOBJ::ahob[HOBBY_TABLE_SIZE] = {
{ aptfxHobby1, sizeof(aptfxHobby1) / sizeof(POINTFIX) },
{ aptfxHobby2, sizeof(aptfxHobby2) / sizeof(POINTFIX) },
{ aptfxHobby3, sizeof(aptfxHobby3) / sizeof(POINTFIX) },
{ aptfxHobby4, sizeof(aptfxHobby4) / sizeof(POINTFIX) },
{ aptfxHobby5, sizeof(aptfxHobby5) / sizeof(POINTFIX) },
{ aptfxHobby6, sizeof(aptfxHobby6) / sizeof(POINTFIX) }
};
/******************************Public*Routine******************************\
* bTurnLeftRandom(pa, pb)
*
* Returns TRUE if the sign of the cross product of a X b is positive.
* For simple minds like mine, it returns:
*
* TRUE if b 'turns left' from a (cross product is < 0)
* FALSE if b is parallel to or 'turns right' from a (cross is >= 0)
*
* When the vectors are random, half the time we take a short-cut by
* checking the signs. Remember that we're in device space, where
* positive 'y' is down!
*
* History:
* 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
INLINE BOOL bTurnLeftRandom(
PEVECTORFX pa,
PEVECTORFX pb)
{
BOOL bRet;
// If the signs are correct, we don't even have to multiply:
if ((pa->x ^ pa->y ^ pb->x ^ pb->y) < 0)
bRet = ((pa->x ^ pb->y) < 0);
else
{
LONGLONG ad;
LONGLONG bc;
// Check sign of (pa->x * pb->y - pa->y * pb->x) = (ad - bc)
ad = Int32x32To64(pa->x, pb->y);
bc = Int32x32To64(pa->y, pb->x);
bRet = (ad < bc);
}
return(bRet);
}
/******************************Public*Routine******************************\
* WIDEPATHOBJ::bGrowPath()
*
* Creates a new path record. Adds to the end of the path, and sets
* 'pptfxPathRecCurrent' and 'pptfxPathRecEnd'.
*
* History:
* 1-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Stole it from PaulB's pprFlattenRec.
\**************************************************************************/
BOOL WIDEPATHOBJ::bGrowPath()
{
PPATHALLOC ppa = ppath->ppachain;
COUNT cMax = 0;
if (ppa != (PPATHALLOC) NULL)
{
// We have a current pathalloc, see how much will fit
// computation done into temps to avoid compiler assertion!
PPOINTFIX pptfxStart = &(ppa->pprfreestart->aptfx[0]);
PPOINTFIX pptfxEnd = (PPOINTFIX) ((char *)ppa + ppa->siztPathAlloc);
if (pptfxEnd > pptfxStart)
{
//Sundown truncation
ASSERT4GB((ULONGLONG)(pptfxEnd - pptfxStart));
cMax = (COUNT)(pptfxEnd - pptfxStart);
}
}
// Now we can decide if we need a new pathalloc
if (cMax < PATHALLOCTHRESHOLD)
{
// allocate a new pathalloc, link it into path
if ( (ppa = newpathalloc()) == (PPATHALLOC) NULL)
return(FALSE);
ppa->ppanext = ppath->ppachain;
ppath->ppachain = ppa;
// adjust maxadd
// Sundown truncation
ASSERT4GB((ULONGLONG)(((char *)ppa + ppa->siztPathAlloc) -
(char *)ppa->pprfreestart));
ULONGSIZE_T numbytes = (ULONG)(((char *)ppa + ppa->siztPathAlloc) -
(char *)ppa->pprfreestart);
cMax = (numbytes - offsetof(PATHRECORD, aptfx))/sizeof(POINTFIX);
}
// Add pathrecord to end of path
PPATHREC pprNew = ppa->pprfreestart;
if (ppath->pprlast == (PPATHREC) NULL)
{
ppath->pprfirst = pprNew;
pprNew->pprprev = (PPATHREC) NULL;
}
else
{
ppath->pprlast->pprnext = pprNew;
pprNew->pprprev = ppath->pprlast;
}
ppath->pprlast = pprNew;
pprNew->pprnext = (PPATHREC) NULL;
pprNew->count = 0;
// pptfxPathRecCurrent points to first available spot. pptfxPathRecEnd
// points to first spot *after* the last available spot.
pptfxPathRecCurrent = &pprNew->aptfx[0];
pptfxPathRecEnd = &pprNew->aptfx[cMax];
return(TRUE);
}
/******************************Public*Routine******************************\
* WIDEPATHOBJ::vGrowPathAndAddPoint(pptfx)
*
* Adds to the current path by creating a new record and adding the
* specified point. If this fails, the path is marked as being out-of-
* memory.
*
* History:
* 1-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
VOID WIDEPATHOBJ::vGrowPathAndAddPoint(
PPOINTFIX pptfx,
PEVECTORFX pvec,
BOOL bInvert)
{
ASSERT4GB((LONGLONG)(pptfxPathRecCurrent - ppath->pprlast->aptfx));
COUNT cpt = (COUNT)(pptfxPathRecCurrent - ppath->pprlast->aptfx);
ppath->pprlast->count = cpt;
ppath->ppachain->pprfreestart = NEXTPATHREC(ppath->pprlast);
if (bValid())
{
if (!bGrowPath())
vSetError();
else
{
ppath->pprlast->flags = 0;
*pptfxPathRecCurrent = *pptfx;
if (pvec != (PEVECTORFX) NULL)
{
if (bInvert)
{
pptfxPathRecCurrent->x -= pvec->x;
pptfxPathRecCurrent->y -= pvec->y;
}
else
{
pptfxPathRecCurrent->x += pvec->x;
pptfxPathRecCurrent->y += pvec->y;
}
}
pptfxPathRecCurrent++;
}
}
}
/******************************Public*Routine******************************\
* WIDEPATHOBJ::bBeginFigure()
*
* Creates the start of a new figure (sub-path) in the path. Must be
* called before vAddPoint().
*
* History:
* 1-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
BOOL WIDEPATHOBJ::bBeginFigure()
{
if (bValid())
{
#ifdef DEBUG_WIDE
ASSERTGDI(!bOpenPath, "BeginFigure on already open path!");
bOpenPath = TRUE;
#endif
if (bGrowPath())
{
ppath->pprlast->flags = PD_BEGINSUBPATH;
return(TRUE);
}
vSetError();
}
return(FALSE);
}
/******************************Public*Routine******************************\
* WIDEPATHOBJ::vEndFigure()
*
* Ends the open figure. Must be called after all vAddPoint()'s are done
* for the figure.
*
* History:
* 1-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
VOID WIDEPATHOBJ::vEndFigure()
{
#ifdef DEBUG_WIDE
ASSERTGDI(bOpenPath, "vEndFigure on unopen path!");
bOpenPath = FALSE;
#endif
//Sundown truncation
ASSERT4GB((ULONGLONG)(pptfxPathRecCurrent - ppath->pprlast->aptfx));
COUNT cpt = (COUNT)(pptfxPathRecCurrent - ppath->pprlast->aptfx);
ppath->pprlast->flags |= PD_ENDSUBPATH;
ppath->pprlast->count = cpt;
// Adjust the pathalloc record:
ppath->ppachain->pprfreestart = NEXTPATHREC(ppath->pprlast);
}
/******************************Public*Routine******************************\
* WIDEPATHOBJ::vPrependBeforeFigure()
*
* Moves the last figure in path and prepends to the first figure.
*
* History:
* 24-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
INLINE VOID WIDEPATHOBJ::vPrependBeforeFigure()
{
#ifdef DEBUG_WIDE
ASSERTGDI(!bOpenPath, "vPrependBeforeFigure on open path!");
#endif
PPATHREC pprSrcStart;
PPATHREC pprTargStart;
// Find the start of the last figure:
pprSrcStart = ppath->pprlast;
while (!(pprSrcStart->flags & PD_BEGINSUBPATH))
{
pprSrcStart = pprSrcStart->pprprev;
ASSERTGDI(pprSrcStart != (PPATHREC) NULL, "Couldn't find start");
}
// Find the start of the 2nd last figure:
pprTargStart = pprFigureStart;
ASSERTGDI(pprTargStart != (PPATHREC) NULL &&
(pprTargStart->flags & PD_BEGINSUBPATH), "Funky pprFigureStart");
PPATHREC pprTargEnd = pprSrcStart->pprprev;
PPATHREC pprSrcEnd = ppath->pprlast;
// 'targ' is currently before 'src', and 'src' is the last figure in the
// path. We'll move 'src' to be before 'targ':
ppath->pprlast = pprTargEnd;
// Hmmm, seems to me we're guaranteed to have pprprev NULL here because
// we're looking at the start of the path:
if (pprTargStart->pprprev == (PPATHREC) NULL)
ppath->pprfirst = pprSrcStart;
else
pprTargStart->pprprev->pprnext = pprSrcStart;
pprSrcStart->pprprev = pprTargStart->pprprev;
pprSrcEnd->pprnext = pprTargStart;
pprTargStart->pprprev = pprSrcEnd;
pprTargEnd->pprnext = (PPATHREC) NULL;
// We're prepending this to the start of the first figure, so clean up
// the flags:
pprTargStart->flags &= ~PD_BEGINSUBPATH;
pprSrcEnd->flags &= ~(PD_ENDSUBPATH | PD_CLOSEFIGURE);
}
/******************************Public*Routine******************************\
* WIDEPATHOBJ::vPrependBeforeSubpath()
*
* Moves the last figure in path and prepends it to the previous subpath.
*
* History:
* 24-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
INLINE VOID WIDEPATHOBJ::vPrependBeforeSubpath()
{
#ifdef DEBUG_WIDE
ASSERTGDI(!bOpenPath, "vPrependBeforeSubpath on open path!");
#endif
PPATHREC pprSrcStart;
PPATHREC pprTargStart;
// Find the start of the last figure:
pprSrcStart = ppath->pprlast;
while (!(pprSrcStart->flags & PD_BEGINSUBPATH))
{
pprSrcStart = pprSrcStart->pprprev;
ASSERTGDI(pprSrcStart != (PPATHREC) NULL, "Couldn't find start");
}
// Find the start of the 2nd last figure:
ASSERTGDI(pprSrcStart->pprprev != (PPATHREC) NULL, "Was no previous figure");
pprTargStart = pprSrcStart->pprprev;
while (!(pprTargStart->flags & PD_BEGINSUBPATH))
{
pprTargStart = pprTargStart->pprprev;
ASSERTGDI(pprTargStart != (PPATHREC) NULL, "Couldn't find previous figure");
}
PPATHREC pprTargEnd = pprSrcStart->pprprev;
PPATHREC pprSrcEnd = ppath->pprlast;
// 'targ' is currently before 'src', and 'src' is the last figure in the
// path. We'll move 'src' to be before 'targ':
ppath->pprlast = pprTargEnd;
if (pprTargStart->pprprev == (PPATHREC) NULL)
ppath->pprfirst = pprSrcStart;
else
pprTargStart->pprprev->pprnext = pprSrcStart;
pprSrcStart->pprprev = pprTargStart->pprprev;
pprSrcEnd->pprnext = pprTargStart;
pprTargStart->pprprev = pprSrcEnd;
pprTargEnd->pprnext = (PPATHREC) NULL;
// We're prepending this to the start of the first figure, so clean up
// the flags:
pprTargStart->flags &= ~PD_BEGINSUBPATH;
pprSrcEnd->flags &= ~(PD_ENDSUBPATH | PD_CLOSEFIGURE);
}
/******************************Public*Routine******************************\
* WIDEPATHOBJ::vReverseConcatenate(wpo)
*
* Reverses 'wpo' and adds all points to the end of this path.
* bBeginFigure() must have been called previously for this path, and
* 'wpo' must be ended by calling vEndFigure().
*
* The path records of 'wpo' are freed as they are copied (in order to
* decrease the total number of path records needed by both paths). As such,
* 'wpo' must be entirely one figure!
*
* History:
* 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
VOID WIDEPATHOBJ::vReverseConcatenate(WIDEPATHOBJ& wpo)
{
#ifdef DEBUG_WIDE
ASSERTGDI(bOpenPath, "vReverseConcatenate on closed target!");
ASSERTGDI(!wpo.bOpenPath, "vReverseConcatenate on open source!");
#endif
ASSERTGDI(bValid(), "Reverse invalid path");
ASSERTGDI(wpo.ppath->pprfirst != (PPATHREC) NULL, "NULL path 1");
ASSERTGDI(wpo.ppath->ppachain != (PPATHALLOC) NULL, "NULL path 2");
ASSERTGDI(ppath->pprfirst != (PPATHREC) NULL, "NULL path 3");
ASSERTGDI(ppath->ppachain != (PPATHALLOC) NULL, "NULL path 4");
// Reverse the path:
PPATHREC ppr = wpo.ppath->pprlast;
while (ppr != (PPATHREC) NULL)
{
PPATHREC pprPrev = ppr->pprprev;
PPOINTFIX pptfxStart;
PPOINTFIX pptfxEnd;
// Copy all the points in reverse order:
pptfxStart = &ppr->aptfx[0];
pptfxEnd = &ppr->aptfx[ppr->count];
while (pptfxEnd > pptfxStart)
vAddPoint(--pptfxEnd);
PPATHALLOC ppaEnd = wpo.ppath->ppachain;
ASSERTGDI(ppaEnd != (PPATHALLOC) NULL, "Null pathalloc");
wpo.ppath->ppachain = ppaEnd->ppanext;
ASSERTGDI(((PBYTE) pptfxStart > (PBYTE) ppaEnd) &&
((PBYTE) pptfxStart - (PBYTE) ppaEnd) < (LONG) ppaEnd->siztPathAlloc,
"Last pathrec doesn't correspond to last pathalloc");
freepathalloc(ppaEnd);
ppr = pprPrev;
}
ASSERTGDI(wpo.ppath->ppachain == (PPATHALLOC) NULL,
"Didn't delete entire chain");
// Clean up old path:
wpo.ppath->ppachain = (PPATHALLOC) NULL;
wpo.ppath->pprlast = (PPATHREC) NULL;
wpo.ppath->pprfirst = (PPATHREC) NULL;
}
/******************************Public*Routine******************************\
* vHalve(vec)
*
* Halves the vector, and always rounds 1/32 up. Symmetric about zero.
*
* History:
* 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
INLINE VOID vHalve(EVECTORFX& vec)
{
if (vec.x >= 0)
vec.x++;
if (vec.y >= 0)
vec.y++;
vec.x >>= 1;
vec.y >>= 1;
}
/******************************Public*Routine******************************\
* vVecSymmetricRound(pvec)
*
* Always rounds 1/2 up. Symmetric about zero.
*
* History:
* 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
INLINE VOID vVecSymmetricRound(PEVECTORFX pvec)
{
// Round 1/2 up:
if (pvec->x >= 0)
pvec->x = (pvec->x + 4) & ~7L;
else
pvec->x = (pvec->x + 3) & ~7L;
if (pvec->y >= 0)
pvec->y = (pvec->y + 4) & ~7L;
else
pvec->y = (pvec->y + 3) & ~7L;
}
/******************************Public*Routine******************************\
* vVecRound(pvec)
*
* Always rounds 1/2 down for 'x'. Rounds 1/2 up for 'y'.
*
* History:
* 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
INLINE VOID vVecRound(PEVECTORFX pvec)
{
// Round 1/2 down:
if (pvec->x >= 0)
pvec->x = (pvec->x + 3) & ~7L;
else
pvec->x = (pvec->x + 4) & ~7L;
// Round 1/2 up:
if (pvec->y >= 0)
pvec->y = (pvec->y + 4) & ~7L;
else
pvec->y = (pvec->y + 3) & ~7L;
}
/******************************Public*Routine******************************\
* WIDEPENOBJ::bHobbyize(avecAxis[])
*
* Will copy a pre-computed Hobby pen polygon if the pen is small and
* circular.
*
* History:
* 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
BOOL WIDEPENOBJ::bHobbyize(EVECTORFX avecAxis[])
{
ASSERTGDI(((avecAxis[0].x == avecAxis[1].y) && (avecAxis[0].y == -avecAxis[1].x))
|| ((avecAxis[0].x == -avecAxis[1].y) && (avecAxis[0].y == avecAxis[1].x)),
"Must be orthogonal and circular");
ASSERTGDI((MAX(ABS(avecAxis[0].x), ABS(avecAxis[0].y))
< LPLUSHALFTOFX(HOBBY_TABLE_SIZE)), "Must be Hobby sized");
// We now know that the transform was orthogonal, and that the pen is
// circular in device space.
// We now know that the vector coordinates have less than 15 bits
// signficance, so we can safely compute its Euclidean length in
// 32 bits:
FIX fxPenWidthSquared = avecAxis[0].x * avecAxis[0].x
+ avecAxis[0].y * avecAxis[0].y;
if (fxPenWidthSquared < SQUARE(LPLUSHALFTOFX(HOBBY_TABLE_SIZE)))
{
// Pen is small enough that it will be in our Hobby tables:
LONG iHobbyPen;
if (fxPenWidthSquared < SQUARE(LPLUSHALFTOFX(1)))
iHobbyPen = 0; // Width less than 1.5
else if (fxPenWidthSquared < SQUARE(LPLUSHALFTOFX(2)))
iHobbyPen = 1; // Width less than 2.5
else if (fxPenWidthSquared < SQUARE(LPLUSHALFTOFX(3)))
iHobbyPen = 2; // Width less than 3.5
else if (fxPenWidthSquared < SQUARE(LPLUSHALFTOFX(4)))
iHobbyPen = 3; // Width less than 4.5
else if (fxPenWidthSquared < SQUARE(LPLUSHALFTOFX(5)))
iHobbyPen = 4; // Width less than 5.5
else
{
#if (HOBBY_TABLE_SIZE != 6L)
#error "Table size changed, update routine"
#endif
iHobbyPen = 5; // Width less than 6.5
}
// Copy the Hobby pen from the table to our path:
if (!bBeginFigure())
return(FALSE);
PPOINTFIX pptfx = ahob[iHobbyPen].pptfx;
PPOINTFIX pptfxEnd = pptfx + ahob[iHobbyPen].cptfx;
while (pptfx < pptfxEnd)
vAddPoint(pptfx++);
vEndFigure();
bIsHobby(TRUE);
return(TRUE);
}
return(FALSE);
}
/******************************Public*Routine******************************\
* WIDEPENOBJ::bThicken(aptfxDiameter)
*
* Check if the pen is so thin in one dimension that it would be invisible
* for some lines. If so, thicken it.
*
* History:
* 31-Jan-1992 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
#define FX_HALF (LTOFX(1) >> 1)
#define FX_HALF_SQUARED (FX_HALF * FX_HALF)
BOOL WIDEPENOBJ::bThicken(PPOINTFIX aptfxDiameter)
{
// The aptfxDiameter vectors passed to us were constructed from the
// diameter of the pen -- we actually want the vectors as based on
// the radius, so halve them:
EVECTORFX aevecTmp[2];
PPOINTFIX aptfx = (PPOINTFIX) aevecTmp;
aevecTmp[0] = aptfxDiameter[0];
aevecTmp[1] = aptfxDiameter[1];
vHalve(aevecTmp[0]);
vHalve(aevecTmp[1]);
// What I would like to do is compute the length of the minor-axis of
// the pen ellipse in device coordinates -- if its length is less than
// half a pixel, we know we have to thicken the pen.
//
// But it's a lot of computation to do that (see Knuth, "Metafont",
// p. 382). So, we take the two vectors that described the axis of
// the ellipse in world space, and transform them to device space (call
// them 'avec'). The perpendicular distance from the smaller vector
// to the larger will be close to the minor-axis length.
// Make sure we won't overflow:
if (((ABS(aptfx[0].x) | ABS(aptfx[0].y) | ABS(aptfx[1].x) | ABS(aptfx[1].y))
& ~(0xfffL)) != 0)
return(FALSE);
FIX fxLengthSquared0 = aptfx[0].x * aptfx[0].x + aptfx[0].y * aptfx[0].y;
FIX fxLengthSquared1 = aptfx[1].x * aptfx[1].x + aptfx[1].y * aptfx[1].y;
POINTFIX ptfx; // Will be the largest vector
FIX fxLength; // Length of largest vector
//
// We use the forumla to compute the distance from a point to a line
// from the origin, where 'd' is the distance, 'B' is the vector,
// 'P' is the point:
//
// 2
// 2 4 [ (P.y * B.x) - (P.x * B.y) ]
// d = -------------------------------
// B.x * B.x + B.y * B.y
//
// We want to exit early when the distance is more than 1/2 pixel (8 in
// FIX units), i.e. HALF < d or:
//
// 2 2 2 2
// HALF (B.x + B.y ) < 4 [ (P.y * B.x) - (P.x * B.y) ]
//
if (fxLengthSquared0 > fxLengthSquared1)
{
// Vector 0 is bigger than 1:
FIX fxNum = aptfx[1].y * aptfx[0].x - aptfx[1].x * aptfx[0].y;
LONGLONG eqLeft = (LONGLONG) fxLengthSquared0 * (FX_HALF_SQUARED >> 2);
LONGLONG eqRight = Int32x32To64((LONG) fxNum, (LONG) fxNum);
if (eqLeft < eqRight)
return(FALSE);
ptfx = aptfx[0];
fxLength = fxLengthSquared0;
}
else
{
// Vector 1 is bigger than (or equal to) 0:
FIX fxNum = aptfx[0].y * aptfx[1].x - aptfx[0].x * aptfx[1].y;
LONGLONG eqLeft = (LONGLONG) fxLengthSquared1 * (FX_HALF_SQUARED >> 2);
LONGLONG eqRight = Int32x32To64((LONG) fxNum, (LONG) fxNum);
if (eqLeft < eqRight)
return(FALSE);
ptfx = aptfx[1];
fxLength = fxLengthSquared1;
}
// Make sure that the largest vector extends outside the unit circle (if
// it's not, we'll make it so that we end up with the entire Hobby pen
// for width 1):
if (fxLength < FX_HALF_SQUARED)
{
ptfx.x = FX_HALF;
ptfx.y = 0;
}
// Okay, we know the larger vector, and we have to fix the smaller one.
// Let's simply figure out the vertex on the Hobby pen of width 1 that
// would be used if we drew a line in the same direction as our large
// vector (which is when our given pen is its thinest).
//
// So let's compute the appropriate Hobby vector of size one (making
// sure it's to the 'left' of the big vector, because the vertices have to
// be in counter-clockwise order):
POINTFIX ptfxHobby;
if (ABS(ptfx.y) <= ptfx.x)
{
ptfxHobby.x = 0;
ptfxHobby.y = -FX_HALF;
}
else if (ABS(ptfx.x) <= -ptfx.y)
{
ptfxHobby.x = -FX_HALF;
ptfxHobby.y = 0;
}
else if (ABS(ptfx.y) <= -ptfx.x)
{
ptfxHobby.x = 0;
ptfxHobby.y = FX_HALF;
}
else
{
ASSERTGDI(ABS(ptfx.x) <= ptfx.y, "Oops");
ptfxHobby.x = FX_HALF;
ptfxHobby.y = 0;
}
// Put stuff into path, remembering that 'pptfx' points to either
// ppath->pprlast->aptfx[0] or [1]. Also, we have to be in counter-
// clockwise order.
if (!bBeginFigure())
return(FALSE);
vAddPoint(&ptfx);
vAddPoint(&ptfxHobby);
ptfx.x = -ptfx.x;
ptfx.y = -ptfx.y;
ptfxHobby.x = -ptfxHobby.x;
ptfxHobby.y = -ptfxHobby.y;
vAddPoint(&ptfx);
vAddPoint(&ptfxHobby);
vEndFigure();
return(TRUE);
}
/******************************Public*Routine******************************\
* WIDEPENOBJ::bPenFlatten(pptfxControl)
*
* Flattens a geometric pen object. pptfxControl points to the 7 control
* points of a half-ellipse.
*
* History:
* 1-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
BOOL WIDEPENOBJ::bPenFlatten(PPOINTFIX pptfxControl)
{
if (!bGrowPath())
return(FALSE);
ASSERTGDI(pptfxPathRecEnd - pptfxPathRecCurrent > 2, "Path rec too small");
ppath->pprlast->flags = PD_BEGINSUBPATH;
// Leave room to insert a point at the beginning of the path. Also leave
// room for an additional point at the end of the path record:
PPOINTFIX pptfxFirst = pptfxPathRecCurrent;
pptfxPathRecCurrent++;
pptfxPathRecEnd--;
// Don't forget the very first point:
*pptfxPathRecCurrent++ = *pptfxControl;
// We'll crack 2 Beziers:
for (LONG ll = 0; ll <= 1; ll++, pptfxControl += 3)
{
BOOL bMore;
BEZIER bez;
bez.vInit(pptfxControl);
do
{
bMore = bez.bNext(pptfxPathRecCurrent++);
if (!bMore)
break;
// Check if we have to create a new path record:
if (pptfxPathRecCurrent > pptfxPathRecEnd)
{
// Wrap up old path record:
ASSERTGDI(pptfxPathRecCurrent == pptfxPathRecEnd + 1,
"Cur != End + 1");
// We want to repeat the last edge of this path record as the
// first edge in the next. That means repeating two points
// (remember that we left room for one more point in this record):
PPOINTFIX pptfxPrev = pptfxPathRecCurrent - 2;
// Sundown truncation
ppath->pprlast->count = (ULONG)(pptfxPathRecCurrent
- ppath->pprlast->aptfx);
ASSERTGDI(ppath->pprlast->count >= 3,
"Pen pathrecord doesn't have 3 vertices");
ppath->ppachain->pprfreestart = NEXTPATHREC(ppath->pprlast);
// Create a new path record:
if (!bGrowPath())
return(FALSE);
ppath->pprlast->flags = 0;
// Repeat those two points in the new path record:
*pptfxPathRecCurrent++ = *(pptfxPrev);
*pptfxPathRecCurrent++ = *(pptfxPrev + 1);
// Don't forget to leave room for an additional point at the
// end of the record:
pptfxPathRecEnd--;
}
} while (1);
}
// The pen covers 180 degrees of the ellipse. The first and last edges
// of the pen must be parallel, their vectors in opposite directions.
// The last point in the path is already the opposite of the first. Now
// make the zeroth point the opposite of the second last and voila,
// parallel edges.
pptfxFirst->x = - (pptfxPathRecCurrent - 2)->x;
pptfxFirst->y = - (pptfxPathRecCurrent - 2)->y;
ppath->pprlast->flags |= PD_ENDSUBPATH;
// Sundown truncation
ppath->pprlast->count = (ULONG)(pptfxPathRecCurrent - ppath->pprlast->aptfx);
ASSERTGDI(ppath->pprlast->count >= 3,
"Final pen pathrecord doesn't have 3 vertices");
return(TRUE);
}
/******************************Public*Routine******************************\
* WIDEPENOBJ::bPolygonizePen(exo, ulWidth)
*
* Creates a polyline approximation to the pen.
*
* If the pen is small and circular, we copy a pre-computed Hobby pen;
* otherwise, we approximate the pen using Bezier curves and convert this
* to lines. Remembering that the pen is defined in world space, we
* construct the Bezier curves that approximate the pen circle in world
* space. We then transform the coordinates to device space, and flatten.
*
* It makes life a lot easier if we can assume that the vertices of the pen
* are stored in counter-clockwise order. So, we check the transform to
* see if it is inverting, and adjust the Bezier control points accordingly.
*
* Due to the half-symmetry property of ellipses, we only have to store
* the vertices for half the ellipse. For the binary search, it is useful
* also to ensure that the last edge vector is the exact opposite of the
* first.
*
* History:
* 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
BOOL WIDEPENOBJ::bPolygonizePen(
EXFORMOBJ& exo, // World to device transform
LONG lWidth) // Pen width in world space
{
EVECTORFX avecAxis[2];
avecAxis[0].x = (FIX) lWidth;
avecAxis[0].y = 0;
avecAxis[1].x = 0;
avecAxis[1].y = - (FIX) lWidth;
exo.bXform((PVECTORL) avecAxis, avecAxis, 2);
if (((avecAxis[0].x == avecAxis[1].y) && (avecAxis[0].y == -avecAxis[1].x))
|| ((avecAxis[0].x == -avecAxis[1].y) && (avecAxis[0].y == avecAxis[1].x)))
{
// We now know that the transform was orthogonal, and that the pen is
// circular in device space.
if (MAX(ABS(avecAxis[0].x), ABS(avecAxis[0].y))
< LPLUSHALFTOFX(HOBBY_TABLE_SIZE))
{
if (bHobbyize(avecAxis))
return(TRUE);
else if (!bValid()) // See if path still okay
return(FALSE);
}
}
if (bThicken((PPOINTFIX) avecAxis))
return(TRUE);
else if (!bValid()) // See if path still okay
return(FALSE);
// The points must be ordered in a counter-clockwise direction.
// The half-ellipse we're creating starts at avecAxis[0], goes through
// avecAxis[1], and ends at -avecAxis[0]. As such, avecAxis[1] must
// lie to the left of avecAxis[0]:
if (!bTurnLeftRandom(&avecAxis[0], &avecAxis[1]))
{
avecAxis[1].x = -avecAxis[1].x;
avecAxis[1].y = -avecAxis[1].y;
}
vHalve(avecAxis[0]);
vHalve(avecAxis[1]);
VECTORFX vecRight;
VECTORFX vecUp;
LONGLONG eqTmp;
vEllipseControlsOut(&avecAxis[0], &vecRight, &eqTmp);
vEllipseControlsOut(&avecAxis[1], &vecUp, &eqTmp);
//
// 4 3 2
// +----x-------E-------x----+
// | |
// | |
// | |
// 5 x x 1 ^
// | | |
// | | |
// | | | vecUp
// | VecRight | |
// 6 E O-------> E 0 |
//
// where 'O' is the origin,
// 'x' is a control point of a Bezier curve
// 'E' is an end point of a Bezier curve
//
EVECTORFX avec[7];
avec[6].x = -avecAxis[0].x;
avec[6].y = -avecAxis[0].y;
avec[5] = avec[6];
avec[5] += vecUp;
avec[4] = avecAxis[1];
avec[4] -= vecRight;
avec[3] = avecAxis[1];
avec[2] = avecAxis[1];
avec[2] += vecRight;
avec[1] = avecAxis[0];
avec[1] += vecUp;
avec[0] = avecAxis[0];
return(bPenFlatten((PPOINTFIX) avec));
}
/******************************Public*Routine******************************\
* bLeft(pvec, pptfx, pfxCross)
*
* Returns TRUE if the vector to 'pptfx' turns left from 'pvec'. Returns
* the cross product in pfxCross.
*
* NOTE: Since only the low DWORD of the cross product is returned, *pfxCross
* is valid only when the vector '*(pptfx + 1) - *pptfx' is close to being
* parallel to 'pvec'.
*
* History:
* 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
INLINE BOOL bLeft(
PEVECTORFX pvec, // First vector
PPOINTFIX pptfx, // Second vector
PLONGLONG pfxCross) // Result of pvec X pptfx
{
LONGLONG ad;
LONGLONG bc;
ad = Int32x32To64(pvec->x, (pptfx + 1)->y - pptfx->y);
bc = Int32x32To64(pvec->y, (pptfx + 1)->x - pptfx->x);
ad -= bc;
*pfxCross = ad;
return(ad < 0);
}
/******************************Public*Routine******************************\
* WIDEPENOBJ::vDetermineDrawVertex(vec, ld)
*
* The core routine of wide-lines. This routine determines the point
* on the pen that draws the wide-line outline for the given line vector.
*
* Think of the line as a ray, and imagine dragging an elliptical pen
* along it. The point on the ellipse that is furthest from the
* ray defines the outline of the widened line. It's a bit of work to
* determine the draw vertex on a true ellipse (involving a bunch of
* floating point calculations).
*
* We take a different approach: we've already computed a convex polygonal
* approximation of the true ellipse. We search the vertices of the
* polygon to determine which is the farthest from the ray.
*
* We could do this by finding the maximum cross product of the line
* vector and the vectors from the center of the pen to each of its
* vertices, but this approach doesn't lend itself to a binary search.
*
* We accomplish a binary search by looking at the cross product of the
* line vector and the vector difference between successive vertices of the
* pen polygon -- we find the point on the pen polygon whose adjacent
* edge vector on one side turns to the 'left' of the line vector, and
* whose other edge vector turns to the 'right' of the line vector.
* That is, the vertex whose cross products of its adjacent edge vectors
* and the line vector have different signs.
*
* Our pen polygon is only half the ellipse -- under an affine transform,
* ellipses are always symmetrical about some axis. Since we made sure
* the first edge is exactly opposite the last edge when we built the
* pen polygon, it follows that for any line vector, the cross product
* with the first and last edges have different signs (unless the line
* vector is parallel to that edge, which we sort of let fall through).
* So we start the binary search by considering the first and last edges
* of the half-ellipse.
*
* For the binary search, we maintain the invariant that the cross product
* of the first and last edges with our line vector has different signs, and
* we subdivide the interval between the first and last edge until they are
* adjacent edges on the polygon; the shared vertex is then our draw vertex.
*
* History:
* 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
VOID WIDEPENOBJ::vDetermineDrawVertex(
EVECTORFX& vec, // Direction of line
LINEDATA& ld) // Result goes here
{
PPOINTFIX pptfxStart;
PPOINTFIX pptfxEnd;
PPOINTFIX pptfxMid;
LONGLONG fxCrossMid;
BOOL bLeftStart;
BOOL bLeftEnd;
ld.ppr = ppath->pprfirst;
if (ppath->pprfirst == ppath->pprlast)
{
bLeftStart = bLeft(&vec, &ld.ppr->aptfx[0], &ld.fxCrossStart);
ld.fxCrossEnd = -ld.fxCrossStart;
}
else
{
// El yucko, the pen is bigger than one PathRecord. Scan through the
// linked list until we find a PathRecord whose edges bracket our vector.
//
// NOTE: If our vector is parallel to and in the same direction as the
// last edge in the last pathrecord, then
//
// bTurnLeft(vec, vecStart) == FALSE
// and bTurnLeft(vec, vecEnd) == FALSE
while (TRUE)
{
bLeftStart = bLeft(&vec,
&ld.ppr->aptfx[0],
&ld.fxCrossStart);
bLeftEnd = bLeft(&vec,
&ld.ppr->aptfx[ld.ppr->count - 2],
&ld.fxCrossEnd);
if ((bLeftStart ^ bLeftEnd) || ld.ppr->pprnext == (PPATHREC) NULL)
break;
ld.ppr = ld.ppr->pprnext;
}
}
if (bLeftStart)
ld.vSetInvert();
else
ld.vClearInvert();
// 'pptfxStart' is a pointer to the first point of the first edge in
// the path record. 'pptfxEnd' is a pointer to the first point of the
// last edge.
pptfxStart = &ld.ppr->aptfx[0];
pptfxEnd = &ld.ppr->aptfx[ld.ppr->count - 2];
while (TRUE)
{
pptfxMid = pptfxStart + ((pptfxEnd - pptfxStart) >> 1);
if (bLeft(&vec, pptfxMid, &fxCrossMid) ^ bLeftStart)
{
// Signs are different, so the point we want must be between
// 'start' and 'mid':
ld.fxCrossEnd = fxCrossMid;
if (pptfxMid == pptfxStart + 1)
{
ld.pptfx = pptfxMid;
break;
}
pptfxEnd = pptfxMid;
}
else
{
// Signs are the same, so the point we want must be between
// 'mid' and 'end':
ld.fxCrossStart = fxCrossMid;
if (pptfxEnd == pptfxMid + 1)
{
ld.pptfx = pptfxEnd;
break;
}
pptfxStart = pptfxMid;
}
ASSERTGDI(pptfxStart < pptfxEnd, "Couldn't find draw vertex");
}
// ld.pptfx is our draw vertex.
#ifdef DEBUG_WIDE
ASSERTGDI((ld.fxCrossEnd >= 0 && ld.fxCrossStart < 0) ||
(ld.fxCrossEnd <= 0 && ld.fxCrossStart >= 0),
"Expected different cross products signs");
#endif
ld.fxCrossStart = ABS(ld.fxCrossStart);
ld.fxCrossEnd = ABS(ld.fxCrossEnd);
}
/******************************Public*Routine******************************\
* vAddNice(wpath, pptfx, pvec, bInvert)
*
* This routine is for adding round joins and end-caps to the given path.
*
* It does a trick to make rounds look nicer: when the given point lies on
* the integer grid, it shrinks the pen vector by 1/16th of a pel in both
* coordinates.
*
* This works on the assumption that most often the pens are constructed
* from integer widths in device space -- when that happens, we will often
* get assymetric filling because our fill convention breaks ties when a
* scan-line has a line fall exactly on the integer grid by being lower-right
* exclusive.
*
* Note: Remember that the bounds calculation has to take this possible
* size increase into account.
*
* History:
* 20-Nov-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
VOID vAddNice(
WIDEPATHOBJ& wpath, // Path to add to
PPOINTFIX pptfx, // Spine point about which the round is added
PEVECTORFX pvec, // The vector to add to pptfx
BOOL bInvert) // TRUE if pvec is to be subtracted from pptfx
{
EVECTORFX vec;
if (((pptfx->x | pptfx->y) & (LTOFX(1) - 1)) == 0)
{
if (bInvert)
{
vec.x = -pvec->x;
vec.y = -pvec->y;
bInvert = FALSE;
}
else
{
vec = *pvec;
}
if (vec.x > 0)
vec.x -= 1;
else if (vec.x < 0)
vec.x += 1;
if (vec.y > 0)
vec.y -= 1;
else if (vec.y < 0)
vec.y += 1;
pvec = &vec;
}
wpath.vAddPoint(pptfx, pvec, bInvert);
}
/******************************Public*Routine******************************\
* WIDEPENOBJ::cptAddRound(wid, ldIn, ldOut, bLeft, bInPerp, bOutPerp)
*
* Adds round joins to 'wid'.
*
* History:
* 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
COUNT WIDEPENOBJ::cptAddRound(
WIDENER& wid, // Path to which to add
LINEDATA& ldIn, // Vector entering the point
LINEDATA& ldOut, // Vector exiting the point
BOOL bLeft, // ldOut turns left from ldIn?
BOOL bInPerp, //
BOOL bOutPerp) //
{
COUNT cpt = 0;
ASSERTGDI(ldIn.bVecDrawComputed() && ldOut.bVecDrawComputed(),
"Draw vertex not computed");
if (ldIn.pptfx == ldOut.pptfx && ldIn.bInvert() == ldOut.bInvert())
return(0);
BOOL bInvert = ldIn.bInvert();
PPATHREC ppr = ldIn.ppr;
PPOINTFIX pptfx = ldIn.pptfx;
if (bLeft)
{
// Turns left, so add counter-clockwise to right side:
if (bInPerp && !ldIn.bToLeftSide())
{
wid.vAddRightNice((PEVECTORFX) pptfx, bInvert);
}
pptfx++;
while (ppr != ldOut.ppr ||
pptfx > ldOut.pptfx ||
bInvert != ldOut.bInvert())
{
PPOINTFIX pptfxEnd = &ppr->aptfx[ldIn.ppr->count - 1];
while (pptfx < pptfxEnd)
{
wid.vAddRightNice((PEVECTORFX) pptfx++, bInvert);
cpt++;
}
if (ppr->pprnext != (PPATHREC) NULL)
ppr = ppr->pprnext;
else
{
ppr = ppath->pprfirst;
bInvert = !bInvert;
}
pptfx = &ppr->aptfx[1];
}
ASSERTGDI(ppr == ldOut.ppr, "Unmatched path record pointers");
ASSERTGDI(ldOut.pptfx - pptfx < (PATHALLOCSIZE >> 3),
"Clockwise join unsynced");
while (pptfx < ldOut.pptfx)
{
wid.vAddRightNice((PEVECTORFX) pptfx++, bInvert);
cpt++;
}
if (bOutPerp && ldOut.bToLeftSide())
{
wid.vAddRightNice((PEVECTORFX) pptfx, bInvert);
}
}
else
{
// Turns right, so add clockwise to left side:
if (bInPerp && ldIn.bToLeftSide())
{
wid.vAddLeftNice((PEVECTORFX) pptfx, bInvert);
}
pptfx--;
while (ppr != ldOut.ppr ||
pptfx < ldOut.pptfx ||
bInvert != ldOut.bInvert())
{
PPOINTFIX pptfxStart = &ppr->aptfx[1];
while (pptfx > pptfxStart)
{
wid.vAddLeftNice((PEVECTORFX) pptfx--, bInvert);
cpt++;
}
if (ppr->pprprev != (PPATHREC) NULL)
ppr = ppr->pprprev;
else
{
ppr = ppath->pprlast;
bInvert = !bInvert;
}
pptfx = &ppr->aptfx[ppr->count - 1];
}
ASSERTGDI(ppr == ldOut.ppr, "Unmatched path record pointers");
ASSERTGDI(pptfx - ldOut.pptfx < (PATHALLOCSIZE >> 3),
"Counterclockwise join unsynced");
while (pptfx > ldOut.pptfx)
{
wid.vAddLeftNice((PEVECTORFX) pptfx--, bInvert);
cpt++;
}
if (bOutPerp && !ldOut.bToLeftSide())
{
wid.vAddLeftNice((PEVECTORFX) pptfx, bInvert);
}
}
return(cpt);
}
/******************************Public*Routine******************************\
* WIDEPENOBJ::vAddRoundEndCap(wid, ld, bStartCap, bRound)
*
* Adds round caps to the path.
*
* History:
* 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
VOID WIDEPENOBJ::vAddRoundEndCap(
WIDENER& wid, // Path to which to add
LINEDATA& ld, // Vector entering the point
BOOL bStartCap, // TRUE if Start-cap
BOOL bRound) // TRUE if round joins
{
ASSERTGDI(ld.bVecDrawComputed(), "Draw vertex not computed");
BOOL bInvert = (bStartCap) ? !ld.bInvert() : ld.bInvert();
PPATHREC ppr = ld.ppr;
PPOINTFIX pptfx = ld.pptfx;
// Turns left, so add counter-clockwise to right side:
if (!bRound && !ld.bToLeftSide())
wid.vAddRightNice((PEVECTORFX) pptfx, bInvert);
pptfx++;
while (ppr != ld.ppr ||
pptfx > ld.pptfx)
{
PPOINTFIX pptfxEnd = &ppr->aptfx[ld.ppr->count - 1];
while (pptfx < pptfxEnd)
wid.vAddRightNice((PEVECTORFX) pptfx++, bInvert);
if (ppr->pprnext != (PPATHREC) NULL)
ppr = ppr->pprnext;
else
{
ppr = ppath->pprfirst;
bInvert = !bInvert;
}
pptfx = &ppr->aptfx[1];
}
ASSERTGDI(ppr == ld.ppr, "Unmatched path record pointers");
ASSERTGDI(ld.pptfx - pptfx < (PATHALLOCSIZE >> 3),
"Clockwise join unsynced");
while (pptfx < ld.pptfx)
wid.vAddRightNice((PEVECTORFX) pptfx++, bInvert);
if (!bRound && ld.bToLeftSide())
wid.vAddRightNice((PEVECTORFX) pptfx, bInvert);
}
/******************************Public*Routine******************************\
* READER::bNextPoint(ptfx)
*
* Reads the next point from the spine of the path.
*
* History:
* 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
BOOL READER::bNextPoint(POINTFIX& ptfx)
{
BOOL bRet;
if (pptfxRead < pptfxEnd)
{
ptfx = *pptfxRead++;
bRet = TRUE;
}
else if (pd.flags & PD_ENDSUBPATH)
bRet = FALSE;
else
{
// Get next path record:
vMoreToEnum(pepoSpine->bEnum(&pd));
ASSERTGDI(pd.count > 0, "Empty path record");
ASSERTGDI(!(pd.flags & PD_BEGINSUBPATH), "Unexpected begin subpath");
ptfx = pd.pptfx[0];
pptfxRead = &pd.pptfx[1];
pptfxEnd = &pd.pptfx[pd.count];
bRet = TRUE;
}
return(bRet);
}
/******************************Public*Routine******************************\
* READER::bNextFigure()
*
* Goes to the next figure in the path. Returns FALSE if there are none.
*
* History:
* 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
BOOL READER::bNextFigure()
{
BOOL bRet = bMoreToEnum();
if (bRet)
{
vMoreToEnum(pepoSpine->bEnum(&pd));
if (pd.count == 0)
{
ASSERTGDI(!bMoreToEnum(), "Empty path record in non-empty path");
bRet = FALSE;
}
ASSERTGDI((pd.flags & PD_BEGINSUBPATH) || (pd.count == 0),
"bNextFig: Not at start of fig");
pptfxRead = &pd.pptfx[0];
pptfxEnd = &pd.pptfx[pd.count];
}
return(bRet);
}
/******************************Public*Routine******************************\
* LINER::vNextPoint()
*
* This function reads the next point in a path. It will break Bezier
* curves into lines as it goes.
*
* CONSTRUCTING THE PATH BY VERTEX INSTEAD OF BY LINE
*
* The widening system is point based and not line based. That is, we
* read the original path one vertex at-a-time and construct the wide-lines
* by making the appropriate structures at each of those vertices (i.e.,
* at every vertex we either construct an end-cap or a join); we don't read
* the path a line at-a-time and add both ends of the line then do the
* join if there is one.
*
* This is a subtle but important distinction.
*
* RETURNING THE DATA
*
* To construct a join, we need 3 things from the original path:
* 1) The point of the join;
* 2) The vector of the line entering the join;
* 3) The vector of the line exiting the join.
*
* A start cap requires:
* 1) The point of the start cap;
* 2) The vector of the line exiting the start cap.
*
* An end cap requires:
* 1) The point of the end cap;
* 2) The vector of the line entering the end cap.
*
* This data is returned by setting 'ptfxThis' as the point of interest,
* 'pldIn' points to the entering vector, and 'pldOut' points to the
* exiting vector.
*
* We don't incur the cost of re-computing the same perpendicular or draw
* vector for both sides of a line because that's handled by the lazy
* evaluation of 'pldIn' and 'pldOut'.
*
* VARIABLES
*
* Internal:
*
* ptfxNext - Next point after 'ptfxThis', used to compute 'ldOut'
* ptfxStartFigure - Start point for current figure
* ls - Internal state for reading path
* ldStartFigure - Line vector data for first line in figure
* ldStartTangent, ldEndTangent - Line vector data for start and end
* tangents of current Bezier
*
* External:
*
* ptfxThis - Current point
* pldIn, pldOut - Entering and exiting vectors from 'ptfxThis'
*
* pld->vecLine is the difference vector between 'ptfxThis' and 'ptfxNext'
* (so its length is the length of that line; used by styler)
* pld->vecTangent is the true tangent vector at the point
* (this is used for calculating true perpendiculars, and
* its length does NOT correspond to the line at that point)
*
* we - Returns point type at 'ptfxThis':
*
* WE_STARTFIGURE - Figure starts here
* WE_JOIN - Do a normal join
* WE_BEZIERJOIN - Do a Bezier join
* WE_CLOSEFIGURE - Do the final CloseFigure join
* WE_ENDFIGURE - Do the end-cap
* WE_FINISHFIGURE - Do the start-cap for the figure we
* just finished
* WE_DONEPATH - All done!
*
* The resulting language is:
*
* {s{j|b}[c|ef]}d
*
* History:
* 1-Dec-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
VOID LINER::vNextPoint()
{
// Assign 'pldOut' a new spot in the LINEDATA buffer. Just make sure
// the location isn't already being used by 'pldIn':
pldOut = &ldBuf[ (pldIn != &ldBuf[0]) ? 0 : 1 ];
switch (ls)
{
case LS_READPATH:
case LS_STARTFIGURE:
if (ls == LS_READPATH)
we = WE_JOIN; // Normal join
else
{
ASSERTGDI(ls == LS_STARTFIGURE, "Not start figure");
we = WE_STARTFIGURE; // The point starts a figure
ls = LS_READPATH;
}
if (bNextPoint(ptfxNext))
{
// The next point in the path may be the second control point
// of a Bezier curve. If it is, we have to set up to crack the
// curve into lines:
if (bIsBezier())
{
POINTFIX aptfx[4];
aptfx[0] = ptfxThis;
aptfx[1] = ptfxNext;
bNextPoint(aptfx[2]);
bNextPoint(aptfx[3]);
bez.vInit(aptfx);
if (!bez.bNext(&ptfxNext))
{
// Okay, we have a real small Bezier curve (it got converted
// into one line). Don't bother trying to use the Bezier's
// control points to get accurate perps (that is a heuristic
// that only works when you have lotsa lines in the
// approximation):
pldOut->vInit(ptfxNext, ptfxThis);
ls = LS_READPATH;
return;
}
pldOut->vInit(ptfxNext, ptfxThis);
ldStartTangent = *pldOut;
ldStartTangent.vecTangent = aptfx[1];
ldStartTangent.vecTangent -= aptfx[0];
// Initialize end-tangent vector with the true end-tangent
// (we will change 'ldEndTangent.vecLine' later):
ldEndTangent.vInit(aptfx[3], aptfx[2]);
pldOut = &ldStartTangent;
// Set up to get the next point from the current Bezier:
ls = LS_READBEZIER;
return;
}
}
else
{
// Since there is no 'ptfxNext', it follows that 'ptfxThis' is the
// last point in the figure. See if we have to close the figure.
ls = LS_FINISHFIGURE;
ptfxNext = ptfxStartFigure;
if (bIsClosedFigure())
we = WE_JOIN;
else
{
we = WE_ENDFIGURE;
return;
}
}
break;
case LS_FINISHFIGURE:
we = bIsClosedFigure() ? WE_CLOSEFIGURE : WE_FINISHFIGURE;
pldOut = &ldStartFigure;
if (!bNextFigure())
ls = LS_DONEPATH;
else
{
bNextPoint(ptfxNext);
ptfxStartFigure = ptfxNext;
ls = LS_STARTFIGURE;
}
return;
case LS_READBEZIER:
we = WE_BEZIERJOIN;
if (!bez.bNext(&ptfxNext))
{
// Since this is the last line in the Bezier, make sure we use
// the tangent vector computed from the last two control points
// when calculating perpendicular for the line:
ls = LS_READPATH;
pldOut->vInit(ptfxNext, ptfxThis);
pldOut->vecTangent = ldEndTangent.vecTangent;
return;
}
break;
case LS_DONEPATH:
we = WE_DONEPATH;
return;
default:
RIP("Unknown line state");
}
// Finally, compute the exit vector for those cases that made it here:
pldOut->vInit(ptfxNext, ptfxThis);
}
/******************************Public*Routine******************************\
* LINER::vZeroFigure()
*
* Sets pldOut and pldIn for a zero-length figure.
*
* History:
* 31-Dec-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
INLINE VOID LINER::vZeroFigure()
{
ldStartFigure.vInit();
ldStartFigure.vecLine.x = LTOFX(1);
ldStartFigure.vecTangent.x = LTOFX(1);
ldStartFigure.vecLine.y = 0;
ldStartFigure.vecTangent.y = 0;
pldIn = &ldStartFigure;
pldOut = &ldStartFigure;
}
/******************************Public*Routine******************************\
* LINER::vNextEvent()
*
* This function reads the next event from the path.
*
* History:
* 31-Dec-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
VOID LINER::vNextEvent()
{
// Update some of our current point data:
ptfxThis = ptfxNext;
pldIn = pldOut;
// Check out the next point:
vNextPoint();
WIDENEVENT weOld = we;
// Eat any zero-length lines. This will stop automatically
while (pldOut->vecLine.x == 0 && pldOut->vecLine.y == 0)
{
if (we != WE_STARTFIGURE && we != WE_JOIN && we != WE_BEZIERJOIN)
break;
// Due to rounding on the integer grid, for small Bezier curves
// the first or last line segments may wind up being zero length.
// In that case, 'vecLine' will be a zero-vector. But we do know
// the true tangents at that point.
vNextPoint();
}
// Have to watch for the case when the vecTagent is zero but vecLine is
// not (such as when the first two or last two control points of the
// Bezier are coincident):
if (pldOut->vecTangent.x == 0 && pldOut->vecTangent.y == 0)
pldOut->vecTangent = pldOut->vecLine;
if (weOld == WE_STARTFIGURE)
{
if (we == WE_CLOSEFIGURE || we == WE_ENDFIGURE)
{
if (we == WE_ENDFIGURE)
{
vNextPoint();
ASSERTGDI(we == WE_FINISHFIGURE, "Expected finish figure");
}
vZeroFigure();
we = WE_ZEROFIGURE;
}
else
{
ldStartFigure = *pldOut;
pldOut = &ldStartFigure;
we = WE_STARTFIGURE;
}
}
}
/******************************Public*Routine******************************\
* STYLER::STYLER(epo, dco)
*
* Constructor for the styler.
*
* History:
* 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
STYLER::STYLER(
EPATHOBJ& epo, // Path of spine
PLINEATTRS pla)
: LINER(epo),
exoDeviceToWorld(&mxDeviceToWorld, DONT_COMPUTE_FLAGS)
{
vDoingStyles((pla->pstyle != (PFLOAT_LONG) NULL) && (pla->cstyle > 0));
if (bDoingStyles())
{
pstyleStart = pla->pstyle;
pstyleCurrent = pstyleStart;
pstyleEnd = pstyleStart + pla->cstyle;
// Next point comes from the path, not from a style:
vStyleNext(FALSE);
}
}
/******************************Public*Routine******************************\
* STYLER::efNextStyleLength()
*
* Reads next style array entry from user-supplied list.
*
* History:
* 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
INLINE EFLOAT STYLER::efNextStyleLength()
{
EFLOATEXT efResult(pstyleCurrent->e);
pstyleCurrent++;
if (pstyleCurrent >= pstyleEnd)
pstyleCurrent = pstyleStart;
ASSERTGDI(!efResult.bIsNegative(), "Negative style length");
return(efResult);
}
/******************************Public*Routine******************************\
* STYLER::efWorldLength(vec)
*
* Computes the world space length of the vector.
*
* History:
* 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
EFLOAT STYLER::efWorldLength(EVECTORFX vec)
{
// Transform the vector from device FIX format to world space LONG:
BOOL bRet = exoDeviceToWorld.bXform(&vec, (PVECTORL) &vec, 1);
ASSERTGDI(bRet, "XForm failed");
// Now compute the Cartesian length:
EFLOAT x;
EFLOAT y;
// The Alpha computes an eensy-weensy length for zero-length vectors
// which causes later math to underflow, so we simply check for zero
// length here. The next smallest possible vector has a length of 1.0,
// and that has no chance of underflow.
if ((vec.x == 0) && (vec.y == 0))
{
x = FP_0_0;
}
else
{
x = vec.x;
y = vec.y;
x *= x;
y *= y;
x += y;
x.vSqrt();
}
return(x);
}
/******************************Public*Routine******************************\
* ptfxFraction(ptfx, pld, efDistance, efLineLength)
*
* Returns ptfx + pld->vecLine * (efDistance / efLineLength).
*
* History:
* 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
POINTFIX ptfxFraction(
POINTFIX ptfx,
PLINEDATA pld,
EFLOAT& efDistance,
EFLOAT& efLineLength)
{
ASSERTGDI(efDistance <= efLineLength, "More than line length");
ASSERTGDI(efDistance >= FP_0_0, "Negative distance");
POINTFIX ptfxResult;
if (efLineLength.bIsZero())
return(ptfx);
if (!pld->bNormalizedComputed())
{
pld->ptflNormalized.x = (LONG) pld->vecLine.x;
pld->ptflNormalized.y = (LONG) pld->vecLine.y;
// Assume a floating point multiply is twice as fast as a divide:
EFLOAT efFactor;
efFactor = FP_1_0;
efFactor /= efLineLength;
pld->ptflNormalized.x *= efFactor;
pld->ptflNormalized.y *= efFactor;
pld->vSetNormalizedComputed();
}
POINTFL ptfl;
ptfl = pld->ptflNormalized;
ptfl.x *= efDistance;
ptfl.y *= efDistance;
LONG x;
LONG y;
// bEfToL has to take a LONG argument (can't be a FIX cast to a LONG):
BOOL bRet1 = ptfl.x.bEfToL(x);
BOOL bRet2 = ptfl.y.bEfToL(y);
ASSERTGDI(bRet1 && bRet2, "Unexpected overflow");
ptfxResult = ptfx;
ptfxResult.x += (FIX) x;
ptfxResult.y += (FIX) y;
return(ptfxResult);
}
/******************************Public*Routine******************************\
* STYLER::vNextStyleEvent()
*
* Gets next style event. Could be a join, end-cap, start-cap, etc.
*
* History:
* 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
VOID STYLER::vNextStyleEvent()
{
if (!bDoingStyles())
vNextEvent();
else
{
if (bStyleNext())
{
if (we == WE_STOPDASH)
{
// Handle gaps:
// This following test includes equality in order to eat zero
// length lines when on a zero length style:
while (efStyleLength >= efRemainingLength)
{
efStyleLength -= efRemainingLength;
vNextEvent();
if (we != WE_JOIN && we != WE_BEZIERJOIN)
{
// We've come to the end of the figure, and it's covered
// by a gap. Since we've effectively already put an end-cap
// on the figure (we're in a gap, after all), we can eat
// an end-cap message.
ASSERTGDI(we == WE_CLOSEFIGURE || we == WE_ENDFIGURE,
"Unexpected event");
if (we == WE_ENDFIGURE)
{
// We need the first point in the figure:
vNextEvent();
ASSERTGDI(we == WE_FINISHFIGURE, "Expected finish fig");
}
vStyleNext(FALSE);
we = WE_FINISHFIGURE;
return;
}
efDoneLength = FP_0_0;
efLineLength = efWorldLength(pldOut->vecLine);
efRemainingLength = efLineLength;
ptfxLineStart = ptfxThis;
}
efRemainingLength -= efStyleLength;
efDoneLength += efStyleLength;
ptfxThis = ptfxFraction(ptfxLineStart, pldOut, efDoneLength, efLineLength);
efStyleLength = efNextStyleLength();
we = WE_STARTDASH;
return;
}
else
{
// Handle a dash:
// If you do a Rectangle(100, 100, 200, 200) with flat caps
// and a world transform set, with a style array of
// {100, 30}, I don't want it to wrap around a corner --
// i.e., both perps for the start and end of the first dash
// should be calculated from the vector (-100, 0), and NOT
// have the start calculated from (-100, 0) and the end
// from (0, 100)..
if (efStyleLength > efRemainingLength)
{
efStyleLength -= efRemainingLength;
}
else
{
efRemainingLength -= efStyleLength;
efDoneLength += efStyleLength;
ptfxThis = ptfxFraction(ptfxLineStart, pldOut, efDoneLength, efLineLength);
pldIn = pldOut;
efStyleLength = efNextStyleLength();
we = WE_STOPDASH;
return;
}
}
}
// Okay, we're done with the current line (we've completely styled it),
// so get the next one from the path:
vNextEvent();
switch(we)
{
case WE_STARTFIGURE:
vResetStyles();
efStyleLength = efNextStyleLength();
efDoneLength = FP_0_0;
efLineLength = efWorldLength(pldOut->vecLine);
efRemainingLength = efLineLength;
ptfxLineStart = ptfxThis;
vStyleNext(TRUE);
break;
case WE_JOIN:
case WE_BEZIERJOIN:
efDoneLength = FP_0_0;
efLineLength = efWorldLength(pldOut->vecLine);
efRemainingLength = efLineLength;
ptfxLineStart = ptfxThis;
vStyleNext(TRUE);
break;
default:
vStyleNext(FALSE);
}
}
}
/******************************Public*Routine******************************\
* WIDENER::vVecDrawCompute(ld)
*
* Computes the draw vector for 'ld'.
*
* History:
* 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
VOID WIDENER::vVecDrawCompute(LINEDATA& ld)
{
#ifdef DEBUG_WIDE
ASSERTGDI(ld.vecTangent.x != 0 || ld.vecTangent.y != 0,
"Can't compute draw vertex for zero vector");
#endif
wpen.vDetermineDrawVertex(ld.vecTangent, ld);
if (!ld.bInvert())
ld.vecDraw = *ld.pptfx;
else
{
ld.vecDraw.x = - ld.pptfx->x;
ld.vecDraw.y = - ld.pptfx->y;
}
ld.vSetVecDrawComputed();
vVecSymmetricRound(&ld.vecDraw);
}
/******************************Public*Routine******************************\
* WIDENER::vVecPerpCompute(ld)
*
* Computes the perp vector for 'ld'.
*
* History:
* 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
VOID WIDENER::vVecPerpCompute(LINEDATA& ld)
{
ASSERTGDI(ld.vecTangent.x != 0 || ld.vecTangent.y != 0,
"Can't compute perp for zero vector");
if (!ld.bVecDrawComputed())
vVecDrawCompute(ld);
ASSERTGDI(ld.fxCrossStart >= 0 && ld.fxCrossEnd >= 0, "-ve cross");
LONGLONG aa = ld.fxCrossStart;
LONGLONG bb = ld.fxCrossEnd;
EVECTORFX vec;
if (aa <= bb)
{
vec.x = ld.pptfx->x - (ld.pptfx - 1)->x;
vec.y = ld.pptfx->y - (ld.pptfx - 1)->y;
}
else
{
vec.x = (ld.pptfx + 1)->x - ld.pptfx->x;
vec.y = (ld.pptfx + 1)->y - ld.pptfx->y;
}
POINTFIX ptfxMid;
ptfxMid.x = ld.pptfx->x - (vec.x >> 1);
ptfxMid.y = ld.pptfx->y - (vec.y >> 1);
LONGLONG x;
LONGLONG y;
LONGLONG fxDivisor = aa + bb;
x = aa * ABS(vec.x);
y = aa * ABS(vec.y);
// This is actually only an unsigned divide:
ULONG ulXRemainder;
ULONG ulYRemainder;
if (fxDivisor != 0)
{
if (fxDivisor < ULONG_MAX)
{
VDIV(x, (ULONG) fxDivisor, &ulXRemainder);
VDIV(y, (ULONG) fxDivisor, &ulYRemainder);
}
else
{
// When the numbers are this big, phooie on the rounding:
ulXRemainder = 0;
x /= fxDivisor;
ulYRemainder = 0;
y /= fxDivisor;
}
}
else
{
ulXRemainder = 0;
ulYRemainder = 0;
}
// Now adjust the signs:
FIX xPrime = (LONG) x;
FIX yPrime = (LONG) y;
// This is a safe conversion because fxDivisor is always positive:
ULONG ulHalfDivisor = (ULONG) fxDivisor;
ulHalfDivisor >>= 1;
// Round:
if (ulXRemainder >= ulHalfDivisor)
xPrime++;
if (ulYRemainder >= ulHalfDivisor)
yPrime++;
// Give it the correct sign:
if (vec.x < 0)
xPrime = -xPrime;
if (vec.y < 0)
yPrime = -yPrime;
ld.vecPerp.x = xPrime + ptfxMid.x;
ld.vecPerp.y = yPrime + ptfxMid.y;
if (ld.bInvert())
{
ld.vecPerp.x = -ld.vecPerp.x;
ld.vecPerp.y = -ld.vecPerp.y;
}
ld.vSetVecPerpComputed();
// Rounds to half integer:
vVecRound(&ld.vecPerp);
}
/******************************Public*Routine******************************\
* WIDENER::vVecSquareCompute(ld)
*
* Computes the square cap vector for 'ld'.
*
* History:
* 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
VOID WIDENER::vVecSquareCompute(LINEDATA& ld)
{
ASSERTGDI(ld.vecTangent.x != 0 || ld.vecTangent.y != 0,
"Can't compute square vector for zero vector");
EFLOAT efAlpha;
EFLOAT efLength;
efAlpha = efHalfWidth;
efLength = efWorldLength(ld.vecTangent);
if (!efLength.bIsZero())
efAlpha /= efLength;
else
efAlpha = FP_0_0;
EFLOATEXT x(ld.vecTangent.x);
EFLOATEXT y(ld.vecTangent.y);
x *= efAlpha;
y *= efAlpha;
// We can assert that these conversions won't fail because we already
// did bounds checking on the entire path.
BOOL bRet1 = x.bEfToL(ld.vecSquare.x);
BOOL bRet2 = y.bEfToL(ld.vecSquare.y);
ASSERTGDI(bRet1 && bRet2, "Square vector out of bounds");
ld.vSetVecSquareComputed();
}
/******************************Public*Routine******************************\
* bIs31Bits(fx)
*
* Returns TRUE if 'fx' is a 31-bit signed number (i.e., the upper 2 bits
* are the same).
*
* History:
* 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
INLINE BOOL bIs31Bits(FIX fx)
{
return(fx < ((LONG) LONG_MAX >> 1) && fx > ((LONG) LONG_MIN >> 1));
}
/******************************Public*Routine******************************\
* bComputeIntersect(pvecA, pvecB, pvecC, pvecD, pvecIntersect)
*
* Computes the intersection of the rays defined by the given line segments.
* Returns FALSE if the computation overflows or if the intersection is
* outside the 31 bit space.
*
* History:
* 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
BOOL bComputeIntersect(
PEVECTORFX pvecA, // Start point of first line segment
PEVECTORFX pvecB, // Direction of first line segment
PEVECTORFX pvecC, // Start point of second line segment
PEVECTORFX pvecD, // Direction of second line segment
PEVECTORFX pvecIntersect) // Resulting point of intersection
{
//
// The intersection is computed by:
//
// (Cx - Ax)(-Dy) + (Cy - Ay)(Dx)
// lambda = ------------------------------
// (Bx)(-Dy) + (By)(Dx)
//
// intersect = A + lambda * B
//
EFLOAT efTerm1;
EFLOAT efTerm2;
EFLOAT efNum;
EFLOAT efDenom;
// (Cx - Ax)(-Dy)
efNum = (pvecC->x - pvecA->x);
efTerm2 = -pvecD->y;
efNum *= efTerm2;
// (Cy - Ay)(Dx)
efTerm1 = (pvecC->y - pvecA->y);
efTerm2 = pvecD->x;
efTerm1 *= efTerm2;
efNum += efTerm1;
// (Bx)(-Dy)
efDenom = pvecB->x;
efTerm2 = -pvecD->y;
efDenom *= efTerm2;
// (By)(Dx)
efTerm1 = pvecB->y;
efTerm2 = pvecD->x;
efTerm1 *= efTerm2;
efDenom += efTerm1;
if (efDenom.bIsZero())
return(FALSE);
// lambda
efNum /= efDenom;
// lambda * B
EVECTORFX lambdaB;
efTerm1 = pvecB->x;
efTerm2 = pvecB->y;
efTerm1 *= efNum;
efTerm2 *= efNum;
if (!efTerm1.bEfToL(lambdaB.x) || !efTerm2.bEfToL(lambdaB.y) ||
!bIs31Bits(lambdaB.x) || !bIs31Bits(lambdaB.y))
{
return(FALSE);
}
// A + lambda * B
pvecIntersect->x = pvecA->x + lambdaB.x;
pvecIntersect->y = pvecA->y + lambdaB.y;
// Check for overflow:
if (!bIs31Bits(pvecIntersect->x) || !bIs31Bits(pvecIntersect->y))
{
WARNING("Miter would fall outside device space\n");
return(FALSE);
}
return(TRUE);
}
/******************************Public*Routine******************************\
* WIDENER::bMiterInLimit(vec)
*
* Determines if the resulting vector from the middle of the wideline to
* the outer corner would be within the specified miter limit.
*
* History:
* 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
BOOL WIDENER::bMiterInLimit(EVECTORFX vec)
{
// Note we change our local copy of 'vec':
BOOL bRet = exoDeviceToWorld.bXform(&vec, (PVECTORL) &vec, 1);
ASSERTGDI(bRet, "XForm failed");
// Now compute the square of the Cartesian length:
EFLOAT x;
EFLOAT y;
x = vec.x;
y = vec.y;
x *= x;
y *= y;
x += y;
return(x <= efHalfWidthMiterLimitSquared);
}
/******************************Public*Routine******************************\
* WIDENER:vAddRoundJoin(bBezierJoin)
*
* Adds a join.
*
* History:
* 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
VOID WIDENER::vAddRoundJoin(BOOL bBezierJoin)
{
BOOL bTurnLeft = bTurnLeftRandom(&pldIn->vecTangent, &pldOut->vecTangent);
BOOL bInPerp = FALSE;
BOOL bOutPerp = FALSE;
if (!bAllRound())
{
if (!bBezierJoin)
{
bInPerp = TRUE;
bOutPerp = TRUE;
}
// else
// {
// if (pldIn->bSamePenSection(ldStartTangent)
// || pldIn->bSamePenSection(ldEndTangent))
// bInPerp = TRUE;
//
// if (pldOut->bSamePenSection(ldStartTangent)
// || pldOut->bSamePenSection(ldEndTangent))
// bOutPerp = TRUE;
// }
}
EVECTORFX vecIn = bInPerp ? vecInPerp() : vecInDraw();
EVECTORFX vecOut = bOutPerp ? vecOutPerp() : vecOutDraw();
vAddRight(vecIn);
vAddLeft(vecIn);
if (vecIn != vecOut)
{
COUNT cpt;
if (bTurnLeft)
{
cpt = wpen.cptAddRound(*this, *pldIn, *pldOut, bTurnLeft, bInPerp, bOutPerp);
vAddLeft();
if (!bAllRound())
// if ((cpt > 0 || bInPerp || bOutPerp) && bBezierJoin) // hmmm and not all round?
{
vAddLeft(vecOut);
if (cpt > 0)
wpen.cptAddRound(*this, *pldOut, *pldIn, !bTurnLeft, bOutPerp, bInPerp);
vAddLeft(vecIn);
vAddLeft();
}
}
else
{
cpt = wpen.cptAddRound(*this, *pldIn, *pldOut, bTurnLeft, bInPerp, bOutPerp);
vAddRight();
if (!bAllRound())
// if ((cpt > 0 || bInPerp || bOutPerp) && bBezierJoin) // hmmm and not all round?
{
vAddRight(vecOut);
if (cpt > 0)
wpen.cptAddRound(*this, *pldOut, *pldIn, !bTurnLeft, bOutPerp, bInPerp);
vAddRight(vecIn);
vAddRight();
}
}
vAddRight(vecOut);
vAddLeft(vecOut);
}
}
/******************************Public*Routine******************************\
* WIDENER:vAddJoin(bBezierJoin)
*
* Adds a join.
*
* History:
* 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
VOID WIDENER::vAddJoin(BOOL bBezierJoin)
{
BOOL bTurnLeft;
if (iJoin == JOIN_ROUND || bBezierJoin)
vAddRoundJoin(bBezierJoin);
else if (iJoin == JOIN_BEVEL)
{
bTurnLeft = bTurnLeftRandom(&pldIn->vecTangent, &pldOut->vecTangent);
EVECTORFX vecIn = vecInPerp();
EVECTORFX vecOut = vecOutPerp();
vAddLeft(vecIn);
vAddRight(vecIn);
if (vecIn != vecOut)
{
if (bTurnLeft)
vAddLeft();
else
vAddRight();
vAddRight(vecOut);
vAddLeft(vecOut);
}
}
else
{
ASSERTGDI(iJoin == JOIN_MITER, "Unexpected join type");
bTurnLeft = bTurnLeftRandom(&pldIn->vecTangent, &pldOut->vecTangent);
// Use the 'perpendicular' vector rather than the 'draw' vector here for
// computing the intersection point. This is done to be consistent:
// if the same path is redrawn but with a different miter limit, we will
// still light the same pels except on the joins that change between
// mitered and beveled.
EVECTORFX vecIn = vecInPerp();
EVECTORFX vecOut = vecOutPerp();
vAddLeft(vecIn);
vAddRight(vecIn);
if (vecIn != vecOut)
{
EVECTORFX vecIntersect;
if (bComputeIntersect(&vecIn, &pldIn->vecLine,
&vecOut, &pldOut->vecLine,
&vecIntersect))
if (bMiterInLimit(vecIntersect))
{
if (bTurnLeft)
vAddRight(vecIntersect);
else
vAddLeft(vecIntersect);
}
if (bTurnLeft)
vAddLeft();
else
vAddRight();
vAddLeft(vecOut);
vAddRight(vecOut);
}
}
}
/******************************Public*Routine******************************\
* WIDENER::vAddEndCap()
*
* Adds an end-cap.
*
* History:
* 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
VOID WIDENER::vAddEndCap()
{
switch(iEndCap)
{
case ENDCAP_SQUARE:
EVECTORFX vecRight;
EVECTORFX vecLeft;
vecRight = vecInSquare();
vecLeft = vecRight;
vecRight += vecInPerp();
vecLeft -= vecInPerp();
vAddRight(vecRight);
vAddRight(vecLeft);
break;
case ENDCAP_BUTT:
EVECTORFX vecInP;
vecInP = vecInPerp();
vAddRight(vecInP, FALSE);
vAddRight(vecInP, TRUE);
break;
case ENDCAP_ROUND:
EVECTORFX vec;
if (bAllRound())
vec = vecInDraw();
else
vec = vecInPerp();
vAddRight(vec, FALSE);
wpen.vAddRoundEndCap(*this, *pldIn, FALSE, bAllRound());
vAddRight(vec, TRUE);
break;
default:
RIP("Unknown cap type");
}
}
/******************************Public*Routine******************************\
* WIDENER::vAddStartCap()
*
* Adds a start cap.
*
* History:
* 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
VOID WIDENER::vAddStartCap()
{
switch(iEndCap)
{
case ENDCAP_SQUARE:
{
EVECTORFX vecRight = vecOutSquare();
EVECTORFX vecLeft;
vecRight.x = -vecRight.x;
vecRight.y = -vecRight.y;
vecLeft = vecRight;
vecRight += vecOutPerp();
vecLeft -= vecOutPerp();
vAddRight(vecLeft);
vAddRight(vecRight);
}
break;
case ENDCAP_BUTT:
EVECTORFX vecOutP;
vecOutP = vecOutPerp();
vAddRight(vecOutP, TRUE);
vAddRight(vecOutP, FALSE);
break;
case ENDCAP_ROUND:
{
EVECTORFX vec;
if (bAllRound())
vec = vecOutDraw();
else
vec = vecOutPerp();
vAddRight(vec, TRUE);
wpen.vAddRoundEndCap(*this, *pldOut, TRUE, bAllRound());
vAddRight(vec, FALSE);
}
break;
default:
RIP("Unknown cap type");
}
}
/******************************Public*Routine******************************\
* BOOL WIDENER::bWiden()
*
* Widens the path.
*
* History:
* 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
BOOL WIDENER::bWiden()
{
while(TRUE)
{
vNextStyleEvent();
switch (we)
{
case WE_ZEROFIGURE:
// If the entire figure is comprised of one point, output the
// pen circle at that point if we have round caps (we can't handle
// square caps because we have no idea of the intended direction):
if (iEndCap == ENDCAP_ROUND)
{
if (!wpathRight.bBeginFigure())
return(FALSE);
vAddStartCap();
vAddEndCap();
// Finish up:
wpathRight.vEndFigure();
wpathRight.vCloseFigure();
}
break;
case WE_STARTFIGURE:
if (!wpathLeft.bBeginFigure() || !wpathRight.bBeginFigure())
return(FALSE);
vFigureStyled(FALSE);
wpathRight.vMarkFigureStart();
break;
case WE_JOIN:
vAddJoin(FALSE);
break;
case WE_BEZIERJOIN:
vAddJoin(TRUE);
break;
case WE_STARTDASH:
if (!wpathLeft.bBeginFigure() || !wpathRight.bBeginFigure())
return(FALSE);
vAddStartCap();
break;
case WE_ENDFIGURE:
case WE_STOPDASH:
vAddEndCap();
// Finish up:
wpathLeft.vEndFigure();
if (!bValid())
return(FALSE);
wpathRight.vReverseConcatenate(wpathLeft);
wpathRight.vEndFigure();
wpathRight.vCloseFigure();
vFigureStyled(TRUE); // We hit a style dash
break;
case WE_FINISHFIGURE:
// Prepend the start cap to the beginning of the figure:
if (!wpathRight.bBeginFigure())
return(FALSE);
vAddStartCap();
wpathRight.vEndFigure();
wpathRight.vPrependBeforeFigure();
break;
case WE_CLOSEFIGURE:
vAddJoin(FALSE);
wpathLeft.vEndFigure();
if (!bFigureStyled())
{
wpathRight.vEndFigure();
wpathRight.vCloseFigure();
if (!wpathRight.bBeginFigure())
return(FALSE);
wpathRight.vReverseConcatenate(wpathLeft);
wpathRight.vEndFigure();
wpathRight.vCloseFigure();
}
else
{
wpathRight.vEndFigure();
if (!wpathRight.bBeginFigure())
return(FALSE);
wpathRight.vReverseConcatenate(wpathLeft);
wpathRight.vEndFigure();
wpathRight.vPrependBeforeSubpath();
wpathRight.vPrependBeforeFigure();
wpathRight.vCloseFigure();
}
break;
case WE_DONEPATH:
return(bValid());
default:
RIP("Unknown widen event");
}
}
RIP("Shouldn't get here");
return(FALSE);
}
/******************************Public*Routine******************************\
* EPATHOBJ::vBecome(wpath)
*
* Free any data in our path, copy all the data from 'epo', and delete
* 'epo'.
*
* Our path can be a PATHMEMOBJ or PATHSTACKOBJ.
*
* History:
* 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
INLINE VOID EPATHOBJ::vBecome(WIDEPATHOBJ& wpath)
{
ASSERTGDI(!(wpath.ppath->flType & PATHTYPE_STACK), "Can't do stacks");
// First, free all the path blocks in our destination path:
vFreeBlocks();
// Now copy all the important stuff about the path from 'wpath', including
// the pointers to all its path data:
cCurves = wpath.cCurves;
ppath->ppachain = wpath.ppath->ppachain;
ppath->pprfirst = wpath.ppath->pprfirst;
ppath->pprlast = wpath.ppath->pprlast;
// The flags, ercfxBoundBox and ptfxCurrent fields should be the same for
// the widened result as they were for the spine, so don't copy them.
ppath->flags |= (PD_BEGINSUBPATH | PD_ENDSUBPATH);
fl &= ~(PO_BEZIERS | PO_ELLIPSE);
// Now delete the wpath path object:
wpath.ppath->ppachain = (PATHALLOC*) NULL;
wpath.vDelete();
}
/******************************Public*Routine******************************\
* WIDENER::vMakeItWide(epo)
*
* Replaces the path 'epo' with its widened result.
*
* History:
* 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
INLINE VOID WIDENER::vMakeItWide(EPATHOBJ& epo)
{
epo.vBecome(wpathRight);
// Recompute the total number of lines in the path:
epo.cCurves = epo.cTotalCurves();
}
/******************************Public*Routine******************************\
* WIDENER::WIDENER(epo, exoWtoD, pla)
*
* Constructor for the widener.
*
* Coming in to here, we expect bounds checking to be already done via
* bComputeWidenedBounds(), so that we don't have to worry about over-
* flowing outside the 31 bit device space.
*
* History:
* 23-Oct-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
WIDENER::WIDENER(
EPATHOBJ& epo,
EXFORMOBJ& exoWtoD,
PLINEATTRS pla)
: STYLER(epo, pla)
{
ASSERTGDI(pla->fl & LA_GEOMETRIC, "Not a geometric line");
ASSERTGDI(epo.bValid(), "Invalid path");
ASSERTGDI(exoWtoD.bValid(), "Invalid xform");
// Bail out here if we failed to allocate any objects. The caller must also
// call WIDENER::bValid() immediately after invoking the WIDENER constructor:
if (!bValid())
return;
iEndCap = pla->iEndCap;
iJoin = pla->iJoin;
// Set 'AllRound' flag if we don't have to ever worry about perpendiculars
// looking funny:
vAllRound((iJoin == JOIN_ROUND) &&
(iEndCap == ENDCAP_ROUND || iEndCap == ENDCAP_SQUARE));
EFLOATEXT efWidth(pla->elWidth.e);
LONG lWidth;
BOOL bRet;
bRet = efWidth.bEfToL(lWidth);
ASSERTGDI(bRet, "Unexpected width overflow");
efHalfWidth = efWidth;
efHalfWidth.vDivBy2();
// Set up for miter limit:
if (iJoin == JOIN_MITER)
{
// Compute (PenWidth * MiterLimit / 2) ^ 2:
efHalfWidthMiterLimitSquared = pla->eMiterLimit;
efHalfWidthMiterLimitSquared *= efHalfWidth;
// *= operator probably can't handle dest and src being the same:
EFLOAT efTemp = efHalfWidthMiterLimitSquared;
efHalfWidthMiterLimitSquared *= efTemp;
}
// Current transform has to be invertible for doing styling or mitering
// (miter limit is specified in world space). Square caps too.
if (pla->pstyle != (PFLOAT_LONG) NULL ||
iEndCap == ENDCAP_SQUARE || iJoin == JOIN_MITER)
{
// Compute inverse of the current transform:
if (exoDeviceToWorld.bInverse(exoWtoD))
{
// Ensure that every vector between every point in the path
// could be transformed to world space:
EVECTORFX avec[2];
avec[0].x = epo.rcfxBoundBox().xRight - epo.rcfxBoundBox().xLeft;
avec[0].y = epo.rcfxBoundBox().yBottom - epo.rcfxBoundBox().yTop;
avec[1].x = -avec[0].x;
avec[1].y = avec[0].y;
if (!exoDeviceToWorld.bXform(avec, (PVECTORL) avec, 2))
vSetError();
}
else
{
SAVE_ERROR_CODE(ERROR_ARITHMETIC_OVERFLOW);
vSetError();
}
}
if (!bValid())
return;
// Do most of the work:
if (!wpen.bPolygonizePen(exoWtoD, lWidth) ||
!bWiden())
{
vSetError();
}
}
/******************************Public*Routine******************************\
* EPATHOBJ::bWiden(epoSpine, exo, pla)
*
* Widens the specified path, overwriting the results of '*this'.
*
* History:
* 3-Feb-1992 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
BOOL EPATHOBJ::bWiden(EPATHOBJ& epoSpine, XFORMOBJ* pxo, LINEATTRS* pla)
{
WIDENER wid(epoSpine, *((EXFORMOBJ*) pxo), pla);
BOOL bValid = wid.bValid();
if (bValid)
wid.vMakeItWide(*this);
return(bValid);
}
/******************************Public*Routine******************************\
* EPATHOBJ::bComputeWidenedBounds(exo, pla)
*
* Adjusts the bounds of the path to allow for joins for geometric
* wide-lines. We do this by transforming the bound box of the pen
* to device space and taking the corners as the extrema of the joins.
*
* For Miter joins, this is an even rougher guess since we assume we'll
* have the longest possible join allowed by the Miter limit.
*
* The values returned are the upper bounds if the path is to be widened
* (which is fine for pointer exclusion).
*
* FALSE is returned if any part of the widened result might fall outside
* the 31 bit device space.
*
* History:
* 3-Feb-1992 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
BOOL EPATHOBJ::bComputeWidenedBounds(
EPATHOBJ& epoSpine,
XFORMOBJ *pexo,
LINEATTRS *pla)
{
ASSERTGDI(epoSpine.bValid(), "Not valid path");
ASSERTGDI(bValid(), "Not valid path");
ASSERTGDI(pla->fl & LA_GEOMETRIC, "Not geometric line");
EFLOATEXT efWidth(pla->elWidth.e);
LONG lWidth;
BOOL b = efWidth.bEfToL(lWidth);
ASSERTGDI(b, "Width too large");
ASSERTGDI(lWidth >= 0, "Negative pen radius");
VECTORL avecl[2];
// We use the diameter and not the radius of the pen here because halving
// it in world space as an integer could possibly lose a lot of precision,
// and it's faster to call 'bXform' with an integer vector than an EFLOAT
// one:
avecl[0].x = lWidth;
avecl[0].y = lWidth;
avecl[1].x = lWidth;
avecl[1].y = -lWidth;
if (!((EXFORMOBJ*) pexo)->bXform(avecl, (PVECTORFX) avecl, 2))
return(FALSE);
// Chop in half to get the maximums of the pen (we were using the diameter
// instead of the radius, remember?). Add a pixel of slop too (remembering
// that there is rounding error, and vAddNice can increase the dimensions
// of the pen slightly):
LONG xAdjust = (MAX(ABS(avecl[0].x), ABS(avecl[1].x)) >> 1) + LTOFX(1);
LONG yAdjust = (MAX(ABS(avecl[0].y), ABS(avecl[1].y)) >> 1) + LTOFX(1);
// Account for square caps by multiplying by a coarse approximation to the
// square root of 2:
if (pla->iEndCap == ENDCAP_SQUARE)
{
xAdjust += (xAdjust >> 1);
yAdjust += (yAdjust >> 1);
// Watch for overflow:
if (!bIs31Bits(xAdjust) || !bIs31Bits(yAdjust))
return(FALSE);
}
// We know here that xAdjust and yAdjust have at most 31 bits significance.
// (Ok, so maybe we overflowed by adding 1).
if (pla->iJoin == JOIN_MITER)
{
EFLOATEXT efMiterLimit(pla->eMiterLimit);
ASSERTGDI(FP_1_0 <= efMiterLimit, "Miter limit less than one");
EFLOATEXT ef(xAdjust);
ef *= efMiterLimit;
if (!ef.bEfToL(xAdjust))
return(FALSE);
ef = yAdjust;
ef *= efMiterLimit;
if (!ef.bEfToL(yAdjust))
return(FALSE);
if (!bIs31Bits(xAdjust) || !bIs31Bits(yAdjust))
return(FALSE);
}
PATH *ppNew = ppath;
PATH *ppOld = epoSpine.ppath;
LONG xLeft = ppOld->rcfxBoundBox.xLeft;
LONG xRight = ppOld->rcfxBoundBox.xRight;
LONG yTop = ppOld->rcfxBoundBox.yTop;
LONG yBottom = ppOld->rcfxBoundBox.yBottom;
// Our widen code expects the coordinates to fix in a 27.4 space in order
// not to overflow (as guaranteed by the DDI spec, but not guaranteed by
// the code):
if (!bIs31Bits(xLeft) || !bIs31Bits(xRight) ||
!bIs31Bits(yTop) || !bIs31Bits(yBottom))
{
return(FALSE);
}
// Copy the bounds from the 'old' spine to the 'new' widened result,
// accounting for the resulting increase in size:
xLeft -= xAdjust;
xRight += xAdjust;
yTop -= yAdjust;
yBottom += yAdjust;
// Make sure we the widened result doesn't fall outside the 31 bit space:
if (!bIs31Bits(xLeft) || !bIs31Bits(xRight) ||
!bIs31Bits(yTop) || !bIs31Bits(yBottom))
{
return(FALSE);
}
ppNew->rcfxBoundBox.xLeft = xLeft;
ppNew->rcfxBoundBox.xRight = xRight;
ppNew->rcfxBoundBox.yTop = yTop;
ppNew->rcfxBoundBox.yBottom = yBottom;
return(TRUE);
}
/******************************Public*Routine******************************\
* EPATHOBJ::vReComputeBounds()
*
* Updates the bounding rectangle, based on reading all the points in
* the path.
*
* History:
* 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
VOID EPATHOBJ::vReComputeBounds()
{
ASSERTGDI(bValid(), "ReComputeWidenedBounds() in invalid path");
PPATHREC ppr;
if (ppath != (PPATH) NULL)
{
if (ppath->pprfirst != (PPATHREC) NULL)
{
ASSERTGDI(ppath->pprfirst->count > 0, "Shouldn't have empty pathrec");
ppath->rcfxBoundBox.xLeft = ppath->pprfirst->aptfx->x;
ppath->rcfxBoundBox.yTop = ppath->pprfirst->aptfx->y;
ppath->rcfxBoundBox.xRight = ppath->rcfxBoundBox.xLeft;
ppath->rcfxBoundBox.yBottom = ppath->rcfxBoundBox.yTop;
for (ppr = ppath->pprfirst; ppr != (PPATHREC) NULL; ppr = ppr->pprnext)
{
PPOINTFIX pptfx = &ppr->aptfx[0];
PPOINTFIX pptfxEnd = &ppr->aptfx[ppr->count];
while (pptfx < pptfxEnd)
((ERECTFX*) &ppath->rcfxBoundBox)->vInclude(*pptfx++);
}
}
else
{
ppath->rcfxBoundBox.xLeft = 0;
ppath->rcfxBoundBox.yTop = 0;
ppath->rcfxBoundBox.xRight = 0;
ppath->rcfxBoundBox.yBottom = 0;
}
}
}
/******************************Public*Routine******************************\
* EPATHOBJ::vWidenSetupForFrameRgn(dco, cxPen, cyPen, pexo, pla)
*
* Initializes a LINEATTRS and EXFORMOBJ to be passed to bWiden to widen
* the path appropriately. Used only for FrameRgn.
*
* The FrameRgn pen is elliptical in world space, whereas the Widen code
* expects it to be circular. So we simply change the transform here.
*
* The FrameRgn ellipse may also have zero-length axis (unlike normal
* widened paths), so a flag must be set for that.
*
* History:
* 12-Sep-1991 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/
VOID EPATHOBJ::vWidenSetupForFrameRgn(
XDCOBJ& dco, // Used to get current world transform
LONG cxPen, // x dimension of pen in world space
LONG cyPen, // y dimension of pen
EXFORMOBJ* pexo, // Must be initialized via 'vInit(&mx, DONT_COMPUTE_FLAGS)'
LINEATTRS* pla)
{
ASSERTGDI(pexo->bValid(), "Xform must be initialized");
// FrameRgn can have zero-width or zero-height brush dimensions:
pla->fl = LA_GEOMETRIC | LA_ALLOW_ZERO_DIMENSIONS;
pla->iJoin = JOIN_MITER;
pla->iEndCap = ENDCAP_ROUND;
pla->eMiterLimit = dco.pdc->l_eMiterLimit();
pla->pstyle = (FLOAT_LONG*) NULL;
pla->cstyle = 0;
// We need to double the pen dimensions because half of the pen
// will draw outside the region, and will be clipped away:
ASSERTGDI(cxPen >= 0 && cyPen >= 0, "Illegal pen dimension");
cxPen *= 2;
cyPen *= 2;
// Make our calculations a little more numerically stable:
BOOL bAdjustX = FALSE;
if (cxPen < cyPen)
{
register LONG lTmp;
SWAPL(cxPen, cyPen, lTmp);
bAdjustX = !bAdjustX;
}
// Figure out the y-value normalizing factor that will make a square
// of dimensions [cxPen, cxPen] become a rectangle [cxPen, cyPen]:
ASSERTGDI(cxPen >= cyPen, "");
EFLOATEXT efX(cxPen);
efX.vEfToF(pla->elWidth.e);
EFLOATEXT efNormalize(cyPen);
if (!efX.bIsZero())
efNormalize /= efX;
// Now copy the world transform and modify it:
pexo->vSet(&dco.pdc->mxWorldToDevice());
if (bAdjustX)
{
pexo->efM11() *= efNormalize;
pexo->efM12() *= efNormalize;
}
else
{
pexo->efM21() *= efNormalize;
pexo->efM22() *= efNormalize;
}
pexo->vComputeAccelFlags();
}