|
|
/******************************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); }
|