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.
2114 lines
61 KiB
2114 lines
61 KiB
/******************************Module*Header*******************************\
|
|
* Module Name: draweng.cxx
|
|
*
|
|
* Internal helper functions for GDI draw calls.
|
|
*
|
|
* Created: 19-Nov-1990
|
|
* Author: J. Andrew Goossen [andrewgo]
|
|
*
|
|
* Copyright (c) 1990-1999 Microsoft Corporation
|
|
*
|
|
\**************************************************************************/
|
|
|
|
#include "precomp.hxx"
|
|
|
|
#include "flhack.hxx"
|
|
|
|
#if DBG
|
|
|
|
LONG lConv(EFLOAT ef)
|
|
{
|
|
LONG l;
|
|
ef *= FP_1000_0;
|
|
ef.bEfToL(l);
|
|
return(l);
|
|
}
|
|
|
|
#endif
|
|
|
|
/******************************Public*Routine******************************\
|
|
* EFLOAT efHalfDiff(a, b)
|
|
*
|
|
* Computes (a - b) / 2 without overflow or loss of precision.
|
|
*
|
|
* History:
|
|
* 19-Nov-1990 -by- J. Andrew Goossen [andrewgo]
|
|
* Wrote it.
|
|
\**************************************************************************/
|
|
|
|
inline EFLOAT efHalfDiff(LONG a, LONG b)
|
|
{
|
|
EFLOATEXT efResult((a >> 1) - (b >> 1));
|
|
|
|
if ((a ^ b) & 1)
|
|
{
|
|
if (a & 1)
|
|
efResult += FP_0_5;
|
|
else
|
|
efResult -= FP_0_5;
|
|
}
|
|
|
|
return(efResult);
|
|
}
|
|
|
|
/******************************Public*Routine******************************\
|
|
* EFLOAT efMid(a, b)
|
|
*
|
|
* Computes (a + b) / 2 without overflow or loss of precision. Note that
|
|
* we can't convert 'a' and 'b' to floats and then add them because we're
|
|
* not guaranteed that an 'EFLOAT' will have a mantissa with more than
|
|
* the 32 bit precision of a LONG.
|
|
*
|
|
* History:
|
|
* 19-Nov-1990 -by- J. Andrew Goossen [andrewgo]
|
|
* Wrote it.
|
|
\**************************************************************************/
|
|
|
|
inline EFLOAT efMid(LONG a, LONG b)
|
|
{
|
|
return(efHalfDiff(a, -b));
|
|
}
|
|
|
|
/******************************Public*Routine******************************\
|
|
* EFLOAT efHalf(ul)
|
|
*
|
|
* Compute half of 'ul' without overflow or loss of precision.
|
|
*
|
|
* History:
|
|
* 19-Nov-1990 -by- J. Andrew Goossen [andrewgo]
|
|
* Wrote it.
|
|
\**************************************************************************/
|
|
|
|
inline EFLOAT efHalf(ULONG ul)
|
|
{
|
|
EFLOATEXT efResult((LONG) (ul >> 1));
|
|
|
|
if (ul & 1)
|
|
efResult += FP_0_5;
|
|
|
|
return(efResult);
|
|
}
|
|
|
|
|
|
/******************************Public*Routine******************************\
|
|
* VOID vHalf(ptl)
|
|
*
|
|
* Halves the given vector. Rounds .5 fractions up. Assumes it's in FIX
|
|
* format so that we don't have to worry about overflow.
|
|
*
|
|
* History:
|
|
* 19-Dec-1990 -by- J. Andrew Goossen [andrewgo]
|
|
* Wrote it.
|
|
\**************************************************************************/
|
|
|
|
inline VOID vHalf(POINTL& ptl)
|
|
{
|
|
ptl.x = (ptl.x + 1) >> 1;
|
|
ptl.y = (ptl.y + 1) >> 1;
|
|
}
|
|
|
|
|
|
/******************************Public*Routine******************************\
|
|
* EBOX::EBOX(ercl)
|
|
*
|
|
* EBOX Constructor for figures created by Create-region APIs.
|
|
*
|
|
* History:
|
|
* 19-Nov-1990 -by- J. Andrew Goossen [andrewgo]
|
|
* Wrote it.
|
|
\**************************************************************************/
|
|
|
|
EBOX::EBOX(ERECTL& ercl, BOOL bFillEllipse)
|
|
{
|
|
ercl.vOrder();
|
|
|
|
rclWorld = ercl;
|
|
bIsEmpty = FALSE;
|
|
bIsFillInsideFrame = FALSE;
|
|
|
|
// Do the Win3 silliness and make the box lower-right exclusive
|
|
// (remember that regions are already lower-right exclusive, so this
|
|
// will double the exclusiveness...)
|
|
|
|
aeptl[0].x = LTOFX(ercl.right - 1);
|
|
aeptl[0].y = LTOFX(ercl.top);
|
|
aeptl[2].x = LTOFX(ercl.left);
|
|
aeptl[2].y = LTOFX(ercl.bottom - 1);
|
|
|
|
// If this will be a filled ellipse, we bump up the size in all
|
|
// dimensions to get a nicer looking fill:
|
|
|
|
if (bFillEllipse)
|
|
{
|
|
aeptl[0].x += GROW_ELLIPSE_SIZE - LTOFX(1);
|
|
aeptl[0].y -= GROW_ELLIPSE_SIZE;
|
|
aeptl[2].x -= GROW_ELLIPSE_SIZE;
|
|
aeptl[2].y += GROW_ELLIPSE_SIZE - LTOFX(1);
|
|
}
|
|
|
|
aeptl[1].y = aeptl[0].y;
|
|
aeptl[1].x = aeptl[2].x;
|
|
aeptl[3].x = aeptl[0].x;
|
|
aeptl[3].y = aeptl[2].y;
|
|
|
|
eptlA.x = ((aeptl[0].x - aeptl[1].x) + 1) >> 1;
|
|
eptlA.y = 0;
|
|
eptlB.x = 0;
|
|
eptlB.y = ((aeptl[1].y - aeptl[2].y) + 1) >> 1;
|
|
|
|
eptlOrigin = aeptl[2];
|
|
eptlOrigin += eptlA;
|
|
eptlOrigin += eptlB;
|
|
}
|
|
|
|
|
|
/******************************Public*Routine******************************\
|
|
* EBOX::EBOX(exo, rcl)
|
|
*
|
|
* EBOX Constructor for figures that don't need lower-right exclusion and
|
|
* PS_INSIDEFRAME functionality. Rectangle must already be well-ordered
|
|
* so that (top, left) is the upper-left corner of the box when
|
|
* the World-to-Page transform is identity and the rectangle is transformed
|
|
* to device coordinates.
|
|
*
|
|
* History:
|
|
* 19-Nov-1990 -by- J. Andrew Goossen [andrewgo]
|
|
* Wrote it.
|
|
\**************************************************************************/
|
|
|
|
EBOX::EBOX(
|
|
EXFORMOBJ& exo,
|
|
RECTL& rcl)
|
|
{
|
|
rclWorld = rcl;
|
|
bIsEmpty = FALSE;
|
|
bIsFillInsideFrame = FALSE;
|
|
|
|
aeptl[0].x = rcl.right;
|
|
aeptl[0].y = rcl.top;
|
|
aeptl[1].x = rcl.left;
|
|
aeptl[1].y = rcl.top;
|
|
aeptl[2].x = rcl.left;
|
|
aeptl[2].y = rcl.bottom;
|
|
|
|
exo.bXformRound(aeptl, (PPOINTFIX) aeptl, 3);
|
|
|
|
eptlA = aeptl[0];
|
|
eptlA -= aeptl[1];
|
|
|
|
eptlB = aeptl[1];
|
|
eptlB -= aeptl[2];
|
|
|
|
aeptl[3] = aeptl[2];
|
|
aeptl[3] += eptlA;
|
|
|
|
vHalf(eptlA);
|
|
vHalf(eptlB);
|
|
|
|
eptlOrigin = aeptl[2];
|
|
eptlOrigin += eptlA;
|
|
eptlOrigin += eptlB;
|
|
}
|
|
|
|
/******************************Public*Routine******************************\
|
|
* EBOX::EBOX(dco, rclBox, pla, bFillEllipse)
|
|
*
|
|
* Constructor for figures that need lower-right exclusion and
|
|
* PS_INSIDEFRAME functionality.
|
|
*
|
|
* History:
|
|
* 19-Nov-1990 -by- J. Andrew Goossen [andrewgo]
|
|
* Wrote it.
|
|
\**************************************************************************/
|
|
|
|
EBOX::EBOX(DCOBJ& dco, RECTL& rclBox, LINEATTRS *pla, BOOL bFillEllipse)
|
|
{
|
|
rclWorld = rclBox;
|
|
bIsEmpty = FALSE;
|
|
bIsFillInsideFrame = FALSE;
|
|
|
|
//Shift the rect one pixcel if the dc is mirrored
|
|
if (MIRRORED_DC(dco.pdc)) {
|
|
--rclWorld.left;
|
|
--rclWorld.right;
|
|
}
|
|
if (dco.pdc->iGraphicsMode() == GM_ADVANCED)
|
|
{
|
|
// If we're in advanced mode, we always draw counterclockwise in
|
|
// logical space:
|
|
|
|
((ERECTL*)&rclWorld)->vOrder();
|
|
}
|
|
else
|
|
{
|
|
register LONG lTmp;
|
|
|
|
// Win3 always draws counterclockwise in device space; this means
|
|
// we might be drawing clockwise in logical space. We have to be
|
|
// compatible.
|
|
//
|
|
// There is the additional problem that metafiles may apply a
|
|
// rotating transform on top; we have to correctly rotate the Win3
|
|
// result
|
|
//
|
|
// Order the points so that with an identity world-to-page transform,
|
|
// the drawing direction will always be counterclockwise (or clock-
|
|
// wise, if the DC bit is set) in device space, regardless of the
|
|
// page-to-device transform (Win3 always draws counterclockwise):
|
|
|
|
if ((dco.pdc->befM11IsNegative() && (rclWorld.left < rclWorld.right)) ||
|
|
(!dco.pdc->befM11IsNegative() && (rclWorld.left > rclWorld.right)))
|
|
{
|
|
SWAPL(rclWorld.left, rclWorld.right, lTmp);
|
|
}
|
|
|
|
if ((dco.pdc->befM22IsNegative() && (rclWorld.top < rclWorld.bottom)) ||
|
|
(!dco.pdc->befM22IsNegative() && (rclWorld.top > rclWorld.bottom)))
|
|
{
|
|
SWAPL(rclWorld.bottom, rclWorld.top, lTmp);
|
|
}
|
|
}
|
|
|
|
// To simplify things, we've assumed we'll be drawing counter-clockwise
|
|
// (in logical space if in Advanced mode, in device space if in Compatibility
|
|
// mode). We now check the SetArcDirection setting; if it says to draw
|
|
// clockwise we merely have to flip our bound box upside down:
|
|
|
|
if (dco.pdc->bClockwise())
|
|
{
|
|
register LONG lTmp;
|
|
SWAPL(rclWorld.top, rclWorld.bottom, lTmp);
|
|
}
|
|
|
|
ERECTL ercl(rclWorld);
|
|
|
|
// It was decided that the PS_INSIDEFRAME attribute of the
|
|
// pen current when the call is done is to be used. That is,
|
|
// when accumulating a path, the pen that is current when the
|
|
// figure call is done is used for PS_INSIDEFRAME; all other
|
|
// pen attributes of the path come from the pen that is active
|
|
// when the path is stroked or filled.
|
|
|
|
PPEN ppen = (PPEN) dco.pdc->pbrushLine();
|
|
|
|
EXFORMOBJ exo(dco, WORLD_TO_DEVICE);
|
|
|
|
BOOL bInsideFrame = (ppen->bIsInsideFrame() && (pla->fl & LA_GEOMETRIC));
|
|
|
|
if (bInsideFrame)
|
|
{
|
|
// We have to be careful of overflow because we're dealing with
|
|
// world space coordinates, which may use all 32 bits:
|
|
|
|
EFLOAT efHalfPen = efHalf(ppen->lWidthPen());
|
|
EFLOAT efdx = efHalfDiff(ercl.left, ercl.right);
|
|
EFLOAT efdy = efHalfDiff(ercl.top, ercl.bottom);
|
|
efdx.vAbs();
|
|
efdy.vAbs();
|
|
|
|
// PS_INSIDEFRAME is plain dumb. What happens when the bounding
|
|
// box is smaller in dimension than the width of the pen? For
|
|
// Ellipses, Rectangles and RoundRects, we'll Fill instead of
|
|
// StrokeAndFill'ing. (Do nothing if this occurs for Arcs, Chords
|
|
// or Pies, just like Win3 does.)
|
|
|
|
if (efHalfPen > efdx || efHalfPen > efdy)
|
|
{
|
|
bIsFillInsideFrame = TRUE;
|
|
bInsideFrame = FALSE;
|
|
}
|
|
}
|
|
|
|
// In Win3, figures are lower-right exclusive in device space. This
|
|
// convention is hard to maintain with the introduction of arbitrary
|
|
// affine transforms (like rotations). If the GraphicsMode has been
|
|
// set to advanced, or we're doing an PS_INSIDEFRAME pen, we're
|
|
// lower-right inclusive; otherwise we're lower-right exclusive.
|
|
//
|
|
// The metafile code is able to set a world transform without going
|
|
// to advanced mode, so we check for that too:
|
|
|
|
if (dco.pdc->iGraphicsMode() == GM_ADVANCED ||
|
|
bInsideFrame ||
|
|
bIsFillInsideFrame ||
|
|
dco.pdc->flXform() & WORLD_TRANSFORM_SET)
|
|
{
|
|
// We're lower-right inclusive:
|
|
|
|
aeptl[0].x = ercl.right;
|
|
aeptl[0].y = ercl.top;
|
|
aeptl[1].x = ercl.left;
|
|
aeptl[1].y = ercl.top;
|
|
aeptl[2].x = ercl.left;
|
|
aeptl[2].y = ercl.bottom;
|
|
|
|
exo.bXformRound(aeptl, (PPOINTFIX) aeptl, 3);
|
|
|
|
// HEURISTIC: For a filled ellipse, if the corners of the bound
|
|
// box are on the integer grid, we expand it a bit so that we get
|
|
// a nicer, more symmetric fill according to our filling conventions:
|
|
|
|
if (bFillEllipse && ppen->flStylePen() == PS_NULL &&
|
|
((aeptl[0].x | aeptl[0].y | aeptl[2].x | aeptl[2].y) &
|
|
(LTOFX(1) - 1)) == 0)
|
|
{
|
|
register LONG lDelta;
|
|
|
|
lDelta = (aeptl[0].x > aeptl[2].x) ?
|
|
GROW_ELLIPSE_SIZE :
|
|
-GROW_ELLIPSE_SIZE;
|
|
|
|
aeptl[0].x += lDelta;
|
|
aeptl[1].x -= lDelta;
|
|
aeptl[2].x -= lDelta;
|
|
|
|
lDelta = (aeptl[2].y > aeptl[0].y) ?
|
|
GROW_ELLIPSE_SIZE :
|
|
-GROW_ELLIPSE_SIZE;
|
|
|
|
aeptl[0].y -= lDelta;
|
|
aeptl[1].y -= lDelta;
|
|
aeptl[2].y += lDelta;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Since the DC is not lower right inclusive, it means that we
|
|
// have a simple transform: scaling and translation only.
|
|
|
|
exo.bXformRound((PPOINTL) &ercl, (PPOINTFIX) &ercl, 2);
|
|
|
|
LONG cShrink = LTOFX(1);
|
|
|
|
// HEURISTIC: For a filled ellipse, if the corners of the bound
|
|
// box are on the integer grid, we expand it a bit so that we get
|
|
// a nicer, more symmetric fill according to our filling conventions:
|
|
|
|
if (bFillEllipse && ppen->flStylePen() == PS_NULL &&
|
|
(((ercl.right | ercl.bottom | ercl.left | ercl.top) &
|
|
(LTOFX(1) - 1)) == 0))
|
|
{
|
|
register LONG lDelta;
|
|
|
|
lDelta = (ercl.right > ercl.left) ?
|
|
GROW_ELLIPSE_SIZE :
|
|
-GROW_ELLIPSE_SIZE;
|
|
|
|
ercl.right += lDelta;
|
|
ercl.left -= lDelta;
|
|
|
|
lDelta = (ercl.bottom > ercl.top) ?
|
|
GROW_ELLIPSE_SIZE :
|
|
-GROW_ELLIPSE_SIZE;
|
|
|
|
ercl.top -= lDelta;
|
|
ercl.bottom += lDelta;
|
|
|
|
// We have to shrink by two pixels to be more compatible with Win3
|
|
// in this case:
|
|
|
|
cShrink = LTOFX(2);
|
|
}
|
|
|
|
LONG dx = ercl.right - ercl.left;
|
|
LONG dy = ercl.bottom - ercl.top;
|
|
|
|
if (ABS(dx) < cShrink || ABS(dy) < cShrink)
|
|
{
|
|
bIsEmpty = TRUE;
|
|
return;
|
|
}
|
|
|
|
// Shrink the bounding box for the lower-right exclusivity.
|
|
|
|
if (dx > 0)
|
|
ercl.right -= cShrink;
|
|
else
|
|
ercl.left -= cShrink;
|
|
|
|
if (dy > 0)
|
|
ercl.bottom -= cShrink;
|
|
else
|
|
ercl.top -= cShrink;
|
|
|
|
// It makes no sense to do this when accumulating a path:
|
|
// when the path is to be converted to a region, we have
|
|
// no idea what orientation of the transform is. That is,
|
|
// we can't be sure what sides to push in (we have no idea
|
|
// what the transform will be when the region is painted or
|
|
// whatever). Oh well: win3.x compatibility rules!
|
|
|
|
aeptl[0].x = ercl.right;
|
|
aeptl[0].y = ercl.top;
|
|
aeptl[1].x = ercl.left;
|
|
aeptl[1].y = ercl.top;
|
|
aeptl[2].x = ercl.left;
|
|
aeptl[2].y = ercl.bottom;
|
|
}
|
|
|
|
// Widelines in Win3 are so broken that it's unclear if the
|
|
// PS_INSIDEFRAME adjustment takes lower-right exclusion into
|
|
// account. We let the region lower-right exclusion take care
|
|
// of it.
|
|
|
|
if (bInsideFrame)
|
|
{
|
|
// We handle the PS_INSIDEFRAME attribute by shrinking the
|
|
// bound box by half the pen width on all sides. This must
|
|
// be done in device space, otherwise we would lose accuracy.
|
|
//
|
|
// As such, the box is now only a parallelogram as it may
|
|
// have been sheered, etc., and so we must push in the corners
|
|
// using vectors.
|
|
|
|
EAPOINTL avecCorner[2];
|
|
|
|
avecCorner[1].x = avecCorner[1].y = ppen->lWidthPen();
|
|
|
|
// Orient the world space vector avecCorner[1] so that in world space
|
|
// it points from the top, left corner of the bound box towards
|
|
// the center of the box:
|
|
|
|
if (rclWorld.right < rclWorld.left)
|
|
avecCorner[1].x = -avecCorner[1].x;
|
|
|
|
if (rclWorld.bottom < rclWorld.top)
|
|
avecCorner[1].y = -avecCorner[1].y;
|
|
|
|
// Now set avecCorner[0] so that in world space it points from the
|
|
// top, right corner of the bound box towards the center:
|
|
|
|
avecCorner[0].x = -avecCorner[1].x;
|
|
avecCorner[0].y = avecCorner[1].y;
|
|
|
|
// This transform shouldn't fail because we've already stripped
|
|
// the MSBs of ptlPen:
|
|
|
|
exo.bXform((PVECTORL) avecCorner, (PVECTORFX) avecCorner, 2);
|
|
|
|
// We push in the box by only half the pen width, so halve the
|
|
// corner vectors:
|
|
|
|
vHalf(avecCorner[0]);
|
|
vHalf(avecCorner[1]);
|
|
|
|
// We push in all the corners of the bound box by the corner vectors:
|
|
|
|
aeptl[0] += avecCorner[0];
|
|
aeptl[1] += avecCorner[1];
|
|
aeptl[2] -= avecCorner[0];
|
|
}
|
|
|
|
eptlA = aeptl[0];
|
|
eptlA -= aeptl[1];
|
|
|
|
eptlB = aeptl[1];
|
|
eptlB -= aeptl[2];
|
|
|
|
aeptl[3] = aeptl[2];
|
|
aeptl[3] += eptlA;
|
|
|
|
vHalf(eptlA);
|
|
vHalf(eptlB);
|
|
eptlOrigin = aeptl[2];
|
|
eptlOrigin += eptlA;
|
|
eptlOrigin += eptlB;
|
|
}
|
|
|
|
|
|
/******************************Public*Routine******************************\
|
|
* EBOX::ptlXform(ptef)
|
|
*
|
|
* Transforms a point constructed on the unit circle centered at the
|
|
* origin to the ellipse described by the bounding box.
|
|
*
|
|
* (A.x A.y )
|
|
* (x' y') = (x y 1) (B.x B.y )
|
|
* (Origin.x Origin.y)
|
|
*
|
|
* History:
|
|
* 19-Nov-1990 -by- J. Andrew Goossen [andrewgo]
|
|
* Wrote it.
|
|
\**************************************************************************/
|
|
|
|
POINTL EBOX::ptlXform(EPOINTFL& ptef)
|
|
{
|
|
EPOINTL eptl;
|
|
|
|
EFLOATEXT efTerm1(eptlA.x);
|
|
EFLOATEXT efTerm2(eptlB.x);
|
|
efTerm1 *= ptef.x;
|
|
efTerm2 *= ptef.y;
|
|
efTerm1 += efTerm2;
|
|
efTerm1.bEfToL(eptl.x);
|
|
|
|
efTerm1 = eptlA.y;
|
|
efTerm2 = eptlB.y;
|
|
efTerm1 *= ptef.x;
|
|
efTerm2 *= ptef.y;
|
|
efTerm1 += efTerm2;
|
|
efTerm1.bEfToL(eptl.y);
|
|
|
|
eptl += eptlOrigin;
|
|
|
|
return(eptl);
|
|
}
|
|
|
|
/******************************Public*Routine******************************\
|
|
* VOID vArctan(x, y, efTheta, lQuadrant)
|
|
*
|
|
* Returns the Arctangent angle in degrees. Uses a look-up table with
|
|
* linear interpolation. Accuracy is kinda good, I guess, with a table
|
|
* size of 32. Returns the quadrant of the angle (0 through 3).
|
|
*
|
|
* History:
|
|
* Wed 23-Oct-1991 09:39:21 by Kirk Olynyk [kirko]
|
|
* This routine is now used in FONTMAP.CXX. Please be careful when
|
|
* modifying.
|
|
* 19-Nov-1990 -by- J. Andrew Goossen [andrewgo]
|
|
* Wrote it.
|
|
\**************************************************************************/
|
|
|
|
const BYTE gajArctanQuadrant[] = { 0, 1, 3, 2, 0, 1, 3, 2 };
|
|
|
|
VOID vArctan
|
|
(
|
|
EFLOAT x,
|
|
EFLOAT y,
|
|
EFLOAT& efTheta,
|
|
LONG& lQuadrant
|
|
)
|
|
{
|
|
LONG lOctant = 0;
|
|
|
|
if (x.bIsNegative())
|
|
{
|
|
x.vNegate();
|
|
lOctant |= NEGATE_X;
|
|
}
|
|
if (y.bIsNegative())
|
|
{
|
|
y.vNegate();
|
|
lOctant |= NEGATE_Y;
|
|
}
|
|
if (y > x)
|
|
{
|
|
EFLOAT ef = x;
|
|
x = y;
|
|
y = ef;
|
|
lOctant |= SWITCH_X_AND_Y;
|
|
}
|
|
|
|
// If x == 0 and y == 0, Arctan is undefined. May as well return 0.
|
|
|
|
if (x.bIsZero())
|
|
{
|
|
efTheta = FP_0_0;
|
|
lQuadrant = 0;
|
|
return;
|
|
}
|
|
|
|
// Calculate efIndex = (y / x) * ARCTAN_TABLE_SIZE:
|
|
|
|
EFLOAT efIndex = y;
|
|
efIndex *= FP_ARCTAN_TABLE_SIZE;
|
|
efIndex /= x;
|
|
|
|
// lIndex = floor(efIndex):
|
|
|
|
LONG lIndex;
|
|
efIndex.bEfToLTruncate(lIndex);
|
|
|
|
// efDelta = fraction(efIndex):
|
|
|
|
EFLOAT efDelta;
|
|
efIndex.vFraction(efDelta);
|
|
|
|
ASSERTGDI(lIndex >= 0 && lIndex <= ARCTAN_TABLE_SIZE + 1,
|
|
"Arctan: Index out of bounds\n");
|
|
ASSERTGDI(!efDelta.bIsNegative() && FP_1_0 > efDelta,
|
|
"Arctan: Delta out of bounds\n");
|
|
|
|
// gaefArctan has an extra 0 at the end of the table so that
|
|
// calculations for slope == 1 don't require special case code.
|
|
//
|
|
// efTheta = gaefArctan[lIndex]
|
|
// + efDelta * (gaefArctan[lIndex + 1] - gaefArctan[lIndex]):
|
|
|
|
efTheta = gaefArctan[lIndex + 1];
|
|
efTheta -= gaefArctan[lIndex];
|
|
efTheta *= efDelta;
|
|
efTheta += gaefArctan[lIndex];
|
|
|
|
switch (lOctant)
|
|
{
|
|
case OCTANT_1:
|
|
{
|
|
efTheta.vNegate();
|
|
efTheta += FP_90_0;
|
|
break;
|
|
}
|
|
case OCTANT_2:
|
|
{
|
|
efTheta += FP_90_0;
|
|
break;
|
|
}
|
|
case OCTANT_3:
|
|
{
|
|
efTheta.vNegate();
|
|
efTheta += FP_180_0;
|
|
break;
|
|
}
|
|
case OCTANT_4:
|
|
{
|
|
efTheta += FP_180_0;
|
|
break;
|
|
}
|
|
case OCTANT_5:
|
|
{
|
|
efTheta.vNegate();
|
|
efTheta += FP_270_0;
|
|
break;
|
|
}
|
|
case OCTANT_6:
|
|
{
|
|
efTheta += FP_270_0;
|
|
break;
|
|
}
|
|
case OCTANT_7:
|
|
{
|
|
efTheta.vNegate();
|
|
efTheta += FP_360_0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ASSERTGDI(!efTheta.bIsNegative() && efTheta <= FP_360_0,
|
|
"Arctan: Weird result\n");
|
|
|
|
lQuadrant = (LONG) gajArctanQuadrant[lOctant];
|
|
return;
|
|
}
|
|
|
|
|
|
/******************************Public*Routine******************************\
|
|
* EFLOAT efSin(efTheta)
|
|
*
|
|
* Returns the Sine of efTheta, which is specified in degrees. Uses
|
|
* a look-up table with linear interpolation for the approximation.
|
|
* It is accurate to within 0.02% using a table size of 32.
|
|
*
|
|
* History:
|
|
* 19-Nov-1990 -by- J. Andrew Goossen [andrewgo]
|
|
* Wrote it.
|
|
\**************************************************************************/
|
|
|
|
EFLOAT efSin(EFLOAT efTheta)
|
|
{
|
|
BOOL bNegate = FALSE;
|
|
EFLOATEXT efResult;
|
|
|
|
// Use property that Sin(-x) = -Sin(x):
|
|
|
|
if (efTheta.bIsNegative())
|
|
{
|
|
bNegate = TRUE;
|
|
efTheta.vNegate();
|
|
}
|
|
|
|
// efIndex = (efTheta / 90) * SINE_TABLE_SIZE:
|
|
|
|
EFLOAT efIndex = efTheta;
|
|
efIndex *= FP_SINE_FACTOR;
|
|
|
|
// Use floor of efIndex to compute table index:
|
|
|
|
LONG lIndex;
|
|
efIndex.bEfToLTruncate(lIndex);
|
|
|
|
// efDelta is used for the linear interpolation:
|
|
|
|
EFLOAT efDelta;
|
|
efIndex.vFraction(efDelta);
|
|
|
|
// Compute the quadrant (0 to 3) in which the angle is:
|
|
|
|
LONG lQuadrant = lIndex >> SINE_TABLE_POWER;
|
|
|
|
// Use property that Sin(180 + x) = -Sin(x):
|
|
|
|
if (lQuadrant & 2)
|
|
bNegate = !bNegate;
|
|
|
|
if (lQuadrant & 1)
|
|
{
|
|
// Use property that Sin(90 + x) = Sin(90 - x):
|
|
|
|
lIndex = SINE_TABLE_SIZE - (lIndex & SINE_TABLE_MASK);
|
|
|
|
// efResult = gaefSin[lIndex]
|
|
// - efDelta * (gaefSin[lIndex] - gaefSin[lIndex - 1]):
|
|
|
|
efResult = gaefSin[lIndex];
|
|
efResult -= gaefSin[lIndex - 1];
|
|
efResult *= efDelta;
|
|
efResult.vNegate();
|
|
efResult += gaefSin[lIndex];
|
|
}
|
|
else
|
|
{
|
|
lIndex &= SINE_TABLE_MASK;
|
|
|
|
// efResult = gaefSin[lIndex]
|
|
// + efDelta * (gaefSin[lIndex + 1] - gaefSin[lIndex]):
|
|
|
|
efResult = gaefSin[lIndex + 1];
|
|
efResult -= gaefSin[lIndex];
|
|
efResult *= efDelta;
|
|
efResult += gaefSin[lIndex];
|
|
}
|
|
|
|
if (bNegate)
|
|
efResult.vNegate();
|
|
|
|
return (efResult);
|
|
}
|
|
|
|
|
|
/******************************Public*Routine******************************\
|
|
* EFLOAT efCos(efTheta)
|
|
*
|
|
* Returns the Cosine of efTheta, which is specified in degrees.
|
|
*
|
|
* Note: Because of rounding errors, for very large values of efTheta,
|
|
* it's possible that the value returned from efCos(efTheta) is
|
|
* approximately that returned from efSin(efTheta).
|
|
*
|
|
* History:
|
|
* 19-Nov-1990 -by- J. Andrew Goossen [andrewgo]
|
|
* Wrote it.
|
|
\**************************************************************************/
|
|
|
|
EFLOAT efCos(EFLOAT efTheta)
|
|
{
|
|
efTheta += FP_90_0;
|
|
return(efSin(efTheta));
|
|
}
|
|
|
|
|
|
/******************************Public*Routine******************************\
|
|
* VOID vCosSin(efTheta, pefCos, pefSin)
|
|
*
|
|
* Returns the Cosine and Sine of efTheta, which is specified in degrees.
|
|
* Uses a look-up table with linear interpolation for the approximation.
|
|
* It is accurate to within 0.02% using a table size of 32.
|
|
*
|
|
* Unlike separately calling efCos(efTheta) and efSin(efTheta), this function
|
|
* will at least guarantee that the returned point is on the unit circle.
|
|
* However, for APIs such as Arc() and AngleArc(), the error in the computation
|
|
* will be far enough off the unit circle so that those APIs don't function
|
|
* correctly. In those cases where even a small error can be significant, one
|
|
* should use vCosSinPrecise() to get an exact computation (to within the precision
|
|
* of a float) of sine and cosine.
|
|
*
|
|
* History:
|
|
* 5-May-1993 -by- J. Andrew Goossen [andrewgo]
|
|
* Wrote it.
|
|
\**************************************************************************/
|
|
|
|
VOID vCosSin(EFLOAT efTheta, EFLOAT* pefCos, EFLOAT* pefSin)
|
|
{
|
|
BOOL bNegate = FALSE;
|
|
EFLOATEXT efResult;
|
|
LONG lTmpIndex;
|
|
|
|
// ------------------------------------------------------------------
|
|
// Handle setup common to both Sin and Cos.
|
|
|
|
// Use property that Sin(-x) = -Sin(x):
|
|
|
|
if (efTheta.bIsNegative())
|
|
{
|
|
bNegate = TRUE;
|
|
efTheta.vNegate();
|
|
}
|
|
|
|
// efIndex = (efTheta / 90) * SINE_TABLE_SIZE:
|
|
|
|
EFLOAT efIndex = efTheta;
|
|
efIndex *= FP_SINE_FACTOR;
|
|
|
|
// Use floor of efIndex to compute table index:
|
|
|
|
LONG lIndex;
|
|
efIndex.bEfToLTruncate(lIndex);
|
|
|
|
// efDelta is used for the linear interpolation:
|
|
|
|
EFLOAT efDelta;
|
|
efIndex.vFraction(efDelta);
|
|
|
|
// Compute the quadrant (0 to 3) in which the angle is:
|
|
|
|
LONG lQuadrant = lIndex >> SINE_TABLE_POWER;
|
|
|
|
// ------------------------------------------------------------------
|
|
// Now handle Sin(efTheta).
|
|
|
|
// Use property that Sin(180 + x) = -Sin(x):
|
|
|
|
if (lQuadrant & 2)
|
|
bNegate = !bNegate;
|
|
|
|
if (lQuadrant & 1)
|
|
{
|
|
// Use property that Sin(90 + x) = Sin(90 - x):
|
|
|
|
lTmpIndex = SINE_TABLE_SIZE - (lIndex & SINE_TABLE_MASK);
|
|
|
|
// efResult = gaefSin[lTmpIndex]
|
|
// - efDelta * (gaefSin[lTmpIndex] - gaefSin[lTmpIndex - 1]):
|
|
|
|
efResult = gaefSin[lTmpIndex];
|
|
efResult -= gaefSin[lTmpIndex - 1];
|
|
efResult *= efDelta;
|
|
efResult.vNegate();
|
|
efResult += gaefSin[lTmpIndex];
|
|
}
|
|
else
|
|
{
|
|
lIndex &= SINE_TABLE_MASK;
|
|
|
|
// efResult = gaefSin[lIndex]
|
|
// + efDelta * (gaefSin[lIndex + 1] - gaefSin[lIndex]):
|
|
|
|
efResult = gaefSin[lIndex + 1];
|
|
efResult -= gaefSin[lIndex];
|
|
efResult *= efDelta;
|
|
efResult += gaefSin[lIndex];
|
|
}
|
|
|
|
if (bNegate)
|
|
efResult.vNegate();
|
|
|
|
*pefSin = efResult;
|
|
|
|
// ------------------------------------------------------------------
|
|
// Now handle Cos(efTheta).
|
|
|
|
// Since Cos(-x) = Cos(x), bNegate is always initially false:
|
|
|
|
bNegate = FALSE;
|
|
|
|
// We use the property that Cos(x) = Sin(x + 90) to convert the
|
|
// problem to determining the Sine again:
|
|
|
|
lQuadrant++;
|
|
|
|
// Use property that Sin(180 + x) = -Sin(x):
|
|
|
|
if (lQuadrant & 2)
|
|
bNegate = !bNegate;
|
|
|
|
if (lQuadrant & 1)
|
|
{
|
|
// Use property that Sin(90 + x) = Sin(90 - x):
|
|
|
|
lTmpIndex = SINE_TABLE_SIZE - (lIndex & SINE_TABLE_MASK);
|
|
|
|
// efResult = gaefSin[lTmpIndex]
|
|
// - efDelta * (gaefSin[lTmpIndex] - gaefSin[lTmpIndex - 1]):
|
|
|
|
efResult = gaefSin[lTmpIndex];
|
|
efResult -= gaefSin[lTmpIndex - 1];
|
|
efResult *= efDelta;
|
|
efResult.vNegate();
|
|
efResult += gaefSin[lTmpIndex];
|
|
}
|
|
else
|
|
{
|
|
lIndex &= SINE_TABLE_MASK;
|
|
|
|
// efResult = gaefSin[lIndex]
|
|
// + efDelta * (gaefSin[lIndex + 1] - gaefSin[lIndex]):
|
|
|
|
efResult = gaefSin[lIndex + 1];
|
|
efResult -= gaefSin[lIndex];
|
|
efResult *= efDelta;
|
|
efResult += gaefSin[lIndex];
|
|
}
|
|
|
|
if (bNegate)
|
|
efResult.vNegate();
|
|
|
|
*pefCos = efResult;
|
|
}
|
|
|
|
|
|
/******************************Public*Routine******************************\
|
|
* VOID vCosSinPrecise(efTheta, pefCos, pefSin)
|
|
*
|
|
* Returns the Cosine and Sine of efTheta, which is specified in degrees.
|
|
* This function should be used when more precision is needed than vCosSin
|
|
* can provide.
|
|
*
|
|
* This function uses the Taylor (actually MacLaurin) expansion of
|
|
* sin x and cos x out to (NUM_TERMS / 2) terms, where x is in radians.
|
|
* The MacLaurin expansions are:
|
|
*
|
|
* cos x = 1 - (x^2)/2! + (x^4)/4! - (x^6)/6! + ... + (-1)^n (x^(2n))/(2n)! + ...
|
|
* sin x = x - (x^3)/3! + (x^5)/5! - (x^7)/7! + ... + (-1)^n (x^(2n+1))/(2n+1)! + ...
|
|
*
|
|
* The result will have error no more than the next term that would have been computed
|
|
* (since it is an alternating and converging series.
|
|
* If x is between 0 and pi/2, then if NUM_TERMS = 13, the error (assuming the
|
|
* floating point compuations are exact) is no more than 5.7e-08, which is less than
|
|
* the precision of an IEEE floating point number (23 bits of precision).
|
|
*
|
|
* History:
|
|
* 19-Feb-1999 -by- Donald Chinn [dchinn]
|
|
* Wrote it.
|
|
\**************************************************************************/
|
|
|
|
#define NUM_TERMS 13
|
|
VOID vCosSinPrecise(EFLOAT efTheta, EFLOAT* pefCos, EFLOAT* pefSin)
|
|
{
|
|
EFLOAT efTemp, efTemp2; // temporaries for intermediate calculation
|
|
BOOL bThetaIsNegative = FALSE;
|
|
BOOL bThetaIsGr180 = FALSE;
|
|
BOOL bThetaIsGr90 = FALSE;
|
|
|
|
ULONG i;
|
|
EFLOAT efThetaRadians; // theta in radians
|
|
EFLOAT efCosResult;
|
|
EFLOAT efSinResult;
|
|
EFLOAT efI; // used to hold the floating point value of i during expansion
|
|
EFLOAT efThetaRadiansPow; // used to hold (efThetaRadians)^i during expansion
|
|
EFLOAT efFactorial; // used to hold i! during expansion
|
|
EFLOAT efTerm; // used to hold +/- (efThetaRadians)^i / (i!)
|
|
|
|
|
|
// sin(x) = - sin(-x)
|
|
// cos(x) = cos(-x)
|
|
if (efTheta.bIsNegative())
|
|
{
|
|
bThetaIsNegative = TRUE;
|
|
efTheta.vNegate();
|
|
}
|
|
|
|
// ASSERT: efTheta >= 0
|
|
|
|
// sin(x) = sin(x + 360*i), for all integers i. (x in degrees)
|
|
// cos(x) = cos(x + 360*i), for all integers i.
|
|
efTemp = efTheta;
|
|
efTemp /= FP_360_0;
|
|
efTemp.vFraction(efTemp2);
|
|
|
|
efTheta = efTemp2;
|
|
efTheta *= FP_360_0;
|
|
|
|
// ASSERT: 0 <= efTheta < 360
|
|
|
|
// if 0 <= x <= 360 (x in degrees), then
|
|
// sin(x) = - sin(360 - x)
|
|
// cos(x) = cos(360 - x).
|
|
|
|
// if efTheta > 180, then set efTheta = 360 - efTheta.
|
|
efTemp = FP_180_0;
|
|
efTemp -= efTheta;
|
|
if (efTemp.bIsNegative())
|
|
{
|
|
bThetaIsGr180 = TRUE;
|
|
efTemp = FP_360_0;
|
|
efTemp -= efTheta;
|
|
efTheta = efTemp;
|
|
}
|
|
|
|
// ASSERT: 0 <= efTheta <= 180
|
|
|
|
// if 0 <= x <= 180 (x in degrees), then
|
|
// sin(x) = sin(180 - x)
|
|
// cos(x) = - cos(180 - x).
|
|
|
|
// if efTheta > 90, then set efTheta = 180 - efTheta.
|
|
efTemp = FP_90_0;
|
|
efTemp -= efTheta;
|
|
if (efTemp.bIsNegative())
|
|
{
|
|
bThetaIsGr90 = TRUE;
|
|
efTemp = FP_180_0;
|
|
efTemp -= efTheta;
|
|
efTheta = efTemp;
|
|
}
|
|
|
|
// ASSERT: 0 <= efTheta <= 90
|
|
|
|
// convert input angle from degrees to radians
|
|
// efThetaRadians = efTheta * PI / 180;
|
|
efThetaRadians = efTheta;
|
|
efThetaRadians *= FP_PI;
|
|
efThetaRadians /= FP_180_0;
|
|
|
|
// first term of the MacLaurin expansion
|
|
efCosResult = FP_1_0;
|
|
efSinResult = efThetaRadians;
|
|
|
|
// later terms in the MacLaurin expansion
|
|
// in this loop, i corresponds to the term +/- (efThetaRadians)^i/(i!)
|
|
for (i = 2, efI = FP_2_0, efFactorial = FP_2_0, efThetaRadiansPow = efThetaRadians;
|
|
i < NUM_TERMS;
|
|
i++, efI += FP_1_0, efFactorial *= efI)
|
|
{
|
|
// compute (efThetaRadians)^i
|
|
efThetaRadiansPow *= efThetaRadians;
|
|
|
|
// ASSERT: at this point, i, efI, efThetaRadiansPow, and efFactorial are all consistent
|
|
|
|
// compute the i-th term
|
|
efTerm = efThetaRadiansPow;
|
|
efTerm /= efFactorial;
|
|
|
|
// sign of the term -- the pattern is:
|
|
// if i == 2 or 3, then the sign is negative;
|
|
// if i == 4 or 5, then the sign is positive;
|
|
// etc. (the pattern repeats every two values of i)
|
|
if ((i / 2) % 2)
|
|
{
|
|
// i/2 is odd -- sign is negative
|
|
efTerm.vNegate();
|
|
}
|
|
|
|
// add the term to the appropriate expansion
|
|
if (i % 2)
|
|
{
|
|
// i is odd -- add to the sine expansion
|
|
efSinResult += efTerm;
|
|
}
|
|
else
|
|
{
|
|
// i is even -- add to the cosine expansion
|
|
efCosResult += efTerm;
|
|
}
|
|
|
|
}
|
|
|
|
// Adjust the sign of the result
|
|
if ((bThetaIsNegative && !bThetaIsGr180) ||
|
|
(!bThetaIsNegative && bThetaIsGr180))
|
|
{
|
|
efSinResult.vNegate();
|
|
}
|
|
|
|
if (bThetaIsGr90)
|
|
{
|
|
efCosResult.vNegate();
|
|
}
|
|
|
|
*pefCos = efCosResult;
|
|
*pefSin = efSinResult;
|
|
}
|
|
|
|
|
|
/******************************Public*Routine******************************\
|
|
* BOOL bPartialQuadrantArc(paType, epo, ebox, efStartAngle, efEndAngle)
|
|
*
|
|
* Constructs a partial arc of 90 degrees or less using an approximation
|
|
* technique by Kirk Olynyk. The arc is approximated by a cubic Bezier.
|
|
* Optionally draws a line to the first point.
|
|
*
|
|
* Restrictions:
|
|
*
|
|
* efEndAngle must be within 90 degrees of efStartAngle.
|
|
*
|
|
* Steps in constructing the curve:
|
|
*
|
|
* 1) Construct the conic section at the origin for the unit circle;
|
|
* 2) Approximate this conic by a cubic Bezier;
|
|
* 3) Scale and translate result.
|
|
*
|
|
* 1) Constructing the Conic
|
|
*
|
|
* 'efStartAngle' and 'efEndAngle' determine the end-points of the
|
|
* conic (call them vectors from the origin, A and C). We need the
|
|
* middle vector B and the sharpness to completely determine the
|
|
* conic.
|
|
*
|
|
* For the portion of a circular arc that is 90 degrees or less,
|
|
* conic sharpness is Cos((efEndAngle - efStartAngle) / 2).
|
|
*
|
|
* B is calculated by the intersection of the two lines that are
|
|
* at the ends of A and C and are perpendicular to A and C,
|
|
* respectively. That is, since A and C lie on the unit circle, B
|
|
* is the point of intersection of the two lines that are tangent
|
|
* to the unit circle at A and C.
|
|
*
|
|
* If A = (a, b), then the equation of the line through (a, b)
|
|
* tangent to the circle is ax + by = 1. Similarly, for
|
|
* C = (c, d), the equation of the line is cx + dy = 1. The
|
|
* intersection of these two lines is defined by:
|
|
*
|
|
* x = (d - b) / (ad - bc)
|
|
* and y = (a - c) / (ad - bc).
|
|
*
|
|
* Then, B = (x, y).
|
|
*
|
|
* 2) Approximating the conic as a Bezier cubic
|
|
*
|
|
* For sharpness values 'close' to 1, the conic may be approximated
|
|
* by a cubic Bezier; error is less for sharpnesses closer to 1.
|
|
*
|
|
* Error
|
|
*
|
|
* Since the largest angle handled by this routine is 90 degrees,
|
|
* sharpness is guaranteed to be between 1 / sqrt(2) = .707 and 1.
|
|
* Error in the approximation for a 90 degree arc is approximately
|
|
* 0.2%; it is less for smaller angles. 0.2% is deemed small
|
|
* enough error; thus, a 90 degree circular arc is always
|
|
* approximated by just one Bezier.
|
|
*
|
|
* One notable implication of the fact that arcs have less error
|
|
* for smaller angles is that when a partial arc is xor-ed with
|
|
* the corresponding complete ellipse, some of the partial arc
|
|
* will not be completely xor-ed out. (Too bad.)
|
|
*
|
|
* Given a conic section defined by (A, B, C, S), we find the
|
|
* cubic Bezier defined by the four control points (V0, V1, V2, V3)
|
|
* that provides the closest approimxation. We require that the
|
|
* Bezier be tangent to the triangle at the same endpoints. That is,
|
|
*
|
|
* V1 = (1 - Tau1) A + (Tau1) B
|
|
* V2 = (1 - Tau2) C + (Tau2) B
|
|
*
|
|
* Simplify by taking Tau = Tau1 = Tau2, and we get:
|
|
*
|
|
* V0 = A
|
|
* V1 = (1 - Tau) A + (Tau) B
|
|
* V2 = (1 - Tau) C + (Tau) B
|
|
* V3 = C
|
|
*
|
|
*
|
|
* Where Tau = 4 S / (3 (S + 1)), S being the sharpness.
|
|
* S = cos(angle / 2) for an arc of 90 degrees or less.
|
|
* So, for one quadrant of a circle, and since A and B actually
|
|
* extend from the corners of the bound box, and not the center,
|
|
*
|
|
* Tau = 1 - (4 * cos(45)) / (3 * (cos(45) + 1)) = 0.44772...
|
|
*
|
|
* See Kirk Olynyk's "Conics to Beziers" for more.
|
|
*
|
|
* 3) The arc is transformed to the bound box.
|
|
*
|
|
* History:
|
|
* 27-Nov-1990 -by- J. Andrew Goossen [andrewgo]
|
|
* Wrote it.
|
|
\**************************************************************************/
|
|
|
|
BOOL bPartialQuadrantArc
|
|
(
|
|
PARTIALARC paType, // MoveToEx or LineTo the first point
|
|
EPATHOBJ& epo,
|
|
EBOX& ebox, // Bound box
|
|
EPOINTFL& eptefVecA,
|
|
EFLOAT& efStartAngle,
|
|
EPOINTFL& eptefVecC,
|
|
EFLOAT& efEndAngle
|
|
)
|
|
{
|
|
|
|
EPOINTFL ptefV0;
|
|
EPOINTFL ptefV1;
|
|
EPOINTFL ptefV2;
|
|
EPOINTFL ptefV3;
|
|
|
|
EPOINTFL eptefVecB;
|
|
|
|
// Do some explicit common sub-expression elimination:
|
|
|
|
// efDenom = eptefVecA.x * eptefVecC.y - eptefVecA.y * eptefVecC.x;
|
|
|
|
EFLOAT efTerm2 = eptefVecA.y;
|
|
efTerm2 *= eptefVecC.x;
|
|
|
|
EFLOAT efDenom = eptefVecA.x;
|
|
efDenom *= eptefVecC.y;
|
|
efDenom -= efTerm2;
|
|
efDenom.vAbs();
|
|
|
|
// efDenom == 0 if eptefVecA and eptefVecC are parallel vectors.
|
|
// Since they're both centered at the origin, and are in the
|
|
// same quadrant, this implies that eptefVecA == eptefVecC, or
|
|
// equivalently, efStartAngle == efEndAngle, which we special case.
|
|
//
|
|
// Compare to epsilon, which is arbitrarily 2 ^ -16, thus skipping
|
|
// angles of less than 0.000874 degrees.
|
|
|
|
if (efDenom <= FP_EPSILON)
|
|
{
|
|
|
|
// We have a zero degree arc. If we're doing a _LINETO or _MOVETO,
|
|
// we have to set the current point in the path. We can't early
|
|
// out if doing a _CONTINUE because of AngleArc in XOR mode doing
|
|
// a sweep of more than 360 degrees when the start angle is a
|
|
// multiple of 90 degrees: bPartialQuadrantArc may have left the
|
|
// current position in a slightly different point what we will
|
|
// expect for the next part of the arc, due to rounding error.
|
|
|
|
ptefV0 = eptefVecA;
|
|
ptefV1 = ptefV0;
|
|
ptefV2 = eptefVecC;
|
|
ptefV3 = ptefV2;
|
|
}
|
|
else
|
|
{
|
|
|
|
// eptefVecB.x = (eptefVecC.y - eptefVecA.y) / efDenom;
|
|
// eptefVecB.y = (eptefVecA.x - eptefVecC.x) / efDenom;
|
|
|
|
eptefVecB.x = eptefVecC.y;
|
|
eptefVecB.x -= eptefVecA.y;
|
|
eptefVecB.x /= efDenom;
|
|
|
|
eptefVecB.y = eptefVecA.x;
|
|
eptefVecB.y -= eptefVecC.x;
|
|
eptefVecB.y /= efDenom;
|
|
|
|
// efSharp = efCos((efEndAngle - efStartAngle) / 2.0f):
|
|
|
|
EFLOAT efSharp;
|
|
{
|
|
EFLOAT efSweep = efEndAngle;
|
|
efSweep -= efStartAngle;
|
|
efSweep.vDivBy2();
|
|
efSharp = efCos(efSweep);
|
|
|
|
// Given cos(x + n * 180) = +/- cos(x) for integer n. We
|
|
// know that the arc is 90 degrees or less, so efSharp is
|
|
// non-negative. This lets us effectively use input angles
|
|
// modulo 360 degrees:
|
|
|
|
efSharp.vAbs();
|
|
}
|
|
|
|
// At this point we've figured out the control points and sharpness
|
|
// of the conic section defining the arc. Now convert them to Bezier
|
|
// form.
|
|
|
|
{
|
|
EFLOAT efSharpPlusOne = efSharp;
|
|
efSharpPlusOne += FP_1_0;
|
|
|
|
// efAlpha = Tau = 4 * S / (3 * (S + 1)):
|
|
|
|
EFLOAT efAlpha = FP_4DIV3;
|
|
efAlpha *= efSharp;
|
|
efAlpha /= efSharpPlusOne;
|
|
|
|
// efBeta = 1 - Tau:
|
|
|
|
EFLOAT efBeta = FP_1_0;
|
|
efBeta -= efAlpha;
|
|
|
|
// ptefAlphaTimesVecB = (Tau) B:
|
|
|
|
EPOINTFL ptefAlphaTimesVecB = eptefVecB;
|
|
ptefAlphaTimesVecB *= efAlpha;
|
|
|
|
// V0 = A:
|
|
|
|
ptefV0 = eptefVecA;
|
|
|
|
// V1 = (1 - Tau) A + (Tau) B:
|
|
|
|
ptefV1 = eptefVecA;
|
|
ptefV1 *= efBeta;
|
|
ptefV1 += ptefAlphaTimesVecB;
|
|
|
|
// V2 = (1 - Tau) C + (Tau) B:
|
|
|
|
ptefV2 = eptefVecC;
|
|
ptefV2 *= efBeta;
|
|
ptefV2 += ptefAlphaTimesVecB;
|
|
|
|
// V3 = C:
|
|
|
|
ptefV3 = eptefVecC;
|
|
}
|
|
}
|
|
|
|
// When PARTIALARCTYPE_CONTINUE is set, we know that the first control
|
|
// point of the Bezier is the same as the last point added to the path,
|
|
// so we don't have to add it again.
|
|
|
|
if (paType != PARTIALARCTYPE_CONTINUE)
|
|
{
|
|
POINTL ptl = ebox.ptlXform(ptefV0);
|
|
switch (paType)
|
|
{
|
|
case PARTIALARCTYPE_MOVETO:
|
|
if (!epo.bMoveTo((PEXFORMOBJ) NULL, &ptl))
|
|
return(FALSE);
|
|
break;
|
|
case PARTIALARCTYPE_LINETO:
|
|
if (!epo.bPolyLineTo((PEXFORMOBJ) NULL, &ptl, 1))
|
|
return(FALSE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
POINTL aptl[3];
|
|
|
|
// Now transform to the bound box:
|
|
|
|
aptl[0] = ebox.ptlXform(ptefV1);
|
|
aptl[1] = ebox.ptlXform(ptefV2);
|
|
aptl[2] = ebox.ptlXform(ptefV3);
|
|
|
|
return(epo.bPolyBezierTo((PEXFORMOBJ) NULL, aptl, 3));
|
|
}
|
|
|
|
/******************************Public*Routine******************************\
|
|
* VOID vGetAxis(lQuadrant, eptef)
|
|
*
|
|
* History:
|
|
* 22-Aug-1991 -by- J. Andrew Goossen [andrewgo]
|
|
* Wrote it.
|
|
\**************************************************************************/
|
|
|
|
inline VOID vGetAxis
|
|
(
|
|
LONG lQuadrant,
|
|
EPOINTFL& eptef
|
|
)
|
|
{
|
|
eptef.x = gaefAxisCoord[(lQuadrant + 1) & 3];
|
|
eptef.y = gaefAxisCoord[lQuadrant];
|
|
}
|
|
|
|
/******************************Public*Routine******************************\
|
|
* BOOL bPartialArc(paType, epo, ebox,
|
|
* eptefStart, lStartQuadrant, efStartAngle,
|
|
* eptefEnd, lEndQuadrant, efEndAngle,
|
|
* lQuadrants)
|
|
*
|
|
* Constructs a partial arc. Optionally draws a line to the first point.
|
|
* The arc is drawn counter-clockwise. efStartAngle and efEndAngle are
|
|
* interpretted modulo 360. If the start and end are coincident, a
|
|
* complete ellipse is drawn if lQuadrants != 0.
|
|
*
|
|
* It works by breaking the arc into curves of 90 degrees or less, and
|
|
* then approximating these using Beziers.
|
|
*
|
|
* Restrictions: Only draws counter-clockwise, up to one revolution.
|
|
*
|
|
* History:
|
|
* 27-Nov-1990 -by- J. Andrew Goossen [andrewgo]
|
|
* Wrote it.
|
|
\**************************************************************************/
|
|
|
|
BOOL bPartialArc
|
|
(
|
|
PARTIALARC paType,
|
|
EPATHOBJ& epo,
|
|
EBOX& ebox,
|
|
EPOINTFL& eptefStart,
|
|
LONG lStartQuadrant,
|
|
EFLOAT& efStartAngle,
|
|
EPOINTFL& eptefEnd,
|
|
LONG lEndQuadrant,
|
|
EFLOAT& efEndAngle,
|
|
LONG lQuadrants
|
|
)
|
|
{
|
|
BOOL bSuccess;
|
|
|
|
// If arc is less than 90 degrees, we can make the call straight to
|
|
// bPartialQuadrantArc:
|
|
|
|
if (lQuadrants == 0)
|
|
{
|
|
bSuccess = bPartialQuadrantArc(paType,
|
|
epo,
|
|
ebox,
|
|
eptefStart,
|
|
efStartAngle,
|
|
eptefEnd,
|
|
efEndAngle);
|
|
}
|
|
else
|
|
{
|
|
|
|
// Increment lStartQuadrant so that it's actually the quadrant
|
|
// of the first possible 90 degree arc:
|
|
|
|
lStartQuadrant = (lStartQuadrant + 1) & 3;
|
|
|
|
// The arc is more than 90 degrees, so we have to break it
|
|
// up into chunks that are 90 degrees or smaller. We break it
|
|
// up by quadrant.
|
|
|
|
EAPOINTL aeptl[3]; // Buffer for quadrant arcs
|
|
EPOINTFL eptefAxis;
|
|
|
|
vGetAxis(lStartQuadrant, eptefAxis);
|
|
bSuccess = bPartialQuadrantArc(paType,
|
|
epo,
|
|
ebox,
|
|
eptefStart,
|
|
efStartAngle,
|
|
eptefAxis,
|
|
gaefAxisAngle[lStartQuadrant]);
|
|
|
|
if (lStartQuadrant != lEndQuadrant)
|
|
{
|
|
|
|
// Compute vectors for constructing arcs of exactly 90 degrees:
|
|
|
|
EPOINTL eptlC;
|
|
EPOINTL eptlD;
|
|
LONGLONG eq; // Temporary variable
|
|
|
|
// Compute the placement of the inner control points for the
|
|
// Bezier curve:
|
|
|
|
vEllipseControlsIn((VECTORFX*) &ebox.eptlA, (VECTORFX*) &eptlC, &eq);
|
|
vEllipseControlsIn((VECTORFX*) &ebox.eptlB, (VECTORFX*) &eptlD, &eq);
|
|
|
|
LONG ll = lStartQuadrant;
|
|
do
|
|
{
|
|
switch (ll)
|
|
{
|
|
case 0:
|
|
aeptl[0] = ebox.aeptl[0]; aeptl[0] -= eptlD;
|
|
aeptl[1] = ebox.aeptl[0]; aeptl[1] -= eptlC;
|
|
aeptl[2] = ebox.aeptl[0]; aeptl[2] -= ebox.eptlA;
|
|
break;
|
|
|
|
case 1:
|
|
aeptl[0] = ebox.aeptl[1]; aeptl[0] += eptlC;
|
|
aeptl[1] = ebox.aeptl[1]; aeptl[1] -= eptlD;
|
|
aeptl[2] = ebox.aeptl[1]; aeptl[2] -= ebox.eptlB;
|
|
break;
|
|
|
|
case 2:
|
|
aeptl[0] = ebox.aeptl[2]; aeptl[0] += eptlD;
|
|
aeptl[1] = ebox.aeptl[2]; aeptl[1] += eptlC;
|
|
aeptl[2] = ebox.aeptl[2]; aeptl[2] += ebox.eptlA;
|
|
break;
|
|
|
|
case 3:
|
|
aeptl[0] = ebox.aeptl[3]; aeptl[0] -= eptlC;
|
|
aeptl[1] = ebox.aeptl[3]; aeptl[1] += eptlD;
|
|
aeptl[2] = ebox.aeptl[3]; aeptl[2] += ebox.eptlB;
|
|
break;
|
|
}
|
|
|
|
bSuccess &= epo.bPolyBezierTo((PEXFORMOBJ) NULL, aeptl, 3);
|
|
|
|
ll = (ll + 1) & 3;
|
|
|
|
} while (ll != lEndQuadrant);
|
|
}
|
|
|
|
vGetAxis(lEndQuadrant, eptefAxis);
|
|
bSuccess &= bPartialQuadrantArc(PARTIALARCTYPE_CONTINUE,
|
|
epo,
|
|
ebox,
|
|
eptefAxis,
|
|
gaefAxisAngle[lEndQuadrant],
|
|
eptefEnd,
|
|
efEndAngle);
|
|
}
|
|
return(bSuccess);
|
|
}
|
|
|
|
|
|
/******************************Public*Routine******************************\
|
|
* BOOL NtGdiArcInternal (arctype,x1,y1,x2,y2,x3,y3,x4,y4)
|
|
*
|
|
* Draws an arc figure. Used by the 'Arc', 'Chord' and 'Pie' APIs.
|
|
* Adds to the active path associated with the DC if there is one;
|
|
* otherwise, adds to a temporary one and bStroke's or bStrokeAndFill's
|
|
* it.
|
|
*
|
|
* History:
|
|
* 27-Nov-1990 -by- J. Andrew Goossen [andrewgo]
|
|
* Wrote it.
|
|
\**************************************************************************/
|
|
|
|
BOOL
|
|
APIENTRY
|
|
NtGdiArcInternal(
|
|
ARCTYPE arctype, // Arc, Pie, Chord, or ArcTo
|
|
HDC hdc,
|
|
int x1,
|
|
int y1,
|
|
int x2,
|
|
int y2,
|
|
int x3,
|
|
int y3,
|
|
int x4,
|
|
int y4
|
|
)
|
|
{
|
|
ERECTL ercl(x1, y1, x2, y2);
|
|
EPOINTL ptl1(x3, y3);
|
|
EPOINTL ptl2(x4, y4);
|
|
|
|
DCOBJ dco(hdc);
|
|
|
|
if (!dco.bValid())
|
|
{
|
|
SAVE_ERROR_CODE(ERROR_INVALID_HANDLE);
|
|
return(FALSE);
|
|
}
|
|
|
|
// Watch out: enums are signed, so we need to check "both ends":
|
|
|
|
if ((arctype < 0) || (arctype >= ARCTYPE_MAX))
|
|
{
|
|
RIP("bCurve: Unknown curve type.");
|
|
SAVE_ERROR_CODE(ERROR_INVALID_PARAMETER);
|
|
return(FALSE);
|
|
}
|
|
|
|
SYNC_DRAWING_ATTRS(dco.pdc);
|
|
|
|
// Get the current path or a temporary path. If we're doing an ArcTo,
|
|
// notify that we will update the current point:
|
|
|
|
PATHSTACKOBJ pso(dco, arctype == ARCTYPE_ARCTO);
|
|
if (!pso.bValid())
|
|
{
|
|
SAVE_ERROR_CODE(ERROR_NOT_ENOUGH_MEMORY);
|
|
return(FALSE);
|
|
}
|
|
|
|
// Need World-to-Device transform for widening the line:
|
|
|
|
EXFORMOBJ exo(dco, WORLD_TO_DEVICE);
|
|
LINEATTRS *pla = dco.plaRealize(exo);
|
|
|
|
// Handle the PS_INSIDEFRAME pen attribute and lower-right exclusion
|
|
// by adjusting the box now. At the same time, get the transform
|
|
// type and order the rectangle. Plus, pass TRUE to indicate that
|
|
// the bound box should be adjusted for nice output with NULL pens:
|
|
|
|
EBOX ebox(dco, ercl, pla, TRUE);
|
|
|
|
// Like Win3, we don't handle the case when the pen is bigger than either
|
|
// dimension of the bound-box:
|
|
|
|
if (ebox.bFillInsideFrame())
|
|
return(FALSE);
|
|
|
|
// We exit early if the the bound box is less than a pixel in some
|
|
// dimension and we're doing lower-right exclusion. Since we have
|
|
// drawn what the app asked for, we return success.
|
|
|
|
if (ebox.bEmpty())
|
|
{
|
|
return(TRUE);
|
|
}
|
|
|
|
EPOINTFL eptefOrg(efMid(ebox.rclWorld.left, ebox.rclWorld.right),
|
|
efMid(ebox.rclWorld.top, ebox.rclWorld.bottom));
|
|
|
|
EFLOAT efStartAngle;
|
|
EFLOAT efEndAngle;
|
|
LONG lStartQuad;
|
|
LONG lEndQuad;
|
|
|
|
// Make sure we don't divide by zero:
|
|
|
|
if (ebox.rclWorld.left == ebox.rclWorld.right ||
|
|
ebox.rclWorld.top == ebox.rclWorld.bottom)
|
|
{
|
|
efEndAngle = efStartAngle = FP_0_0;
|
|
lStartQuad = lEndQuad = 0;
|
|
}
|
|
else
|
|
{
|
|
EFLOAT efdx = efHalfDiff(ebox.rclWorld.right, ebox.rclWorld.left);
|
|
EFLOAT efdy = efHalfDiff(ebox.rclWorld.top, ebox.rclWorld.bottom);
|
|
|
|
ASSERTGDI(!efdx.bIsZero() && !efdy.bIsZero(), "bArc: scaling is zero\n");
|
|
|
|
// Compute the start and end angles of the ellipse when its
|
|
// more convenient, namely when we have the points in world
|
|
// space.
|
|
//
|
|
// The ellipse must be scaled back to the unit circle before
|
|
// the angle is computed. The signs of 'efdx' and 'efdy'
|
|
// determine the correct orientation:
|
|
|
|
EPOINTFL ptefNormalized;
|
|
|
|
ptefNormalized = ptl1;
|
|
ptefNormalized -= eptefOrg;
|
|
ptefNormalized.x /= efdx;
|
|
ptefNormalized.y /= efdy;
|
|
|
|
vArctan(ptefNormalized.x, ptefNormalized.y, efStartAngle, lStartQuad);
|
|
|
|
ptefNormalized = ptl2;
|
|
ptefNormalized -= eptefOrg;
|
|
ptefNormalized.x /= efdx;
|
|
ptefNormalized.y /= efdy;
|
|
|
|
vArctan(ptefNormalized.x, ptefNormalized.y, efEndAngle, lEndQuad);
|
|
|
|
// If efEndAngle == efStartAngle, we'll draw a complete ellipse.
|
|
// Note that we can't just call 'bEllipse' for this case because
|
|
// drawing must start and end at the specified angles (this
|
|
// matters for styled lines).
|
|
|
|
}
|
|
|
|
EPOINTFL eptefStart;
|
|
EPOINTFL eptefEnd;
|
|
|
|
EFLOAT efAngleSwept;
|
|
BOOL bAngleSweptIsZero;
|
|
|
|
// If the difference between efEndAngle and efStartAngle is less than about 3 degrees,
|
|
// then the error in computation of eptefStart and eptefEnd using vCosSin will be enough
|
|
// so that the computation of the Bezier points in bPartialArc (which calls bPartialQuadrantArc)
|
|
// will be noticeably wrong.
|
|
|
|
// if efEndAngle == efStartAngle, then we are drawing an entire ellipse, and so
|
|
// it is safe to use vCosSin.
|
|
|
|
// determine whether (abs(efEndAngle - efStartAngle) - 3.0 < 0.0)
|
|
efAngleSwept = efEndAngle;
|
|
efAngleSwept -= efStartAngle;
|
|
|
|
if (efAngleSwept.bIsNegative())
|
|
{
|
|
efAngleSwept.vNegate();
|
|
}
|
|
bAngleSweptIsZero = efAngleSwept.bIsZero();
|
|
efAngleSwept -= FP_3_0;
|
|
|
|
if (efAngleSwept.bIsNegative() && !bAngleSweptIsZero)
|
|
{
|
|
vCosSinPrecise(efStartAngle, &eptefStart.x, &eptefStart.y);
|
|
vCosSinPrecise(efEndAngle, &eptefEnd.x, &eptefEnd.y);
|
|
}
|
|
else
|
|
{
|
|
vCosSin(efStartAngle, &eptefStart.x, &eptefStart.y);
|
|
vCosSin(efEndAngle, &eptefEnd.x, &eptefEnd.y);
|
|
}
|
|
|
|
if (!bPartialArc((arctype == ARCTYPE_ARCTO) ?
|
|
PARTIALARCTYPE_LINETO :
|
|
PARTIALARCTYPE_MOVETO,
|
|
pso,
|
|
ebox,
|
|
eptefStart,
|
|
lStartQuad,
|
|
efStartAngle,
|
|
eptefEnd,
|
|
lEndQuad,
|
|
efEndAngle,
|
|
((lStartQuad == lEndQuad) &&
|
|
(efEndAngle > efStartAngle)) ? 0 : 1))
|
|
return(FALSE);
|
|
|
|
switch(arctype)
|
|
{
|
|
case ARCTYPE_ARC:
|
|
break;
|
|
|
|
case ARCTYPE_ARCTO:
|
|
|
|
// Set the DC's current position in device space. It would be too much
|
|
// work to calculate the world space current position, so simply mark it
|
|
// as invalid:
|
|
|
|
dco.pdc->vInvalidatePtlCurrent();
|
|
dco.pdc->vValidatePtfxCurrent();
|
|
dco.ptfxCurrent() = pso.ptfxGetCurrent();
|
|
|
|
break;
|
|
case ARCTYPE_CHORD:
|
|
|
|
// Draw a line from the end of the curve to the start to create
|
|
// the 'Chord':
|
|
|
|
if (!pso.bCloseFigure())
|
|
return(FALSE);
|
|
|
|
break;
|
|
|
|
case ARCTYPE_PIE:
|
|
{
|
|
|
|
// Draw a line from the end of the curve to the center of the
|
|
// figure to the start of the curve to create the 'Pie':
|
|
|
|
if (!pso.bPolyLineTo((PEXFORMOBJ) NULL, &ebox.eptlOrigin, 1) ||
|
|
!pso.bCloseFigure())
|
|
return(FALSE);
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Return if we're accumulating a path:
|
|
|
|
if (dco.pdc->bActive())
|
|
return(TRUE);
|
|
|
|
// Stroke or StrokeAndFill depending on the curve type:
|
|
|
|
BOOL bSuccess;
|
|
switch(arctype)
|
|
{
|
|
case ARCTYPE_ARCTO:
|
|
case ARCTYPE_ARC:
|
|
bSuccess = pso.bStroke(dco, dco.plaRealize(exo), &exo);
|
|
break;
|
|
case ARCTYPE_CHORD:
|
|
case ARCTYPE_PIE:
|
|
bSuccess = pso.bStrokeAndFill(dco, dco.plaRealize(exo), &exo);
|
|
break;
|
|
}
|
|
|
|
return(bSuccess);
|
|
}
|
|
|
|
/******************************Public*Routine******************************\
|
|
* BOOL bEllipse(epo, ebox)
|
|
*
|
|
* Adds an ellipse to the path. Used by 'Ellipse' and 'CreateEllipticalRgn'
|
|
* APIs.
|
|
*
|
|
* 4 Beziers are used, one for each quadrant of the ellipse. Drawing
|
|
* starts at the positive x-axis, and proceeds in a counter-clockwise
|
|
* direction.
|
|
*
|
|
* History:
|
|
* 27-Nov-1990 -by- J. Andrew Goossen [andrewgo]
|
|
* Wrote it.
|
|
\**************************************************************************/
|
|
|
|
BOOL bEllipse
|
|
(
|
|
EPATHOBJ& epo,
|
|
EBOX& ebox // Bounding box
|
|
)
|
|
{
|
|
ASSERTGDI(epo.bValid(), "bEllipse: Bad object parm\n");
|
|
|
|
// 'eptlC' and 'eptlD' are vectors from the corners of the bounding
|
|
// box that are where the inner control points of the Beziers are
|
|
// placed:
|
|
|
|
EPOINTL eptlC;
|
|
EPOINTL eptlD;
|
|
|
|
LONGLONG eqTmp;
|
|
|
|
vEllipseControlsIn((VECTORFX*) &ebox.eptlA, (VECTORFX*) &eptlC, &eqTmp);
|
|
vEllipseControlsIn((VECTORFX*) &ebox.eptlB, (VECTORFX*) &eptlD, &eqTmp);
|
|
|
|
BOOL bSuccess;
|
|
{
|
|
// Start drawing at the 'x-axis':
|
|
|
|
EPOINTL eptlStart = ebox.aeptl[3];
|
|
eptlStart += ebox.eptlB;
|
|
bSuccess = epo.bMoveTo((PEXFORMOBJ) NULL, &eptlStart);
|
|
}
|
|
|
|
// Use one 96 byte buffer so that we only have to call bPolyBezierTo
|
|
// once. I would declare this as an array of EPOINTLs, but the
|
|
// compiler doesn't like the static constructors:
|
|
|
|
EAPOINTL aeptl[12];
|
|
|
|
// First Quadrant
|
|
|
|
aeptl[0] = ebox.aeptl[0]; aeptl[0] -= eptlD;
|
|
aeptl[1] = ebox.aeptl[0]; aeptl[1] -= eptlC;
|
|
aeptl[2] = ebox.aeptl[0]; aeptl[2] -= ebox.eptlA;
|
|
|
|
// Second Quadrant:
|
|
|
|
aeptl[3] = ebox.aeptl[1]; aeptl[3] += eptlC;
|
|
aeptl[4] = ebox.aeptl[1]; aeptl[4] -= eptlD;
|
|
aeptl[5] = ebox.aeptl[1]; aeptl[5] -= ebox.eptlB;
|
|
|
|
// Third Quadrant:
|
|
|
|
aeptl[6] = ebox.aeptl[2]; aeptl[6] += eptlD;
|
|
aeptl[7] = ebox.aeptl[2]; aeptl[7] += eptlC;
|
|
aeptl[8] = ebox.aeptl[2]; aeptl[8] += ebox.eptlA;
|
|
|
|
// Fourth Quadrant:
|
|
|
|
aeptl[9] = ebox.aeptl[3]; aeptl[9] -= eptlC;
|
|
aeptl[10] = ebox.aeptl[3]; aeptl[10] += eptlD;
|
|
aeptl[11] = ebox.aeptl[3]; aeptl[11] += ebox.eptlB;
|
|
|
|
return(epo.bPolyBezierTo((PEXFORMOBJ) NULL, aeptl, 12) &&
|
|
epo.bCloseFigure());
|
|
}
|
|
|
|
|
|
/******************************Public*Routine******************************\
|
|
* BOOL bRoundRect(epo, exoWorldToDevice, ebox, x, y)
|
|
*
|
|
* Adds a rounded rectangle to the path. Used by 'RoundRect' and
|
|
* 'CreateRoundRectRgn' APIs.
|
|
*
|
|
* 4 Beziers and 4 lines are used. Drawing starts at the first curve
|
|
* in the "upper right" corner of the rounded rectangle, and proceeds
|
|
* in a "counter-clockwise" direction.
|
|
*
|
|
* This routine constructs the RoundRect by taking advantage of the fact
|
|
* that all the control points defining the lines and the Beziers lie on
|
|
* the bounding box.
|
|
*
|
|
* History:
|
|
* 27-Nov-1990 -by- J. Andrew Goossen [andrewgo]
|
|
* Wrote it.
|
|
\**************************************************************************/
|
|
|
|
BOOL bRoundRect
|
|
(
|
|
EPATHOBJ& epo,
|
|
EBOX& ebox, // Bound box of rectangle
|
|
LONG x, // Width of ellipse in World coordinates
|
|
LONG y // Height of ellipse in World coordinates
|
|
)
|
|
{
|
|
ASSERTGDI(epo.bValid(), "bRoundRect: Bad object parm\n");
|
|
|
|
EFLOAT efdx = efHalfDiff(ebox.rclWorld.left, ebox.rclWorld.right);
|
|
EFLOAT efdy = efHalfDiff(ebox.rclWorld.top, ebox.rclWorld.bottom);
|
|
|
|
EFLOATEXT efFractionA;
|
|
EFLOATEXT efFractionB;
|
|
|
|
if (efdx.bIsZero() || efdy.bIsZero())
|
|
{
|
|
efFractionA = FP_0_0;
|
|
efFractionB = FP_0_0;
|
|
}
|
|
else
|
|
{
|
|
// Old Windows takes the absolute values of the ellipse dimensions
|
|
// used for drawing the corners:
|
|
|
|
x = ABS(x);
|
|
y = ABS(y);
|
|
|
|
// Determine what fraction of the bound box that the ellipse
|
|
// comprises:
|
|
|
|
efdx.vAbs();
|
|
efdy.vAbs();
|
|
efFractionA = x;
|
|
efFractionB = y;
|
|
efFractionA /= efdx;
|
|
efFractionB /= efdy;
|
|
|
|
}
|
|
|
|
//
|
|
// If the ellipse given by the user has larger dimensions than the
|
|
// bound box, shrink those dimensions so that they are the same as
|
|
// the bound box:
|
|
//
|
|
|
|
if (efFractionA > FP_2_0)
|
|
efFractionA = FP_1_0;
|
|
else
|
|
efFractionA.vDivBy2();
|
|
|
|
if (efFractionB > FP_2_0)
|
|
efFractionB = FP_1_0;
|
|
else
|
|
efFractionB.vDivBy2();
|
|
|
|
//DbgPrint("FracA: %li FracB: %li\n", lConv(efdx), lConv(efdy));
|
|
|
|
//
|
|
// 'eptlX' and 'eptlY' are the vectors from the vertices of the
|
|
// bounding box defining the start and end-points of the Beziers
|
|
// used for the rounded corners. 'eptlXprime' and 'eptlYprime'
|
|
// define the inner two control points (all the Beziers' control
|
|
// points lie on the bounding box):
|
|
//
|
|
// -------------------> ebox.eptlA
|
|
//
|
|
// : :
|
|
// | |
|
|
// ^ |\ /|
|
|
// | | \ / |
|
|
// | ^ | \ / |
|
|
// eptlY | eptlYprime | 2--------------------------------------3
|
|
//
|
|
// --> eptlXprime
|
|
//
|
|
// ------> eptlX
|
|
//
|
|
// eptlX = efFractionA * ebox.eptlA
|
|
// eptlY = efFractionB * ebox.eptlB
|
|
//
|
|
|
|
EPOINTL eptlX;
|
|
EPOINTL eptlY;
|
|
EPOINTL eptlXprime;
|
|
EPOINTL eptlYprime;
|
|
|
|
{
|
|
LONGLONG eqTmp;
|
|
EPOINTFL eptefX;
|
|
EPOINTFL eptefY;
|
|
eptefX = ebox.eptlA;
|
|
eptefY = ebox.eptlB;
|
|
|
|
eptefX *= efFractionA;
|
|
eptefY *= efFractionB;
|
|
|
|
// This conversion should not fail:
|
|
|
|
eptefX.bToPOINTL(eptlX);
|
|
eptefY.bToPOINTL(eptlY);
|
|
|
|
// We now know where to put the end-points of the bezier curves. Now
|
|
// compute the inner control points:
|
|
|
|
vEllipseControlsIn((VECTORFX*) &eptlX, (VECTORFX*) &eptlXprime, &eqTmp);
|
|
vEllipseControlsIn((VECTORFX*) &eptlY, (VECTORFX*) &eptlYprime, &eqTmp);
|
|
}
|
|
|
|
EAPOINTL aeptl[3];
|
|
EPOINTL eptl;
|
|
BOOL bFailure = FALSE; // Fail by default
|
|
|
|
eptl = ebox.aeptl[0];
|
|
eptl -= eptlY;
|
|
if (!epo.bMoveTo((PEXFORMOBJ) NULL, &eptl))
|
|
return(bFailure);
|
|
|
|
aeptl[0] = ebox.aeptl[0]; aeptl[0] -= eptlYprime;
|
|
aeptl[1] = ebox.aeptl[0]; aeptl[1] -= eptlXprime;
|
|
aeptl[2] = ebox.aeptl[0]; aeptl[2] -= eptlX;
|
|
|
|
if (!epo.bPolyBezierTo((PEXFORMOBJ) NULL, aeptl, 3))
|
|
return(bFailure);
|
|
|
|
// Quadrant two:
|
|
|
|
eptl = ebox.aeptl[1];
|
|
eptl += eptlX;
|
|
if (!epo.bPolyLineTo((PEXFORMOBJ) NULL, &eptl, 1))
|
|
return(bFailure);
|
|
|
|
aeptl[0] = ebox.aeptl[1]; aeptl[0] += eptlXprime;
|
|
aeptl[1] = ebox.aeptl[1]; aeptl[1] -= eptlYprime;
|
|
aeptl[2] = ebox.aeptl[1]; aeptl[2] -= eptlY;
|
|
|
|
if (!epo.bPolyBezierTo((PEXFORMOBJ) NULL, aeptl, 3))
|
|
return(bFailure);
|
|
|
|
// Quadrant three:
|
|
|
|
eptl = ebox.aeptl[2];
|
|
eptl += eptlY;
|
|
if (!epo.bPolyLineTo((PEXFORMOBJ) NULL, &eptl, 1))
|
|
return(bFailure);
|
|
|
|
aeptl[0] = ebox.aeptl[2]; aeptl[0] += eptlYprime;
|
|
aeptl[1] = ebox.aeptl[2]; aeptl[1] += eptlXprime;
|
|
aeptl[2] = ebox.aeptl[2]; aeptl[2] += eptlX;
|
|
|
|
if (!epo.bPolyBezierTo((PEXFORMOBJ) NULL, aeptl, 3))
|
|
return(bFailure);
|
|
|
|
// Quadrant four:
|
|
|
|
eptl = ebox.aeptl[3];
|
|
eptl -= eptlX;
|
|
if (!epo.bPolyLineTo((PEXFORMOBJ) NULL, &eptl, 1))
|
|
return(bFailure);
|
|
|
|
aeptl[0] = ebox.aeptl[3]; aeptl[0] -= eptlXprime;
|
|
aeptl[1] = ebox.aeptl[3]; aeptl[1] += eptlYprime;
|
|
aeptl[2] = ebox.aeptl[3]; aeptl[2] += eptlY;
|
|
|
|
if (!epo.bPolyBezierTo((PEXFORMOBJ) NULL, aeptl, 3))
|
|
return(bFailure);
|
|
|
|
// Done:
|
|
|
|
return(epo.bCloseFigure());
|
|
}
|
|
|
|
|
|
/******************************Public*Routine******************************\
|
|
* BOOL bPolyPolygon(epo, exo, pptl, pcptl, ccptl, cMaxPoints)
|
|
*
|
|
* Adds a PolyPolygon to the path. Used by CreatePolyPolygonRgn and
|
|
* PolyPolygon APIs. Returns FALSE if fails, and sets last error code.
|
|
*
|
|
* History:
|
|
* 27-Nov-1990 -by- J. Andrew Goossen [andrewgo]
|
|
* Wrote it.
|
|
\**************************************************************************/
|
|
|
|
BOOL bPolyPolygon
|
|
(
|
|
EPATHOBJ& epo,
|
|
EXFORMOBJ& exo,
|
|
PPOINTL pptl,
|
|
LONG* pcptl,
|
|
ULONG ccptl,
|
|
LONG cMaxPoints
|
|
)
|
|
{
|
|
ASSERTGDI(epo.bValid(), "bPolyPolygon: Bad epo\n");
|
|
ASSERTGDI(exo.bValid(), "bPolyPolygon: Bad exo\n");
|
|
|
|
if (ccptl == 0)
|
|
return(TRUE);
|
|
|
|
LONG cPts;
|
|
LONG* pcptlEnd = pcptl + ccptl;
|
|
|
|
// Now add to the path:
|
|
|
|
do {
|
|
|
|
// We have to be careful to make a local copy of this polygon's point
|
|
// count (by copying to to cPts) to get the value out of the shared
|
|
// client/server memory window, where the app could trash the value at
|
|
// any time:
|
|
|
|
cPts = *pcptl;
|
|
cMaxPoints -= cPts;
|
|
|
|
// Check parameters. Each polygon must have at least 2 points.
|
|
|
|
if (cMaxPoints < 0 || cPts < 2)
|
|
{
|
|
SAVE_ERROR_CODE(ERROR_INVALID_PARAMETER);
|
|
return(FALSE);
|
|
}
|
|
|
|
if (!epo.bMoveTo(&exo, pptl) ||
|
|
!epo.bPolyLineTo(&exo, pptl + 1, cPts - 1) ||
|
|
!epo.bCloseFigure())
|
|
return(FALSE);
|
|
|
|
pptl += cPts;
|
|
pcptl++;
|
|
|
|
} while (pcptl < pcptlEnd);
|
|
|
|
return(TRUE);
|
|
}
|