WindowsXP-SP1/windows/advcore/gdiplus/engine/entry/endcap.cpp
2020-09-30 16:53:49 +02:00

1194 lines
32 KiB
C++

/**************************************************************************
*
* 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<GpCustomLineCap*>(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<GpCustomLineCap*>(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<GpPointF> &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<GpPointF> &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<GpPointF*>(Path->GetPathPoints()),
const_cast<BYTE*>(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<GpPointF> pointIterator(
const_cast<GpPointF*>(centerPoints),
centerPointCount
);
GpVector2D gradient;
ComputeCapGradient(
pointIterator,
const_cast<BYTE*>(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<GpPointF> pointIterator(
const_cast<GpPointF*>(centerPoints),
centerPointCount
);
GpReverseIterator<GpPointF> pointReverse(&pointIterator);
pointReverse.SeekFirst();
GpVector2D gradient;
ComputeCapGradient(
pointReverse,
const_cast<BYTE*>(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)<REAL_EPSILON)
{
length = 1.0f;
}
// 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);
inset /= length;
length *= width;
// Compute the gradient of the cap.
GpArrayIterator<GpPointF> pointIterator(
const_cast<GpPointF*>(centerPoints),
centerPointCount
);
GpVector2D gradient;
ComputeCapGradient(
pointIterator,
const_cast<BYTE*>(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)<REAL_EPSILON)
{
length = 1.0f;
}
// 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);
inset /= length;
length *= width;
// Compute the gradient of the cap.
GpArrayIterator<GpPointF> pointIterator(
const_cast<GpPointF*>(centerPoints),
centerPointCount
);
GpReverseIterator<GpPointF> pointReverse(&pointIterator);
pointReverse.SeekFirst();
GpVector2D gradient;
ComputeCapGradient(
pointReverse,
const_cast<BYTE*>(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;
}