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
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();
|
|
}
|