/************************************************************************** * * Copyright (c) 2000 Microsoft Corporation * * Module Name: * * End Cap Creator. * * Abstract: * * This module defines a class called GpEndCapCreator. This class is * responsible for constructing a path containing all the custom endcaps * and anchor endcaps for a given path. These are correctly transformed * and positioned. * * This class is used to create and position all the endcaps for a * given path and pen. This class is also responsible for trimming * the original path down so that it fits the end caps properly. * This class will handle all types of end caps except the base endcaps * (round, flat and triangle) which may be used as dash caps. * Caps that are handled are CustomCaps and the 3 Anchor caps (round, * diamond and arrow). Note that the round anchor cap is distinct from * the round base cap. * * Created: * * 10/09/2000 asecchia * Created it. * **************************************************************************/ #include "precomp.hpp" //------------------------------------------------------------- // GetMajorAndMinorAxis() is defined in PathWidener.cpp. //------------------------------------------------------------- extern GpStatus GetMajorAndMinorAxis( REAL* majorR, REAL* minorR, const GpMatrix* matrix ); GpEndCapCreator::GpEndCapCreator( GpPath *path, DpPen *pen, const GpMatrix *m, REAL dpi_x, REAL dpi_y, bool antialias ) { Path = path; Pen = pen; if(m) {XForm = *m;} XForm.Prepend(pen->Xform); DpiX = dpi_x; DpiY = dpi_y; Antialias = antialias; StartCap = NULL; EndCap = NULL; switch(Pen->StartCap) { case LineCapCustom: StartCap = static_cast(Pen->CustomStartCap); break; case LineCapArrowAnchor: StartCap = GpEndCapCreator::ReferenceArrowAnchor(); break; case LineCapDiamondAnchor: StartCap = GpEndCapCreator::ReferenceDiamondAnchor(); break; case LineCapRoundAnchor: StartCap = GpEndCapCreator::ReferenceRoundAnchor(); break; case LineCapSquareAnchor: StartCap = GpEndCapCreator::ReferenceSquareAnchor(); break; // The non-anchor caps are handled by the widener. }; switch(Pen->EndCap) { case LineCapCustom: EndCap = static_cast(Pen->CustomEndCap); break; case LineCapArrowAnchor: EndCap = GpEndCapCreator::ReferenceArrowAnchor(); break; case LineCapDiamondAnchor: EndCap = GpEndCapCreator::ReferenceDiamondAnchor(); break; case LineCapRoundAnchor: EndCap = GpEndCapCreator::ReferenceRoundAnchor(); break; case LineCapSquareAnchor: EndCap = GpEndCapCreator::ReferenceSquareAnchor(); break; // The non-anchor caps are handled by the widener. }; // If we're flipped in the X or Y direction (but not both), // reverse the fill and stroke paths so that the winding // mode will be correct. if (pen->Xform.GetDeterminant() < 0) { if (StartCap) { StartCap->ReverseFillPath(); StartCap->ReverseStrokePath(); } if (EndCap) { EndCap->ReverseFillPath(); EndCap->ReverseStrokePath(); } } } /**************************************************************************\ * * Function Description: * * This function will return true if the GpEndCapCreator is required for * the given pen. If the pen only has simple endcaps, then the * GpEndCapCreator can skipped. * * The GpEndCapCreator is used for creating Anchor and/or Custom caps. * LineCap- Flat, Round, Square and Triangle are handled directly by the * widener. * * Revision History: * * 11/10/2000 asecchia * Created it * \**************************************************************************/ bool GpEndCapCreator::PenNeedsEndCapCreator(const DpPen *pen) { return ( (pen->StartCap == LineCapCustom) || (pen->EndCap == LineCapCustom) || ((pen->StartCap & LineCapAnchorMask) != 0) || ((pen->EndCap & LineCapAnchorMask) != 0) ); } GpEndCapCreator::~GpEndCapCreator() { // If we allocated memory for temporary custom caps, then // throw that memory away. if(Pen->StartCap != LineCapCustom) { delete StartCap; StartCap = NULL; } if(Pen->EndCap != LineCapCustom) { delete EndCap; EndCap = NULL; } } /**************************************************************************\ * * Function Description: * * Creates a reference GpCustomLineCap representing an ArrowAnchor. * This is an equilateral triangle with edge equal to 2. This means * that the scaling will create a 2xStrokeWidth cap edge length. * * Revision History: * * 10/08/2000 asecchia * Created it * \**************************************************************************/ GpCustomLineCap *GpEndCapCreator::ReferenceArrowAnchor() { // the square root of 3 const REAL root3 = 1.732050808f; // Anti-clockwise definition of an equilateral triangle of side length 2.0f // with a vertex on the origin and axis extending along the negative // y axis. const GpPointF points[3] = { GpPointF(0.0f, 0.0f), GpPointF(-1.0f, -root3), GpPointF(1.0f, -root3) }; GpPath arrowAnchor(FillModeWinding); arrowAnchor.AddPolygon(points, 3); // Create the custom line cap. If it fails it will return NULL. GpCustomLineCap *cap = new GpCustomLineCap(&arrowAnchor, NULL); if(cap) { cap->SetBaseInset(1.0f); } return cap; } /**************************************************************************\ * * Function Description: * * Creates a reference GpCustomLineCap representing a DiamondAnchor. * This is a square centered on the end point of the path with it's * diagonal along the axis of the spine. * * Revision History: * * 10/08/2000 asecchia * Created it * \**************************************************************************/ GpCustomLineCap *GpEndCapCreator::ReferenceDiamondAnchor() { // Anti-clockwise definition of a square of diagonal size 2.0f // with the center on the origin and axis extending along the negative // y axis. const GpPointF points[4] = { GpPointF(0.0f, 1.0f), GpPointF(-1.0f, 0.0f), GpPointF(0.0f, -1.0f), GpPointF(1.0f, 0.0f) }; GpPath diamondAnchor(FillModeWinding); diamondAnchor.AddPolygon(points, 4); // Create the custom line cap. If it fails it will return NULL. GpCustomLineCap *cap = new GpCustomLineCap(&diamondAnchor, NULL); if(cap) { cap->SetBaseInset(0.0f); } return cap; } /**************************************************************************\ * * Function Description: * * Creates a reference GpCustomLineCap representing a SquareAnchor. * This is a square that has a 2 unit long diagonal and is centered on * the end point of the path. * * Revision History: * * 10/17/2000 peterost * Created it * \**************************************************************************/ GpCustomLineCap *GpEndCapCreator::ReferenceSquareAnchor() { const REAL halfRoot2 = 0.7071068f; const GpPointF points[4] = { GpPointF(-halfRoot2, -halfRoot2), GpPointF(halfRoot2, -halfRoot2), GpPointF(halfRoot2, halfRoot2), GpPointF(-halfRoot2, halfRoot2) }; GpPath squareAnchor(FillModeWinding); squareAnchor.AddPolygon(points, 4); // Create the custom line cap. If it fails it will return NULL. GpCustomLineCap *cap = new GpCustomLineCap(&squareAnchor, NULL); if(cap) { cap->SetBaseInset(0.0f); } return cap; } /**************************************************************************\ * * Function Description: * * Creates a reference GpCustomLineCap representing a RoundAnchor. * This is a circle centered on the end point of the path. * * Revision History: * * 10/08/2000 asecchia * Created it * \**************************************************************************/ GpCustomLineCap *GpEndCapCreator::ReferenceRoundAnchor() { // Create the custom line cap. If it fails it will return NULL. GpPath roundAnchor(FillModeWinding); roundAnchor.AddEllipse(-1.0f, -1.0f, 2.0f, 2.0f); GpCustomLineCap *cap = new GpCustomLineCap(&roundAnchor, NULL); if(cap) { cap->SetBaseInset(0.0f); } return cap; } /**************************************************************************\ * * Function Description: * * ComputeCapGradient. * * Compute the correct gradient for a line cap of a given length. * Work out the direction of the cap from the list of input * points in the path and the length of the cap. * Simply put, the direction is the line segment formed by * the end point of the path and the first intersection along the * path with a circle of length "length" and centered at the * first point of the path. * * Arguments: * * GpIterator &pointIterator, * BYTE *types, * IN REAL lengthSquared, length of the cap squared. * IN baseInset, amount to draw into the shape. * OUT GpVector2D *grad, output gradient vector * * * Revision History: * * 08/23/00 asecchia * Created it * \**************************************************************************/ void GpEndCapCreator::ComputeCapGradient( GpIterator &pointIterator, BYTE *types, IN REAL lengthSquared, IN REAL baseInset, OUT GpVector2D *grad ) { // Start at the beginning of the iterator (end of the list of // points if isStartCap is FALSE) GpPointF *endPoint = pointIterator.CurrentItem(); GpPointF *curPoint = endPoint; INT index; bool intersectionFound = false; bool priorDeletion = false; while(!pointIterator.IsDone()) { curPoint = pointIterator.CurrentItem(); if(lengthSquared < distance_squared(*curPoint, *endPoint)) { intersectionFound = true; break; } // Mark this point for deletion by the trimming algorithm. index = pointIterator.CurrentIndex(); // Check to see if anyone already deleted this segment. // PathPointTypeInternalUse is the marked-for-deletion flag. priorDeletion = (types[index] & PathPointTypeInternalUse) == PathPointTypeInternalUse; types[index] |= PathPointTypeInternalUse; pointIterator.Next(); } // Now we have the segment that intersects the base of the arrow. // or the last segment. pointIterator.Prev(); // if we couldn't get the Prev, then we were at the beginning. #if DBG if(pointIterator.IsDone()) { ONCE(WARNING(("not enough points in array"))); } #endif // If the intersection was not found we have marked the entire subpath // for deletion. if(intersectionFound && !priorDeletion) { // We overagressively marked this point for deletion, // instead of deleting this point, we're going to move it. // Note: we may have found an intersection point in a segment // that has already been marked for deletion. Checking priorDeletion // here ensures that we don't incorrectly undelete this point. index = pointIterator.CurrentIndex(); // PathPointTypeInternalUse is the marked-for-deletion flag. types[index] &= ~PathPointTypeInternalUse; } GpPointF *prevPoint = pointIterator.CurrentItem(); GpPointF intersectionPoint; if(!intersect_circle_line( *endPoint, // center lengthSquared, // radius^2 *curPoint, // P0 *prevPoint, // P1 &intersectionPoint )) { // If there is no intersection, then the line segment is likely too // short, so just take the previous point as the intersection. // This is our best guess and in this case will give us the slope from // the start to end point as the cap direction. intersectionPoint.X = prevPoint->X; intersectionPoint.Y = prevPoint->Y; } // Compute the gradient - and normalize the vector. *grad = intersectionPoint - *endPoint; grad->Normalize(); // Update the point in the path directly. GpVector2D v = *endPoint - intersectionPoint; *prevPoint = intersectionPoint + (v*(1.0f-baseInset)); } /**************************************************************************\ * * Function Description: * * This creates a path containing all the custom end caps for all * the open subpaths in the input path. * * Return * * Status * * Arguments: * * [OUT] caps -- this is where we put the caps we generate * * Created: * * 10/05/2000 asecchia * created it. * \**************************************************************************/ GpStatus GpEndCapCreator::CreateCapPath(GpPath **caps) { // Validate our input data. ASSERT(Pen != NULL); ASSERT(Path != NULL); ASSERT(caps != NULL); ASSERT(*caps == NULL); // Create our cap path. *caps = new GpPath(FillModeWinding); if(*caps==NULL) { return OutOfMemory; } // Create a path points iterator because our GpPath doesn't know how // to iterate over its own data *sigh* GpPathPointIterator pathIterator( const_cast(Path->GetPathPoints()), const_cast(Path->GetPathTypes()), Path->GetPointCount() ); GpSubpathIterator subpathIterator(&pathIterator); // Loop through all the available subpaths. while(!subpathIterator.IsDone()) { // Compute the length of the subpath. INT startIndex = subpathIterator.CurrentIndex(); GpPointF *points = subpathIterator.CurrentItem(); BYTE *types = subpathIterator.CurrentType(); subpathIterator.Next(); INT elementCount = subpathIterator.CurrentIndex() - startIndex; // Work out if it's a closed subpath. // Leave the subpath iterator in the same state. pathIterator.Prev(); bool isClosed = ((*(pathIterator.CurrentType()) & PathPointTypeCloseSubpath) == PathPointTypeCloseSubpath); pathIterator.Next(); // only want to add end caps if this is an open subpath. if(!isClosed) { GpPath *startCap = NULL; GpPath *endCap = NULL; // Create the cap using the points and types GetCapsForSubpath( &startCap, &endCap, points, types, elementCount ); // Add the cap to our caps path. (*caps)->AddPath(startCap, FALSE); (*caps)->AddPath(endCap, FALSE); // Clean up the temporary caps for the next iteration. delete startCap; delete endCap; } } return Ok; } /**************************************************************************\ * * Function Description: * * This takes a pen and sets it up to match the internal Pen, but modified * to support stroking the StrokeCap. E.g. the caps are removed to avoid * recursive compound capping etc. * * Arguments: * * [OUT] pen -- this is where we put the pen we generate * [IN] customCap -- input custom cap. * * Created: * * 10/09/2000 asecchia * rewrote it. * \**************************************************************************/ VOID GpEndCapCreator::PrepareDpPenForCustomCap( DpPen* pen, const GpCustomLineCap* customCap ) const { ASSERT(pen); *pen = *Pen; pen->StartCap = LineCapFlat; pen->EndCap = LineCapFlat; pen->Join = LineJoinMiter; pen->MiterLimit = 10; pen->PenAlignment = PenAlignmentCenter; pen->DashStyle = DashStyleSolid; pen->DashCap = LineCapFlat; pen->DashCount = 0; pen->DashOffset = 0; pen->DashArray = NULL; pen->CompoundCount = 0; pen->CompoundArray = NULL; pen->CustomEndCap = NULL; pen->CustomStartCap = NULL; GpLineCap startCap, endCap; GpLineJoin lineJoin; if(customCap) { REAL widthScale; customCap->GetStrokeCaps(&startCap, &endCap); customCap->GetStrokeJoin(&lineJoin); customCap->GetWidthScale(&widthScale); pen->Width *= widthScale; pen->StartCap = startCap; pen->EndCap = endCap; pen->Join = lineJoin; } } GpStatus GpEndCapCreator::SetCustomFillCaps( GpCustomLineCap* customStartCap, GpCustomLineCap* customEndCap, const GpPointF& startPoint, const GpPointF& endPoint, const GpPointF *centerPoints, const BYTE *centerTypes, INT centerPointCount, DynPointFArray *startCapPoints, DynPointFArray *endCapPoints, DynByteArray *startCapTypes, DynByteArray *endCapTypes ) { GpStatus status = Ok; startCapPoints->Reset(FALSE); startCapTypes->Reset(FALSE); endCapPoints->Reset(FALSE); endCapTypes->Reset(FALSE); INT count; GpPointF tangent; GpPointF* points; BYTE* types; REAL width, widthScale; // Get minimum line width based on the transform currently in effect. REAL majorR, minorR, unitScale; GetMajorAndMinorAxis(&majorR, &minorR, &XForm); unitScale = min(majorR, minorR); if(customStartCap) { // Get the start cap and inset of the base start cap. count = customStartCap->GetFillPointCount(); if(count > 0) { points = startCapPoints->AddMultiple(count); types = startCapTypes->AddMultiple(count); if(!points || !types) { startCapPoints->Reset(FALSE); startCapTypes->Reset(FALSE); status = OutOfMemory; } if(status == Ok) { customStartCap->GetWidthScale(&widthScale); width = Pen->Width*widthScale; REAL length = customStartCap->GetFillLength(); // Compute the base inset. Divide by the length to get a // number between 0 and 1. 0=no inset, 1=inset to the full // length of the cap. REAL inset; customStartCap->GetBaseInset(&inset); if(REALABS(length) < REAL_EPSILON) { inset = 0.0f; } else { inset /= length; } length *= max(width, 1.0f/unitScale); // Compute the gradient of the cap. GpArrayIterator pointIterator( const_cast(centerPoints), centerPointCount ); GpVector2D gradient; ComputeCapGradient( pointIterator, const_cast(centerTypes), length*length, inset, &gradient // OUT parameters ); tangent.X = -gradient.X; tangent.Y = -gradient.Y; // Move start point left or right to account for inset // pens, if needed. GpPointF start; start.X = startPoint.X; start.Y = startPoint.Y; customStartCap->GetTransformedFillCap( points, types, count, start, tangent, width, 2.0f / unitScale ); } } } if(status == Ok && customEndCap) { // Get the start cap and inset of the base start cap. count = customEndCap->GetFillPointCount(); if(count > 0) { points = endCapPoints->AddMultiple(count); types = endCapTypes->AddMultiple(count); if(!points || !types) { endCapPoints->Reset(FALSE); endCapTypes->Reset(FALSE); status = OutOfMemory; } if(status == Ok) { customEndCap->GetWidthScale(&widthScale); width = Pen->Width*widthScale; REAL length = customEndCap->GetFillLength(); // Compute the base inset. Divide by the length to get a // number between 0 and 1. 0=no inset, 1=inset to the full // length of the cap. REAL inset; customEndCap->GetBaseInset(&inset); if(REALABS(length) < REAL_EPSILON) { inset = 0.0f; } else { inset /= length; } length *= max(width, 1.0f/unitScale); // Compute the gradient of the cap. GpArrayIterator pointIterator( const_cast(centerPoints), centerPointCount ); GpReverseIterator pointReverse(&pointIterator); pointReverse.SeekFirst(); GpVector2D gradient; ComputeCapGradient( pointReverse, const_cast(centerTypes), length*length, inset, &gradient // OUT parameters ); tangent.X = - gradient.X; tangent.Y = - gradient.Y; // Move end point left or right to account for inset // pens, if needed. GpPointF end; end.X = endPoint.X; end.Y = endPoint.Y; customEndCap->GetTransformedFillCap( points, types, count, end, tangent, width, 2.0f / unitScale ); } } } return status; } GpStatus GpEndCapCreator::SetCustomStrokeCaps( GpCustomLineCap* customStartCap, GpCustomLineCap* customEndCap, const GpPointF& startPoint, const GpPointF& endPoint, const GpPointF *centerPoints, const BYTE *centerTypes, INT centerPointCount, DynPointFArray *startCapPoints, DynPointFArray *endCapPoints, DynByteArray *startCapTypes, DynByteArray *endCapTypes ) { GpStatus status = Ok; GpPointF* points = NULL; BYTE* types = NULL; INT count; GpPointF tangent, start, end; INT startCount = 0; INT endCount = 0; if(customStartCap) { startCount = customStartCap->GetStrokePointCount(); } if(customEndCap) { endCount = customEndCap->GetStrokePointCount(); } INT maxCount = max(startCount, endCount); if(maxCount <= 0) { return Ok; } points = (GpPointF*) GpMalloc(maxCount*sizeof(GpPointF)); types = (BYTE*) GpMalloc(maxCount); if(!points || !types) { GpFree(points); GpFree(types); return OutOfMemory; } DpPen pen; GpPointF* widenedPts; INT widenedCount; REAL widthScale, width; if(customStartCap && startCount > 0) { startCapPoints->Reset(FALSE); startCapTypes->Reset(FALSE); customStartCap->GetWidthScale(&widthScale); width = Pen->Width*widthScale; REAL length = customStartCap->GetStrokeLength(); // Handle the case of a non-closed stroke path // in this case the length is typically zero. if(REALABS(length)GetBaseInset(&inset); inset /= length; length *= width; // Compute the gradient of the cap. GpArrayIterator pointIterator( const_cast(centerPoints), centerPointCount ); GpVector2D gradient; ComputeCapGradient( pointIterator, const_cast(centerTypes), length*length, inset, &gradient // OUT parameters ); tangent.X = -gradient.X; tangent.Y = -gradient.Y; // Move start point left or right to account for inset // pens, if needed. GpPointF start; start.X = startPoint.X; start.Y = startPoint.Y; customStartCap->GetTransformedStrokeCap( maxCount, &points, &types, &startCount, start, tangent, width, width ); PrepareDpPenForCustomCap(&pen, customStartCap); GpPathWidener widener( points, types, startCount, &pen, &XForm, DpiX, // widener doesn't use these. DpiY, Antialias ); widener.Widen(startCapPoints, startCapTypes); } if(customEndCap && endCount > 0) { endCapPoints->Reset(FALSE); endCapTypes->Reset(FALSE); customEndCap->GetWidthScale(&widthScale); width = Pen->Width*widthScale; REAL length = customEndCap->GetStrokeLength(); // Handle the case of a non-closed stroke path // in this case the length is typically zero. if(REALABS(length)GetBaseInset(&inset); inset /= length; length *= width; // Compute the gradient of the cap. GpArrayIterator pointIterator( const_cast(centerPoints), centerPointCount ); GpReverseIterator pointReverse(&pointIterator); pointReverse.SeekFirst(); GpVector2D gradient; ComputeCapGradient( pointReverse, const_cast(centerTypes), length*length, inset, &gradient // OUT parameter ); tangent.X = - gradient.X; tangent.Y = - gradient.Y; // Move end point left or right to account for inset // pens, if needed. GpPointF end; end.X = endPoint.X; end.Y = endPoint.Y; customEndCap->GetTransformedStrokeCap( maxCount, &points, &types, &endCount, end, tangent, width, width ); PrepareDpPenForCustomCap(&pen, customEndCap); GpPathWidener widener( points, types, endCount, &pen, &XForm, DpiX, // widener doesn't use these. DpiY, Antialias ); widener.Widen(endCapPoints, endCapTypes); } GpFree(points); GpFree(types); return status; } /**************************************************************************\ * * Function Description: * * This creates and returns two GpPaths containing the start and end cap. * The two caps are correctly positioned and scaled. * * Return * * Status * * Arguments: * * [OUT] startCapPath, endCapPath * * Created: * * 10/05/2000 asecchia * created it. * \**************************************************************************/ GpStatus GpEndCapCreator::GetCapsForSubpath( GpPath **startCapPath, GpPath **endCapPath, GpPointF *centerPoints, BYTE *centerTypes, INT centerCount ) { // Validate our input parameters. ASSERT(startCapPath != NULL); ASSERT(endCapPath != NULL); ASSERT(*startCapPath == NULL); ASSERT(*endCapPath == NULL); DynPointFArray startCapPoints; DynPointFArray endCapPoints; DynByteArray startCapTypes; DynByteArray endCapTypes; GpPointF startPoint, endPoint; startPoint = *(centerPoints); endPoint = *(centerPoints + centerCount - 1); GpStatus status = Ok; if(StartCap || EndCap) { status = SetCustomFillCaps( StartCap, EndCap, startPoint, endPoint, centerPoints, centerTypes, centerCount, &startCapPoints, &endCapPoints, &startCapTypes, &endCapTypes ); if(status == Ok) { status = SetCustomStrokeCaps( StartCap, EndCap, startPoint, endPoint, centerPoints, centerTypes, centerCount, &startCapPoints, &endCapPoints, &startCapTypes, &endCapTypes ); } } if(startCapPoints.GetCount() > 0) { *startCapPath = new GpPath( startCapPoints.GetDataBuffer(), startCapTypes.GetDataBuffer(), startCapPoints.GetCount() ); if(*startCapPath == NULL) { status = OutOfMemory; } } if(endCapPoints.GetCount() > 0) { *endCapPath = new GpPath( endCapPoints.GetDataBuffer(), endCapTypes.GetDataBuffer(), endCapPoints.GetCount() ); if(*endCapPath == NULL) { status = OutOfMemory; } } if(status != Ok) { delete *startCapPath; delete *endCapPath; *startCapPath = NULL; *endCapPath = NULL; status = OutOfMemory; } return status; }