You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5668 lines
142 KiB
5668 lines
142 KiB
/**************************************************************************\
|
|
*
|
|
* Copyright (c) 1998 Microsoft Corporation
|
|
*
|
|
* Module Name:
|
|
*
|
|
* path.cpp
|
|
*
|
|
* Abstract:
|
|
*
|
|
* Implementation of the GpPath and DpPath classes
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 12/11/1998 davidx
|
|
* Add path functions.
|
|
*
|
|
* 12/07/1998 davidx
|
|
* Initial placeholders.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
#include "precomp.hpp"
|
|
|
|
//-------------------------------------------------------------
|
|
// ReversePath(), CombinePaths(), CalculateGradientArray(), and
|
|
// GetMajorAndMinorAxis(), and GetFastAngle are defined in
|
|
// PathWidener.cpp.
|
|
//-------------------------------------------------------------
|
|
|
|
extern GpStatus
|
|
ReversePath(
|
|
INT count,
|
|
GpPointF* points,
|
|
BYTE* types
|
|
);
|
|
|
|
extern INT
|
|
CombinePaths(
|
|
INT count,
|
|
GpPointF* points,
|
|
BYTE* types,
|
|
INT count1,
|
|
const GpPointF* points1,
|
|
const BYTE* types1,
|
|
BOOL forward1,
|
|
INT count2,
|
|
const GpPointF* points2,
|
|
const BYTE* types2,
|
|
BOOL forward2,
|
|
BOOL connect
|
|
);
|
|
|
|
extern GpStatus
|
|
CalculateGradientArray(
|
|
GpPointF* grad,
|
|
REAL* distances,
|
|
const GpPointF* points,
|
|
INT count
|
|
);
|
|
|
|
extern GpStatus
|
|
GetMajorAndMinorAxis(
|
|
REAL* majorR,
|
|
REAL* minorR,
|
|
const GpMatrix* matrix
|
|
);
|
|
|
|
VOID NormalizeAngle(REAL* angle, REAL width, REAL height);
|
|
|
|
INT NormalizeArcAngles(
|
|
REAL* startAngle,
|
|
REAL* sweepAngle,
|
|
REAL width,
|
|
REAL height
|
|
);
|
|
|
|
// Note that this is different from GpPathData.
|
|
|
|
class MetaPathData : public ObjectData
|
|
{
|
|
public:
|
|
UINT32 Count;
|
|
INT32 Flags;
|
|
};
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Get the path data.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] dataBuffer - fill this buffer with the data
|
|
* [IN/OUT] size - IN - size of buffer; OUT - number bytes written
|
|
*
|
|
* Return Value:
|
|
*
|
|
* GpStatus - Ok or error code
|
|
*
|
|
* Created:
|
|
*
|
|
* 9/13/1999 DCurtis
|
|
*
|
|
\**************************************************************************/
|
|
GpStatus
|
|
DpPath::GetData(
|
|
IStream * stream
|
|
) const
|
|
{
|
|
ASSERT (stream != NULL);
|
|
|
|
INT count = Points.GetCount();
|
|
MetafilePointData pointData(Points.GetDataBuffer(), count);
|
|
UINT pointsSize = pointData.GetDataSize();
|
|
INT flags = pointData.GetFlags();
|
|
|
|
if (FillMode == FillModeWinding)
|
|
{
|
|
flags |= GDIP_EPRFLAGS_WINDINGFILL;
|
|
}
|
|
|
|
MetaPathData pathData;
|
|
pathData.Count = count;
|
|
pathData.Flags = flags;
|
|
stream->Write(&pathData, sizeof(pathData), NULL);
|
|
|
|
stream->Write(pointData.GetData(), pointsSize, NULL);
|
|
stream->Write(Types.GetDataBuffer(), count, NULL);
|
|
|
|
// align
|
|
if ((count & 0x03) != 0)
|
|
{
|
|
INT pad = 0;
|
|
stream->Write(&pad, 4 - (count & 0x03), NULL);
|
|
}
|
|
|
|
return Ok;
|
|
}
|
|
|
|
UINT
|
|
DpPath::GetDataSize() const
|
|
{
|
|
INT count = Points.GetCount();
|
|
MetafilePointData pointData(Points.GetDataBuffer(), count);
|
|
UINT pointsSize = pointData.GetDataSize();
|
|
UINT dataSize = sizeof(MetaPathData) + pointsSize + count;
|
|
|
|
return ((dataSize + 3) & (~3)); // align
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Read the path object from memory.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] memory - the data that was read from the stream
|
|
* [IN] size - the size of the memory data
|
|
*
|
|
* Return Value:
|
|
*
|
|
* GpStatus - Ok or failure status
|
|
*
|
|
* Created:
|
|
*
|
|
* 4/26/1999 DCurtis
|
|
*
|
|
\**************************************************************************/
|
|
GpStatus
|
|
DpPath::SetData(
|
|
const BYTE * dataBuffer,
|
|
UINT size
|
|
)
|
|
{
|
|
Points.Reset();
|
|
Types.Reset();
|
|
|
|
if (dataBuffer == NULL)
|
|
{
|
|
WARNING(("dataBuffer is NULL"));
|
|
return InvalidParameter;
|
|
}
|
|
|
|
if (size >= sizeof(MetaPathData))
|
|
{
|
|
const MetaPathData * pathData = reinterpret_cast<const MetaPathData *>(dataBuffer);
|
|
|
|
if (!pathData->MajorVersionMatches())
|
|
{
|
|
WARNING(("Version number mismatch"));
|
|
return InvalidParameter;
|
|
}
|
|
|
|
InitDefaultState(::GetFillMode(pathData->Flags));
|
|
SetValid(TRUE);
|
|
|
|
INT count = pathData->Count;
|
|
|
|
if (count > 0)
|
|
{
|
|
UINT pointDataSize;
|
|
|
|
if ((pathData->Flags & GDIP_EPRFLAGS_COMPRESSED) != 0)
|
|
{
|
|
pointDataSize = count * sizeof(GpPoint16);
|
|
}
|
|
else
|
|
{
|
|
pointDataSize = count * sizeof(GpPointF);
|
|
}
|
|
|
|
if (size >= sizeof(MetaPathData) + count + pointDataSize)
|
|
{
|
|
GpPointF * points = Points.AddMultiple(count);
|
|
BYTE * types = Types.AddMultiple(count);
|
|
const BYTE * typeData;
|
|
const BYTE * pointData = dataBuffer + sizeof(MetaPathData);
|
|
|
|
if ((points != NULL) && (types != NULL))
|
|
{
|
|
if ((pathData->Flags & GDIP_EPRFLAGS_COMPRESSED) != 0)
|
|
{
|
|
BYTE * tmp = NULL;
|
|
|
|
::GetPointsForPlayback(
|
|
pointData,
|
|
size - (sizeof(MetaPathData) + count),
|
|
count,
|
|
pathData->Flags,
|
|
sizeof(GpPointF) * count,
|
|
(BYTE *)points,
|
|
tmp);
|
|
typeData = pointData + (count * 4);
|
|
}
|
|
else
|
|
{
|
|
GpMemcpy(points, pointData, count * sizeof(points[0]));
|
|
typeData = pointData + (count * sizeof(points[0]));
|
|
}
|
|
GpMemcpy(types, typeData, count);
|
|
|
|
if (ValidatePathTypes(types, count, &SubpathCount, &HasBezier))
|
|
{
|
|
UpdateUid();
|
|
return Ok;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WARNING(("size is too small"));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WARNING(("size is too small"));
|
|
}
|
|
|
|
SetValid(FALSE);
|
|
return GenericError;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Construct a new GpPath object using the specified path data
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] points - Point to an array of path points
|
|
* [IN] types - Specify path point types
|
|
* count - Number of path points
|
|
* fillMode - Path fill mode
|
|
*
|
|
* Return Value:
|
|
*
|
|
* NONE
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpPath::GpPath(
|
|
const GpPointF* points,
|
|
const BYTE* types,
|
|
INT count,
|
|
GpFillMode fillMode
|
|
)
|
|
{
|
|
SetValid(FALSE);
|
|
|
|
// Validate function parameters
|
|
|
|
if (count <= 0 ||
|
|
(count > 0 && (!points || !types)) ||
|
|
(fillMode != FillModeAlternate && fillMode != FillModeWinding))
|
|
{
|
|
WARNING(("Invalid path data in GpPath::GpPath"));
|
|
return;
|
|
}
|
|
|
|
InitDefaultState(fillMode);
|
|
|
|
// Validate path point types
|
|
|
|
if (!ValidatePathTypes(types, count, &SubpathCount, &HasBezier))
|
|
{
|
|
WARNING(("Invalid path type information"));
|
|
return;
|
|
}
|
|
|
|
// Copy path point and type information
|
|
|
|
SetValid(Types.AddMultiple(types, count) == Ok &&
|
|
Points.AddMultiple(points, count) == Ok);
|
|
|
|
if(IsValid()) {
|
|
// Make sure the first point is the start type.
|
|
|
|
Types.First() = PathPointTypeStart;
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------
|
|
// Constructor for polygon.
|
|
//--------------------------------
|
|
|
|
GpPath::GpPath(
|
|
const GpPointF *points,
|
|
INT count,
|
|
GpPointF *stackPoints,
|
|
BYTE *stackTypes,
|
|
INT stackCount,
|
|
GpFillMode fillMode,
|
|
DpPathFlags flags
|
|
) : DpPath(points, count, stackPoints, stackTypes, stackCount,
|
|
fillMode, flags)
|
|
{
|
|
InvalidateCache();
|
|
}
|
|
|
|
//--------------------------------
|
|
// Copy constructor.
|
|
//--------------------------------
|
|
|
|
GpPath::GpPath(const GpPath* path) : DpPath(path)
|
|
{
|
|
SetValid(path != NULL);
|
|
|
|
InvalidateCache();
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Copies the path data. Points and Types array in pathData
|
|
* must be allocated by the caller.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [OUT] pathData - the path data.
|
|
*
|
|
* Return Value:
|
|
*
|
|
* TRUE if successfull.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
DpPath::GetPathData(GpPathData* pathData)
|
|
{
|
|
if ((!pathData) || (!pathData->Points) || (!pathData->Types) || (pathData->Count < 0))
|
|
return InvalidParameter;
|
|
|
|
INT count = GetPointCount();
|
|
const GpPointF* points = GetPathPoints();
|
|
const BYTE* types = GetPathTypes();
|
|
|
|
if (pathData->Count >= count)
|
|
{
|
|
if (count > 0)
|
|
{
|
|
GpMemcpy(pathData->Points, points, count*sizeof(GpPointF));
|
|
GpMemcpy(pathData->Types, types, count);
|
|
}
|
|
|
|
pathData->Count = count;
|
|
return Ok;
|
|
}
|
|
else
|
|
return OutOfMemory;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Set a marker at the current location. You cannot set a marker at the
|
|
* first position.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* None
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
GpPath::SetMarker()
|
|
{
|
|
INT count = Types.GetCount();
|
|
BYTE* types = Types.GetDataBuffer();
|
|
|
|
// Don't set a marker at the first point.
|
|
|
|
if(count > 1 && types)
|
|
{
|
|
types[count - 1] |= PathPointTypePathMarker;
|
|
UpdateUid();
|
|
}
|
|
|
|
return Ok;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Clears all the markers in the path.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* None
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
GpPath::ClearMarkers()
|
|
{
|
|
INT count = Types.GetCount();
|
|
BYTE* types = Types.GetDataBuffer();
|
|
|
|
BOOL modified = FALSE;
|
|
|
|
if(count > 0 && types)
|
|
{
|
|
for(INT i = 0; i < count; i++)
|
|
{
|
|
if(types[i] & PathPointTypePathMarker)
|
|
{
|
|
types[i] &= ~PathPointTypePathMarker;
|
|
modified = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(modified)
|
|
{
|
|
UpdateUid();
|
|
}
|
|
|
|
return Ok;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Set the path data.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] pathData - the path data.
|
|
*
|
|
* Return Value:
|
|
*
|
|
* TRUE if successfull.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
DpPath::SetPathData(const GpPathData* pathData)
|
|
{
|
|
if(!pathData || pathData->Count <= 0)
|
|
return InvalidParameter;
|
|
|
|
INT count = pathData->Count;
|
|
DpPathIterator iter(pathData->Points, pathData->Types, count);
|
|
|
|
if(!iter.IsValid())
|
|
return InvalidParameter;
|
|
|
|
Points.Reset(FALSE);
|
|
Types.Reset(FALSE);
|
|
|
|
GpPointF* points = Points.AddMultiple(count);
|
|
BYTE* types = Types.AddMultiple(count);
|
|
|
|
if(points && types)
|
|
{
|
|
INT number, startIndex, endIndex;
|
|
BOOL isClosed = FALSE;
|
|
|
|
while(number = iter.NextSubpath(&startIndex, &endIndex, &isClosed))
|
|
{
|
|
GpMemcpy(
|
|
points,
|
|
pathData->Points + startIndex,
|
|
number*sizeof(GpPointF)
|
|
);
|
|
GpMemcpy(
|
|
types,
|
|
pathData->Types + startIndex,
|
|
number
|
|
);
|
|
|
|
points += number;
|
|
types += number;
|
|
}
|
|
|
|
SetValid(TRUE);
|
|
HasBezier = iter.HasCurve();
|
|
Flags = PossiblyNonConvex;
|
|
SubpathCount = iter.GetSubpathCount();
|
|
IsSubpathActive = !isClosed;
|
|
UpdateUid();
|
|
return Ok;
|
|
}
|
|
else
|
|
return OutOfMemory;
|
|
}
|
|
|
|
// Determine if the specified points form a rectangle that is axis aligned.
|
|
// Do the test in the coordinate space specified by the matrix (if present).
|
|
// Covert the REAL values to FIX4 values so that the test is compatible
|
|
// with what the rasterizer would do.
|
|
// If transformedBounds is not NULL, return the rect (if it is a rect)
|
|
// in the transformed (device) space.
|
|
BOOL
|
|
IsRectanglePoints(
|
|
const GpPointF* points,
|
|
INT count,
|
|
const GpMatrix * matrix,
|
|
GpRectF * transformedBounds
|
|
)
|
|
{
|
|
if(count < 4 || count > 5)
|
|
return FALSE;
|
|
|
|
GpPointF transformedPoints[5];
|
|
|
|
if ((matrix != NULL) && (!matrix->IsIdentity()))
|
|
{
|
|
matrix->Transform(points, transformedPoints, count);
|
|
points = transformedPoints;
|
|
}
|
|
|
|
PointFix4 fix4Points[5];
|
|
|
|
fix4Points[0].Set(points[0].X, points[0].Y);
|
|
|
|
if(count == 5)
|
|
{
|
|
fix4Points[4].Set(points[4].X, points[4].Y);
|
|
|
|
if(fix4Points[0].X != fix4Points[4].X || fix4Points[0].Y != fix4Points[4].Y)
|
|
return FALSE;
|
|
}
|
|
|
|
fix4Points[1].Set(points[1].X, points[1].Y);
|
|
fix4Points[2].Set(points[2].X, points[2].Y);
|
|
fix4Points[3].Set(points[3].X, points[3].Y);
|
|
|
|
REAL maxValue;
|
|
|
|
if (fix4Points[0].Y == fix4Points[1].Y)
|
|
{
|
|
if ((fix4Points[2].Y == fix4Points[3].Y) &&
|
|
(fix4Points[0].X == fix4Points[3].X) &&
|
|
(fix4Points[1].X == fix4Points[2].X))
|
|
{
|
|
if (transformedBounds != NULL)
|
|
{
|
|
transformedBounds->X = min(points[0].X, points[1].X);
|
|
maxValue = max(points[0].X, points[1].X);
|
|
transformedBounds->Width = maxValue - transformedBounds->X;
|
|
|
|
transformedBounds->Y = min(points[0].Y, points[2].Y);
|
|
maxValue = max(points[0].Y, points[2].Y);
|
|
transformedBounds->Height = maxValue - transformedBounds->Y;
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
else if ((fix4Points[0].X == fix4Points[1].X) &&
|
|
(fix4Points[2].X == fix4Points[3].X) &&
|
|
(fix4Points[0].Y == fix4Points[3].Y) &&
|
|
(fix4Points[1].Y == fix4Points[2].Y))
|
|
{
|
|
if (transformedBounds != NULL)
|
|
{
|
|
transformedBounds->X = min(points[0].X, points[2].X);
|
|
maxValue = max(points[0].X, points[2].X);
|
|
transformedBounds->Width = maxValue - transformedBounds->X;
|
|
|
|
transformedBounds->Y = min(points[0].Y, points[1].Y);
|
|
maxValue = max(points[0].Y, points[1].Y);
|
|
transformedBounds->Height = maxValue - transformedBounds->Y;
|
|
}
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL
|
|
GpPath::IsRectangle(
|
|
const GpMatrix * matrix,
|
|
GpRectF * transformedBounds
|
|
) const
|
|
{
|
|
if((SubpathCount != 1) || HasBezier)
|
|
return FALSE;
|
|
|
|
INT count = GetPointCount();
|
|
GpPointF* points = Points.GetDataBuffer();
|
|
|
|
return IsRectanglePoints(points, count, matrix, transformedBounds);
|
|
}
|
|
|
|
BOOL
|
|
DpPath::IsRectangle(
|
|
const GpMatrix * matrix,
|
|
GpRectF * transformedBounds
|
|
) const
|
|
{
|
|
if ((GetSubpathCount() != 1) || HasCurve())
|
|
return FALSE;
|
|
|
|
INT count = GetPointCount();
|
|
GpPointF* points = Points.GetDataBuffer();
|
|
|
|
return IsRectanglePoints(points, count, matrix, transformedBounds);
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Determine if the receiver and path represent the same path
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] path - GpPath to compare
|
|
*
|
|
* Return Value:
|
|
*
|
|
* TRUE if the paths are the same.
|
|
*
|
|
* Created - 5/27/99 peterost
|
|
*
|
|
\**************************************************************************/
|
|
|
|
BOOL GpPath::IsEqual(const GpPath* path) const
|
|
{
|
|
if (path == this)
|
|
return TRUE;
|
|
|
|
INT count;
|
|
|
|
if (IsValid() == path->IsValid() &&
|
|
(count=GetPointCount()) == path->GetPointCount() &&
|
|
HasBezier == path->HasBezier &&
|
|
FillMode == path->FillMode &&
|
|
Flags == path->Flags &&
|
|
IsSubpathActive == path->IsSubpathActive &&
|
|
SubpathCount == path->SubpathCount)
|
|
{
|
|
BYTE* types = path->Types.GetDataBuffer();
|
|
BYTE* mytypes = Types.GetDataBuffer();
|
|
GpPointF* points = path->Points.GetDataBuffer();
|
|
GpPointF* mypoints = Points.GetDataBuffer();
|
|
|
|
for (INT i=0; i<count; i++)
|
|
{
|
|
if (types[i] != mytypes[i] ||
|
|
points[i].X != mypoints[i].X ||
|
|
points[i].Y != mypoints[i].Y)
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
VOID
|
|
GpPath::InitDefaultState(
|
|
GpFillMode fillMode
|
|
)
|
|
|
|
{
|
|
DpPath::InitDefaultState(fillMode);
|
|
InvalidateCache();
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Validate path point type information
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] types - Point to an array of path point types
|
|
* count - Number of points
|
|
* subpathCount - Return the number of subpaths
|
|
* hasBezier - Return whether the path has Bezier segments
|
|
* [IN] needsFirstPointToBeStartPoint - TRUE if this data needs to start
|
|
* with a StartPoint. (Default is TRUE)
|
|
*
|
|
* Return Value:
|
|
*
|
|
* TRUE if the path point type information is valid
|
|
* FALSE otherwise
|
|
*
|
|
\**************************************************************************/
|
|
|
|
BOOL
|
|
DpPath::ValidatePathTypes(
|
|
const BYTE* types,
|
|
INT count,
|
|
INT* subpathCount,
|
|
BOOL* hasBezier
|
|
)
|
|
{
|
|
DpPathTypeIterator iter(types, count);
|
|
|
|
if(!iter.IsValid())
|
|
{
|
|
WARNING(("Invalid path type information"));
|
|
return FALSE;
|
|
}
|
|
|
|
*subpathCount = iter.GetSubpathCount();
|
|
*hasBezier = iter.HasCurve();
|
|
|
|
return iter.IsValid();
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Private helper function to add points to a path object
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] points - Specify the points to be added
|
|
* count - Number of points to add
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Point to location in the point type data buffer
|
|
* that corresponds to the *SECOND* path point added.
|
|
*
|
|
* The first point type is always handled inside this
|
|
* function:
|
|
*
|
|
* 1. If either the previous subpath is closed, or addClosedFigure
|
|
* parameter is TRUE, the first point type will be StartPoint.
|
|
*
|
|
* 2. Otherwise, the previous subpath is open and addClosedFigure
|
|
* parameter is FALSE. We have two separate cases to handle:
|
|
*
|
|
* 2.1 if the first point to be added is the same as the last
|
|
* point of the open subpath, then the first point is ignored.
|
|
*
|
|
* 2.2 otherwise, the first point type will be LinePoint.
|
|
*
|
|
* NULL if there is an error. In this case, existing path
|
|
* data is not affected.
|
|
*
|
|
* Note:
|
|
*
|
|
* We assume the caller has already obtained a lock
|
|
* on the path object.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
BYTE*
|
|
GpPath::AddPointHelper(
|
|
const GpPointF* points,
|
|
INT count,
|
|
BOOL addClosedFigure
|
|
)
|
|
{
|
|
// If we're adding a closed figure, then make sure
|
|
// there is no more currently active subpath.
|
|
|
|
if (addClosedFigure)
|
|
StartFigure();
|
|
|
|
INT origCount = GetPointCount();
|
|
|
|
BOOL isDifferentPoint = TRUE;
|
|
|
|
// Check if the first point is the same as the last point.
|
|
|
|
if(IsSubpathActive && origCount > 0)
|
|
{
|
|
GpPointF lastPt = Points.Last();
|
|
if ((REALABS(points->X - lastPt.X) < REAL_EPSILON) &&
|
|
(REALABS(points->Y - lastPt.Y) < REAL_EPSILON) )
|
|
{
|
|
if(count == 1)
|
|
return NULL;
|
|
|
|
// case 2.1 above
|
|
// Skip the first point and its type.
|
|
|
|
count--;
|
|
points++;
|
|
isDifferentPoint = FALSE;
|
|
}
|
|
}
|
|
|
|
// Resize Points and Types
|
|
|
|
GpPointF* pointbuf = Points.AddMultiple(count);
|
|
BYTE* typebuf = Types.AddMultiple(count);
|
|
|
|
if(pointbuf == NULL || typebuf == NULL)
|
|
{
|
|
// Resize the original size.
|
|
|
|
Points.SetCount(origCount);
|
|
Types.SetCount(origCount);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
// Record the type of the first point (Start or Line Point).
|
|
|
|
if (!IsSubpathActive)
|
|
{
|
|
// case 1 above
|
|
|
|
*typebuf++ = PathPointTypeStart;
|
|
SubpathCount++; // Starting a new subpath.
|
|
}
|
|
else
|
|
{
|
|
// If the first point is different, add a Line type.
|
|
// Otherwise, skip the first point and its type.
|
|
|
|
if(isDifferentPoint)
|
|
{
|
|
// case 2.2 above
|
|
|
|
*typebuf++ = PathPointTypeLine;
|
|
}
|
|
}
|
|
|
|
// Copy path point data
|
|
|
|
GpMemcpy(pointbuf, points, count*sizeof(GpPointF));
|
|
|
|
// Subpath is active if the added figure is not closed.
|
|
|
|
if(!addClosedFigure)
|
|
IsSubpathActive = TRUE;
|
|
|
|
UpdateUid();
|
|
InvalidateCache();
|
|
|
|
// Return the starting location for the new point type data
|
|
// From the second point type.
|
|
|
|
return typebuf;
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Add a series of line segments to the current path object
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] points - Specify the line points
|
|
* count - Number of points
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
GpPath::AddLines(
|
|
const GpPointF* points,
|
|
INT count
|
|
)
|
|
{
|
|
ASSERT(IsValid());
|
|
|
|
// Validate function parameters
|
|
|
|
if (points == NULL || count < 1)
|
|
return InvalidParameter;
|
|
|
|
InvalidateCache();
|
|
|
|
// Call the internal helper function to add the points
|
|
|
|
BYTE* types = AddPointHelper(points, count, FALSE);
|
|
|
|
if (types == NULL)
|
|
{
|
|
if(count > 1)
|
|
return OutOfMemory;
|
|
else
|
|
return Ok;
|
|
}
|
|
|
|
// Set path point type information
|
|
|
|
GpMemset(types, PathPointTypeLine, count-1);
|
|
// IsSubpathActive = TRUE; This is set in AddPointHelper. - ikkof
|
|
UpdateUid();
|
|
|
|
return Ok;
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Add rectangles to the current path object
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] rects - Specify the rectangles to be added
|
|
* count - Number of rectangles
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
GpPath::AddRects(
|
|
const GpRectF* rect,
|
|
INT count
|
|
)
|
|
{
|
|
if (count < 1 || rect == NULL)
|
|
return InvalidParameter;
|
|
|
|
// NOTE: We don't need a lock here because
|
|
// AddPolygon will handle it.
|
|
|
|
// Add one rectangle at a time as a polygon
|
|
|
|
GpPointF points[4];
|
|
GpStatus status;
|
|
|
|
for ( ; count--; rect++)
|
|
{
|
|
if (rect->IsEmptyArea())
|
|
continue;
|
|
|
|
// NOTE: Rectangle points are added in clockwise
|
|
// order, starting from the top-left corner.
|
|
|
|
points[0].X = rect->GetLeft(); // top-left
|
|
points[0].Y = rect->GetTop();
|
|
points[1].X = rect->GetRight(); // top-right
|
|
points[1].Y = rect->GetTop();
|
|
points[2].X = rect->GetRight(); // bottom-right
|
|
points[2].Y = rect->GetBottom();
|
|
points[3].X = rect->GetLeft(); // bottom-left
|
|
points[3].Y = rect->GetBottom();
|
|
|
|
if ((status = AddPolygon(points, 4)) != Ok)
|
|
return status;
|
|
}
|
|
|
|
return Ok;
|
|
}
|
|
|
|
GpStatus
|
|
GpPath::AddRects(
|
|
const RECT* rects,
|
|
INT count
|
|
)
|
|
{
|
|
if ((count < 1) || (rects == NULL))
|
|
{
|
|
return InvalidParameter;
|
|
}
|
|
|
|
// NOTE: We don't need a lock here because
|
|
// AddPolygon will handle it.
|
|
|
|
// Add one rectangle at a time as a polygon
|
|
|
|
GpPointF points[4];
|
|
GpStatus status;
|
|
|
|
for ( ; count--; rects++)
|
|
{
|
|
if ((rects->left >= rects->right) || (rects->top >= rects->bottom))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// NOTE: Rectangle points are added in clockwise
|
|
// order, starting from the top-left corner.
|
|
|
|
points[0].X = (REAL)rects->left; // top-left
|
|
points[0].Y = (REAL)rects->top;
|
|
points[1].X = (REAL)rects->right; // top-right
|
|
points[1].Y = (REAL)rects->top;
|
|
points[2].X = (REAL)rects->right; // bottom-right
|
|
points[2].Y = (REAL)rects->bottom;
|
|
points[3].X = (REAL)rects->left; // bottom-left
|
|
points[3].Y = (REAL)rects->bottom;
|
|
|
|
if ((status = AddPolygon(points, 4)) != Ok)
|
|
{
|
|
return status;
|
|
}
|
|
}
|
|
|
|
return Ok;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Add a polygon to the current path object
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] Specify the polygon points
|
|
* count - Number of points
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
GpPath::AddPolygon(
|
|
const GpPointF* points,
|
|
INT count
|
|
)
|
|
{
|
|
ASSERT(IsValid());
|
|
|
|
if (count < 3 || points == NULL)
|
|
{
|
|
return InvalidParameter;
|
|
}
|
|
|
|
// Check if the last point is the same as the first point.
|
|
// If so, ignore it.
|
|
|
|
if (count > 3 &&
|
|
points[0].X == points[count-1].X &&
|
|
points[0].Y == points[count-1].Y)
|
|
{
|
|
count--;
|
|
}
|
|
|
|
// Call the internal helper function to add the points
|
|
|
|
BYTE* types = AddPointHelper(points, count, TRUE);
|
|
|
|
InvalidateCache();
|
|
|
|
if (types == NULL)
|
|
{
|
|
return OutOfMemory;
|
|
}
|
|
|
|
// Set path point type information
|
|
|
|
GpMemset(types, PathPointTypeLine, count-2);
|
|
types[count-2] = PathPointTypeLine | PathPointTypeCloseSubpath;
|
|
|
|
UpdateUid();
|
|
|
|
return Ok;
|
|
}
|
|
|
|
|
|
#define PI TOREAL(3.1415926535897932)
|
|
#define HALF_PI TOREAL(1.5707963267948966)
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Convert an angle defined in a box with (width, height) to
|
|
* an angle defined in a square box.
|
|
* In other words, this shrink x- and y-coordinates by width and height,
|
|
* and then calculates the new angle.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN/OUT] angle - the angle is given in degrees and return it in radian.
|
|
* [IN] width - the width of the box.
|
|
* [IN] height - the height of the box.
|
|
*
|
|
* Return Value:
|
|
*
|
|
* NONE
|
|
*
|
|
* History:
|
|
*
|
|
* 02/22/1999 ikkof
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
VOID
|
|
NormalizeAngle(REAL* angle, REAL width, REAL height)
|
|
{
|
|
REAL a = *angle;
|
|
|
|
// Set the angle between 0 and 360 degrees.
|
|
|
|
a = GpModF(a, 360);
|
|
|
|
if(a < 0 || a > 360)
|
|
{
|
|
// The input data may have been too large or loo small
|
|
// to calculate the mode. In that case, set to 0.
|
|
|
|
a = 0;
|
|
}
|
|
|
|
if(width != height)
|
|
{
|
|
INT plane = 1;
|
|
REAL b = a;
|
|
|
|
if(a <= 90)
|
|
plane = 1;
|
|
else if(a <= 180)
|
|
{
|
|
plane = 2;
|
|
b = 180 - a;
|
|
}
|
|
else if(a <= 270)
|
|
{
|
|
plane = 3;
|
|
b = a - 180;
|
|
}
|
|
else
|
|
{
|
|
plane = 4;
|
|
b = 360 - a;
|
|
}
|
|
|
|
b = b*PI/180; // Convert to radian
|
|
|
|
// Get the normalized angle in the plane 1.
|
|
|
|
a = TOREAL( atan2(width*sin(b), height*cos(b)) );
|
|
|
|
// Adjust to the angle in one of 4 planes.
|
|
|
|
switch(plane)
|
|
{
|
|
case 1:
|
|
default:
|
|
break;
|
|
|
|
case 2:
|
|
a = PI - a;
|
|
break;
|
|
|
|
case 3:
|
|
a = PI + a;
|
|
break;
|
|
|
|
case 4:
|
|
a = 2*PI - a;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
a = a*PI/180; // Convert to radian.
|
|
}
|
|
|
|
*angle = a;
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Convert the start and sweep angles defined in a box with (width, height)
|
|
* to an angle defined in a square box.
|
|
* In other words, this shrink x- and y-coordinates by width and height,
|
|
* and then calculates the new angles.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN/OUT] startAngle - it is given in degrees and return it in radian.
|
|
* [IN/OUT] sweepAngle - it is given in degrees and return it in radian.
|
|
* [IN] width - the width of the box.
|
|
* [IN] height - the height of the box.
|
|
*
|
|
* Return Value:
|
|
*
|
|
* INT - +1 if sweeping in clockwise and -1 in counterclockwise.
|
|
*
|
|
* History:
|
|
*
|
|
* 02/22/1999 ikkof
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
INT
|
|
NormalizeArcAngles(
|
|
REAL* startAngle,
|
|
REAL* sweepAngle,
|
|
REAL width,
|
|
REAL height
|
|
)
|
|
{
|
|
REAL a0 = *startAngle; // The start angle.
|
|
REAL dA = *sweepAngle;
|
|
REAL a1 = a0 + dA; // The end angle.
|
|
INT sweepSign;
|
|
|
|
if(dA > 0)
|
|
sweepSign = 1;
|
|
else
|
|
{
|
|
sweepSign = - 1;
|
|
dA = - dA; // Convert to a positive sweep angle.
|
|
}
|
|
|
|
// Normalize the start and end angle.
|
|
|
|
NormalizeAngle(&a0, width, height);
|
|
NormalizeAngle(&a1, width, height);
|
|
|
|
if(dA < 360)
|
|
{
|
|
if(sweepSign > 0)
|
|
{
|
|
dA = a1 - a0;
|
|
}
|
|
else
|
|
{
|
|
dA = a0 - a1;
|
|
}
|
|
if(dA < 0)
|
|
dA += 2*PI;
|
|
}
|
|
else
|
|
dA = 2*PI; // Don't sweep more than once.
|
|
|
|
*startAngle = a0;
|
|
*sweepAngle = dA;
|
|
|
|
return sweepSign;
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Convert an elliptical arc to a series of Bezier curve segments
|
|
*
|
|
* Arguments:
|
|
*
|
|
* points - Specify a point buffer for returning Bezier control points
|
|
* The array should be able to hold 13 elements or more.
|
|
* rect - Specify the bounding box for the ellipse
|
|
* startAngle - Start angle (in elliptical space and degrees)
|
|
* sweepAngle - Sweep angle
|
|
* positive to sweep clockwise
|
|
* negative to sweep counterclockwise
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Number of Bezier control points generated
|
|
* 0 if sweep angle is 0
|
|
* -1 if bounding rectangle is empty
|
|
*
|
|
\**************************************************************************/
|
|
|
|
INT
|
|
GpPath::GetArcPoints(
|
|
GpPointF* points,
|
|
const GpRectF& rect,
|
|
REAL startAngle,
|
|
REAL sweepAngle
|
|
)
|
|
{
|
|
if (rect.IsEmptyArea())
|
|
return -1;
|
|
else if (sweepAngle == 0)
|
|
return 0;
|
|
|
|
// Determine which direction we should sweep
|
|
// and clamp sweep angle to a max of 360 degrees
|
|
// Both start and sweep angles are conveted to radian.
|
|
|
|
INT sweepSign = NormalizeArcAngles(
|
|
&startAngle,
|
|
&sweepAngle,
|
|
rect.Width,
|
|
rect.Height);
|
|
|
|
// Temporary variables
|
|
|
|
REAL dx, dy;
|
|
REAL w2, h2;
|
|
|
|
w2 = rect.Width / 2;
|
|
h2 = rect.Height / 2;
|
|
dx = rect.X + w2;
|
|
dy = rect.Y + h2;
|
|
|
|
// Determine the number of Bezier segments needed
|
|
|
|
int segments, count;
|
|
GpMatrix m;
|
|
|
|
segments = (INT) (sweepAngle / HALF_PI);
|
|
|
|
if (segments*HALF_PI < sweepAngle)
|
|
segments++;
|
|
|
|
if (segments == 0)
|
|
segments = 1;
|
|
else if (segments > 4)
|
|
segments = 4;
|
|
|
|
count = segments*3 + 1;
|
|
|
|
while (segments--)
|
|
{
|
|
// Compute the Bezier control points in unit-circle space
|
|
|
|
REAL A, C, S;
|
|
REAL x, y;
|
|
|
|
A = (sweepAngle > HALF_PI) ? HALF_PI/2 : sweepAngle/2;
|
|
C = REALCOS(A);
|
|
S = REALSIN(A);
|
|
|
|
x = (4 - C) / 3;
|
|
y = (3 - C) * S / (3 + 3*C);
|
|
|
|
if (sweepSign > 0)
|
|
{
|
|
// clockwise sweep
|
|
|
|
points[0].X = C;
|
|
points[0].Y = -S;
|
|
points[1].X = x;
|
|
points[1].Y = -y;
|
|
points[2].X = x;
|
|
points[2].Y = y;
|
|
points[3].X = C;
|
|
points[3].Y = S;
|
|
}
|
|
else
|
|
{
|
|
// counterclockwise sweep
|
|
|
|
points[0].X = C;
|
|
points[0].Y = S;
|
|
points[1].X = x;
|
|
points[1].Y = y;
|
|
points[2].X = x;
|
|
points[2].Y = -y;
|
|
points[3].X = C;
|
|
points[3].Y = -S;
|
|
}
|
|
|
|
// Transform the control points to elliptical space
|
|
|
|
m.Reset();
|
|
m.Translate(dx, dy);
|
|
m.Scale(w2, h2);
|
|
REAL theta = (startAngle + sweepSign*A)*180/PI;
|
|
m.Rotate(theta); // Rotate takes degrees.
|
|
|
|
if(segments > 0)
|
|
m.Transform(points, 3);
|
|
else
|
|
m.Transform(points, 4); // Include the last point.
|
|
|
|
if(sweepSign > 0)
|
|
startAngle += HALF_PI;
|
|
else
|
|
startAngle -= HALF_PI;
|
|
sweepAngle -= HALF_PI;
|
|
points += 3;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Add an elliptical arc to the current path object
|
|
*
|
|
* Arguments:
|
|
*
|
|
* rect - Specify the bounding rectangle for the ellipse
|
|
* startAngle - Starting angle for the arc
|
|
* sweepAngle - Sweep angle for the arc
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
GpPath::AddArc(
|
|
const GpRectF& rect,
|
|
REAL startAngle,
|
|
REAL sweepAngle
|
|
)
|
|
{
|
|
GpPointF points[13];
|
|
INT count;
|
|
BOOL isClosed = FALSE;
|
|
|
|
if(sweepAngle >= 360)
|
|
{
|
|
sweepAngle = 360;
|
|
isClosed = TRUE;
|
|
}
|
|
else if(sweepAngle <= - 360)
|
|
{
|
|
sweepAngle = - 360;
|
|
isClosed = TRUE;
|
|
}
|
|
|
|
// Convert arc to Bezier curve segments
|
|
|
|
count = GetArcPoints(points, rect, startAngle, sweepAngle);
|
|
|
|
// Add resulting Bezier curve segment to the path
|
|
|
|
GpStatus status = Ok;
|
|
|
|
if(count > 0)
|
|
{
|
|
AddBeziers(points, count);
|
|
if(isClosed)
|
|
CloseFigure();
|
|
}
|
|
else if(count < 0)
|
|
status = InvalidParameter;
|
|
|
|
InvalidateCache();
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Add an ellipse to the current path object
|
|
*
|
|
* Arguments:
|
|
*
|
|
* rect - Bounding rectangle for the ellipse
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* History:
|
|
*
|
|
* 02/22/1999 ikkof
|
|
* Defined an array of a circle with radius 1 and used it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
GpPath::AddEllipse(
|
|
const GpRectF& rect
|
|
)
|
|
{
|
|
GpPointF points[13];
|
|
INT count = 13;
|
|
REAL u_cir = 4*(REALSQRT(2.0) - 1)/3;
|
|
GpPointF center;
|
|
REAL wHalf, hHalf;
|
|
|
|
wHalf = rect.Width/2;
|
|
hHalf = rect.Height/2;
|
|
center.X = rect.X + wHalf;
|
|
center.Y = rect.Y + hHalf;
|
|
|
|
// 4 Bezier segment of a circle with radius 1.
|
|
|
|
points[ 0].X = 1; points[ 0].Y = 0;
|
|
points[ 1].X = 1; points[ 1].Y = u_cir;
|
|
points[ 2].X = u_cir; points[ 2].Y = 1;
|
|
points[ 3].X = 0; points[ 3].Y = 1;
|
|
points[ 4].X = -u_cir; points[ 4].Y = 1;
|
|
points[ 5].X = -1; points[ 5].Y = u_cir;
|
|
points[ 6].X = -1; points[ 6].Y = 0;
|
|
points[ 7].X = -1; points[ 7].Y = -u_cir;
|
|
points[ 8].X = -u_cir; points[ 8].Y = -1;
|
|
points[ 9].X = 0; points[ 9].Y = -1;
|
|
points[10].X = u_cir; points[10].Y = -1;
|
|
points[11].X = 1; points[11].Y = -u_cir;
|
|
points[12].X = 1; points[12].Y = 0;
|
|
|
|
// Scale to the appropriate size.
|
|
|
|
for(INT i = 0; i < count; i++)
|
|
{
|
|
points[i].X = points[i].X*wHalf + center.X;
|
|
points[i].Y = points[i].Y*hHalf + center.Y;
|
|
}
|
|
|
|
// Add resulting Bezier curve segments to the path
|
|
|
|
GpStatus status;
|
|
|
|
StartFigure();
|
|
status = AddBeziers(points, count);
|
|
CloseFigure();
|
|
|
|
InvalidateCache();
|
|
UpdateUid();
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Add an elliptical pie to the current path object
|
|
*
|
|
* Arguments:
|
|
*
|
|
* rect - Bounding rectangle for the ellipse
|
|
* startAngle - Specify the starting angle for the pie
|
|
* sweepAngle - Sweep angle for the pie
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
GpPath::AddPie(
|
|
const GpRectF& rect,
|
|
REAL startAngle,
|
|
REAL sweepAngle
|
|
)
|
|
{
|
|
GpPointF pt;
|
|
|
|
StartFigure();
|
|
|
|
// Add the center point.
|
|
|
|
pt.X = rect.X + rect.Width/2;
|
|
pt.Y = rect.Y + rect.Height/2;
|
|
GpStatus status = AddLines(&pt, 1);
|
|
|
|
// Add the arc points.
|
|
|
|
if(status == Ok)
|
|
status = AddArc(rect, startAngle, sweepAngle);
|
|
|
|
CloseFigure();
|
|
|
|
InvalidateCache();
|
|
UpdateUid();
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Add Bezier curve segments to the current path object
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] points - Specify Bezier control points
|
|
* count - Number of points
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
GpPath::AddBeziers(
|
|
const GpPointF* points,
|
|
INT count
|
|
)
|
|
{
|
|
// Number of points must be 3 * N + 1
|
|
if ((!points) || (count < 4) || (count % 3 != 1))
|
|
{
|
|
return InvalidParameter;
|
|
}
|
|
|
|
// Check if the first point is the same as the last point.
|
|
INT firstType;
|
|
INT origCount = GetPointCount();
|
|
|
|
if(!IsSubpathActive)
|
|
{
|
|
SubpathCount++; // Starting a new subpath.
|
|
firstType = PathPointTypeStart;
|
|
}
|
|
else
|
|
{
|
|
if (origCount > 0)
|
|
{
|
|
GpPointF lastPt = Points.Last();
|
|
if ((REALABS(points[0].X - lastPt.X) < REAL_EPSILON) &&
|
|
(REALABS(points[0].Y - lastPt.Y) < REAL_EPSILON))
|
|
{
|
|
firstType = -1; // Indicating no copying of the first point.
|
|
points++;
|
|
count--;
|
|
}
|
|
else
|
|
{
|
|
firstType = PathPointTypeLine;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SubpathCount++;
|
|
firstType = PathPointTypeStart;
|
|
}
|
|
}
|
|
|
|
// Resize Points and Types
|
|
GpPointF* pointBuf = Points.AddMultiple(count);
|
|
BYTE* typeBuf = Types.AddMultiple(count);
|
|
|
|
if(pointBuf == NULL || typeBuf == NULL)
|
|
{
|
|
// Resize the original size.
|
|
|
|
Points.SetCount(origCount);
|
|
Types.SetCount(origCount);
|
|
|
|
return OutOfMemory;
|
|
}
|
|
|
|
GpMemcpy(pointBuf, points, count * sizeof(GpPointF));
|
|
GpMemset(typeBuf, PathPointTypeBezier, count);
|
|
|
|
if(firstType == PathPointTypeStart)
|
|
typeBuf[0] = PathPointTypeStart;
|
|
else if(firstType == PathPointTypeLine)
|
|
typeBuf[0] = PathPointTypeLine;
|
|
|
|
IsSubpathActive = TRUE;
|
|
HasBezier = TRUE;
|
|
|
|
InvalidateCache();
|
|
UpdateUid();
|
|
|
|
return Ok;
|
|
}
|
|
|
|
GpStatus
|
|
GpPath::AddBezier(
|
|
const GpPointF& pt1,
|
|
const GpPointF& pt2,
|
|
const GpPointF& pt3,
|
|
const GpPointF& pt4
|
|
)
|
|
{
|
|
GpPointF points[4];
|
|
|
|
points[0] = pt1;
|
|
points[1] = pt2;
|
|
points[2] = pt3;
|
|
points[3] = pt4;
|
|
|
|
return AddBeziers(points, 4);
|
|
}
|
|
|
|
GpStatus
|
|
GpPath::AddBezier(
|
|
REAL x1, REAL y1,
|
|
REAL x2, REAL y2,
|
|
REAL x3, REAL y3,
|
|
REAL x4, REAL y4
|
|
)
|
|
{
|
|
GpPointF points[4];
|
|
|
|
points[0].X = x1;
|
|
points[0].Y = y1;
|
|
points[1].X = x2;
|
|
points[1].Y = y2;
|
|
points[2].X = x3;
|
|
points[2].Y = y3;
|
|
points[3].X = x4;
|
|
points[3].Y = y4;
|
|
|
|
return AddBeziers(points, 4);
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Add a path to the current path object.
|
|
* When connect is TRUE, this combine the end point of the current
|
|
* path and the start point of the given path if both paths are
|
|
* open.
|
|
* If either path is closed, the two paths will not be connected
|
|
* even if connect is set to TRUE.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] points - Specify a subpath points
|
|
* [IN] types - Specify a subpath control types.
|
|
* [IN] count - Number of points
|
|
* [IN] connect - TRUE if two open paths needs to be connected.
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* 02/09/2000 ikkof
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
GpPath::AddPath(
|
|
const GpPointF* points,
|
|
const BYTE* types,
|
|
INT count,
|
|
BOOL connect
|
|
)
|
|
{
|
|
GpStatus status = Ok;
|
|
|
|
if(points == NULL || types == NULL || count <= 0)
|
|
{
|
|
return InvalidParameter;
|
|
}
|
|
|
|
INT count1 = GetPointCount();
|
|
INT count2 = count;
|
|
const GpPointF* points2 = points;
|
|
const BYTE* types2 = types;
|
|
|
|
INT totalCount = count1 + count2;
|
|
BOOL forward1 = TRUE, forward2 = TRUE;
|
|
|
|
status = Points.ReserveSpace(count2);
|
|
|
|
if(status != Ok)
|
|
{
|
|
return status;
|
|
}
|
|
|
|
status = Types.ReserveSpace(count2);
|
|
|
|
if(status != Ok)
|
|
{
|
|
return status;
|
|
}
|
|
|
|
GpPointF* outPoints = Points.GetDataBuffer();
|
|
BYTE* outTypes = Types.GetDataBuffer();
|
|
const GpPointF* points1 = outPoints;
|
|
const BYTE* types1 = outTypes;
|
|
|
|
totalCount = CombinePaths(
|
|
totalCount,
|
|
outPoints,
|
|
outTypes,
|
|
count1,
|
|
points1,
|
|
types1,
|
|
forward1,
|
|
count2,
|
|
points2,
|
|
types2,
|
|
forward2,
|
|
connect
|
|
);
|
|
|
|
if( (totalCount >= count1) &&
|
|
ValidatePathTypes(outTypes, totalCount, &SubpathCount, &HasBezier))
|
|
{
|
|
count2 = totalCount - count1;
|
|
Points.AdjustCount(count2);
|
|
Types.AdjustCount(count2);
|
|
|
|
// Turn on the active subpath so that we can connect lines to this
|
|
// path.
|
|
// In order to not connect, call CloseFigure after adding.
|
|
|
|
IsSubpathActive = !IsClosedType(Types[Types.GetCount()-1]);
|
|
|
|
InvalidateCache();
|
|
UpdateUid();
|
|
|
|
return Ok;
|
|
}
|
|
else
|
|
{
|
|
return InvalidParameter;
|
|
}
|
|
}
|
|
|
|
GpStatus
|
|
GpPath::AddPath(const GpPath* path, BOOL connect)
|
|
{
|
|
if(!path)
|
|
{
|
|
return InvalidParameter;
|
|
}
|
|
|
|
INT count2 = path->GetPointCount();
|
|
const GpPointF* points2 = path->GetPathPoints();
|
|
const BYTE* types2 = path->GetPathTypes();
|
|
|
|
return AddPath(points2, types2, count2, connect);
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Reverse the direction of the path.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* NONE
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* 02/09/2000 ikkof
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
GpPath::Reverse()
|
|
{
|
|
if(!IsValid())
|
|
return InvalidParameter;
|
|
|
|
INT count = GetPointCount();
|
|
GpPointF* points = Points.GetDataBuffer();
|
|
BYTE* types = Types.GetDataBuffer();
|
|
|
|
GpStatus status = Ok;
|
|
|
|
if(count > 1)
|
|
status = ::ReversePath(count, points, types);
|
|
UpdateUid();
|
|
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpPath::GetLastPoint(GpPointF* lastPoint)
|
|
{
|
|
INT count = GetPointCount();
|
|
if(count <= 0 || lastPoint == NULL)
|
|
return InvalidParameter;
|
|
|
|
GpPointF* points = Points.GetDataBuffer();
|
|
|
|
// Return the last point.
|
|
|
|
*lastPoint = points[count - 1];
|
|
|
|
return Ok;
|
|
}
|
|
|
|
GpPath*
|
|
GpPath::GetOpenPath()
|
|
{
|
|
BOOL openPath = TRUE;
|
|
return GetOpenOrClosedPath(openPath);
|
|
}
|
|
|
|
GpPath*
|
|
GpPath::GetClosedPath()
|
|
{
|
|
BOOL openPath = FALSE;
|
|
return GetOpenOrClosedPath(openPath);
|
|
}
|
|
|
|
GpPath*
|
|
GpPath::GetOpenOrClosedPath(BOOL openPath)
|
|
{
|
|
INT startIndex, endIndex;
|
|
BOOL isClosed;
|
|
const GpPointF* points = Points.GetDataBuffer();
|
|
const BYTE* types = Types.GetDataBuffer();
|
|
|
|
DpPathIterator iter(points, types, GetPointCount());
|
|
|
|
GpPath* path = new GpPath(FillMode);
|
|
|
|
if(path)
|
|
{
|
|
INT segmentCount = 0;
|
|
while(iter.NextSubpath(&startIndex, &endIndex, &isClosed))
|
|
{
|
|
if(isClosed != openPath)
|
|
{
|
|
// path->AddSubpath(points + startIndex, types + startIndex,
|
|
// endIndex - startIndex + 1);
|
|
|
|
BOOL connect = FALSE;
|
|
path->AddPath(points + startIndex, types + startIndex,
|
|
endIndex - startIndex + 1, connect);
|
|
segmentCount++;
|
|
}
|
|
}
|
|
|
|
if(segmentCount == 0)
|
|
{
|
|
delete path;
|
|
path = NULL;
|
|
}
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Add an open cardinal spline curve to the current path object
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] points - Specify the spline points
|
|
* count - Number of points
|
|
* tension - Tension parameter
|
|
* offset - Index of the first point we're interested in
|
|
* numberOfSegments - Number of curve segments
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
|
|
#define DEFAULT_TENSION 0.5
|
|
|
|
GpStatus
|
|
GpPath::AddCurve(
|
|
const GpPointF* points,
|
|
INT count,
|
|
REAL tension,
|
|
INT offset,
|
|
INT numberOfSegments
|
|
)
|
|
{
|
|
// Verify input parameters
|
|
|
|
if (points == NULL ||
|
|
count < 2 ||
|
|
offset < 0 ||
|
|
offset >= count ||
|
|
numberOfSegments < 1 ||
|
|
numberOfSegments >= count-offset)
|
|
{
|
|
return InvalidParameter;
|
|
}
|
|
|
|
// Convert spline points to Bezier control points
|
|
|
|
GpPointF* bezierPoints;
|
|
INT bezierCount;
|
|
|
|
bezierPoints = ConvertSplineToBezierPoints(
|
|
points,
|
|
count,
|
|
offset,
|
|
numberOfSegments,
|
|
tension,
|
|
&bezierCount);
|
|
|
|
if (bezierPoints == NULL)
|
|
return OutOfMemory;
|
|
|
|
// Add the resulting Bezier segments to the current path
|
|
|
|
GpStatus status;
|
|
|
|
status = AddBeziers(bezierPoints, bezierCount);
|
|
delete[] bezierPoints;
|
|
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpPath::AddCurve(
|
|
const GpPointF* points,
|
|
INT count
|
|
)
|
|
{
|
|
return AddCurve(points,
|
|
count,
|
|
DEFAULT_TENSION,
|
|
0,
|
|
count-1);
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Add a closed cardinal spline curve to the current path object
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] points - Specify the spline points
|
|
* count - Number of points
|
|
* tension - Tension parameter
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
GpPath::AddClosedCurve(
|
|
const GpPointF* points,
|
|
INT count,
|
|
REAL tension
|
|
)
|
|
{
|
|
// Verify input parameters
|
|
|
|
if (points == NULL || count <= 2)
|
|
return InvalidParameter;
|
|
|
|
// Convert spline points to Bezier control points
|
|
|
|
GpPointF* bezierPoints;
|
|
INT bezierCount;
|
|
|
|
bezierPoints = ConvertSplineToBezierPoints(
|
|
points,
|
|
count,
|
|
0,
|
|
count,
|
|
tension,
|
|
&bezierCount);
|
|
|
|
if (bezierPoints == NULL)
|
|
return OutOfMemory;
|
|
|
|
// Add the resulting Bezier segments as a closed curve
|
|
|
|
GpStatus status;
|
|
|
|
StartFigure();
|
|
status = AddBeziers(bezierPoints, bezierCount);
|
|
CloseFigure();
|
|
|
|
delete[] bezierPoints;
|
|
|
|
InvalidateCache();
|
|
UpdateUid();
|
|
|
|
return status;
|
|
}
|
|
|
|
GpStatus
|
|
GpPath::AddClosedCurve(
|
|
const GpPointF* points,
|
|
INT count
|
|
)
|
|
{
|
|
return AddClosedCurve(points, count, DEFAULT_TENSION);
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Convert cardinal spline curve points to Bezier curve control points
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] points - Array of spline curve points
|
|
* count - Number of points in the "points" array
|
|
* offset - Specify the index of the first control point in
|
|
* the "points" array that the curve should start from
|
|
* numberOfSegments - Specify the number of curve segments to draw
|
|
* tension - Specify the tension parameter
|
|
* bezierCount - Return the number of Bezier control points
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Pointer to an array of Bezier control points
|
|
* NULL if there is an error
|
|
*
|
|
* Reference:
|
|
*
|
|
* Spline Tutorial Notes
|
|
* Technical Memo No. 77
|
|
* Alvy Ray Smith
|
|
* Presented as tutorial notes at the 1983 SIGGRAPH, July 1983
|
|
* and the SIGGRAPH, July 1984
|
|
*
|
|
* Notes:
|
|
*
|
|
* Support for cardinal spline curves
|
|
*
|
|
* Cardinal splines are local interpolating splines, i.e. they
|
|
* pass through their control points and they maintain
|
|
* first-order continuity at their control points.
|
|
*
|
|
* a cardinal spline is specified by three parameters:
|
|
* a set of control points P1, ..., Pn
|
|
* tension parameter a
|
|
* close flag
|
|
*
|
|
* If n is 1, then the spline degenerates into a single point P1.
|
|
* If n > 1 and the close flag is false, the spline consists of
|
|
* n-1 cubic curve segments. The first curve segment starts from
|
|
* P1 and ends at P2. The last segment starts at Pn-1 and ends at Pn.
|
|
*
|
|
* The cubic curve segment from Pi to Pi+1 is determined by
|
|
* 4 control points:
|
|
* Pi-1 = (xi-1, yi-1)
|
|
* Pi = (xi, yi)
|
|
* Pi+1 = (xi+1, yi+1)
|
|
* Pi+2 = (xi+2, yi+2)
|
|
*
|
|
* The parametric equation is defined as:
|
|
*
|
|
* [ X(t) Y(t) ] = [t^3 t^2 t 1] * M * [ xi-1 yi-1 ]
|
|
* [ xi yi ]
|
|
* [ xi+1 yi+1 ]
|
|
* [ xi+2 yi+2 ]
|
|
*
|
|
* where t ranges from 0 to 1 and M is a 4x4 matrix satisfying
|
|
* the following constraints:
|
|
*
|
|
* X(0) = xi interpolating through control points
|
|
* X(1) = xi+1
|
|
* X'(0) = a(xi+1 - xi-1) first-order continuity
|
|
* X'(1) = a(xi+2 - xi)
|
|
*
|
|
* In the case of segments from P1 to P2 and from Pn-1 to Pn,
|
|
* we replicate the first and last control points, i.e. we
|
|
* define P0 = P1 and Pn+1 = Pn.
|
|
*
|
|
* If the close flag is true, we have an additional curve segment
|
|
* from Pn to Pn+1 = P1. For the segments near the beginning and
|
|
* the end of the spline, we wrap around the control points, i.e.
|
|
* P0 = Pn, Pn+1 = P1, and Pn+2 = P2.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpPointF*
|
|
GpPath::ConvertSplineToBezierPoints(
|
|
const GpPointF* points,
|
|
INT count,
|
|
INT offset,
|
|
INT numberOfSegments,
|
|
REAL tension,
|
|
INT* bezierCount
|
|
)
|
|
{
|
|
BOOL closed;
|
|
GpPointF* bezierPoints;
|
|
|
|
ASSERT(count > 1 &&
|
|
offset >= 0 &&
|
|
offset < count &&
|
|
numberOfSegments > 0 &&
|
|
numberOfSegments <= count-offset);
|
|
|
|
// Curve is closed if the number of segments is equal to
|
|
// the number of curve points
|
|
|
|
closed = (numberOfSegments == count);
|
|
|
|
// Allocate memory to hold Bezier control points
|
|
|
|
*bezierCount = numberOfSegments*3 + 1;
|
|
bezierPoints = new GpPointF[*bezierCount];
|
|
|
|
if (bezierPoints == NULL)
|
|
return NULL;
|
|
|
|
// Convert each spline segment to a Bezier segment
|
|
// resulting in 3 additional Bezier points
|
|
|
|
GpPointF buffer[4], *q;
|
|
const GpPointF* p;
|
|
REAL a3;
|
|
|
|
a3 = tension / 3;
|
|
q = bezierPoints;
|
|
*q = points[offset];
|
|
|
|
for (INT index=offset; index < offset+numberOfSegments; index++)
|
|
{
|
|
if (index > 1 && index < count-2)
|
|
p = points + (index-1);
|
|
else
|
|
{
|
|
// Points near the beginning and end of the curve
|
|
// require special attention
|
|
|
|
if (closed)
|
|
{
|
|
// If the curve is closed, make sure the control points
|
|
// wrap around the beginning and end of the array.
|
|
|
|
buffer[0] = points[(index-1+count) % count];
|
|
buffer[1] = points[index];
|
|
buffer[2] = points[(index+1) % count];
|
|
buffer[3] = points[(index+2) % count];
|
|
}
|
|
else
|
|
{
|
|
// If the curve is not closed, replicate the first
|
|
// and last point in the array.
|
|
|
|
buffer[0] = points[(index > 0) ? (index-1) : 0];
|
|
buffer[1] = points[index];
|
|
buffer[2] = points[(index+1 < count) ? (index+1) : (count-1)];
|
|
buffer[3] = points[(index+2 < count) ? (index+2) : (count-1)];
|
|
}
|
|
|
|
p = buffer;
|
|
}
|
|
|
|
q[1].X = -a3*p[0].X + p[1].X + a3*p[2].X;
|
|
q[1].Y = -a3*p[0].Y + p[1].Y + a3*p[2].Y;
|
|
q[2].X = a3*p[1].X + p[2].X - a3*p[3].X;
|
|
q[2].Y = a3*p[1].Y + p[2].Y - a3*p[3].Y;
|
|
q[3] = p[2];
|
|
|
|
q += 3;
|
|
}
|
|
|
|
return bezierPoints;
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Transform all path points by the specified matrix
|
|
*
|
|
* Arguments:
|
|
*
|
|
* matrix - Transform matrix
|
|
*
|
|
* Return Value:
|
|
*
|
|
* NONE
|
|
*
|
|
* Created:
|
|
*
|
|
* 02/08/1999 ikkof
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
VOID
|
|
GpPath::Transform(
|
|
const GpMatrix *matrix
|
|
)
|
|
{
|
|
ASSERT(IsValid());
|
|
|
|
if(matrix)
|
|
{
|
|
INT count = GetPointCount();
|
|
GpPointF* points = Points.GetDataBuffer();
|
|
|
|
matrix->Transform(points, count);
|
|
UpdateUid();
|
|
InvalidateCache();
|
|
}
|
|
}
|
|
|
|
// Debug only.
|
|
|
|
#if DBG
|
|
void DpPath::DisplayPath() {
|
|
INT size = GetPointCount();
|
|
|
|
const GpPointF *points = GetPathPoints();
|
|
const BYTE *types = GetPathTypes();
|
|
|
|
for(int i=0; i<size; i++)
|
|
{
|
|
WARNING(("points[%d].X = %ff;", i, points[i].X));
|
|
WARNING(("points[%d].Y = %ff;", i, points[i].Y));
|
|
WARNING(("types[%d] = 0x%x;", i, types[i]));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Callback to accumulate the flattened edges that the Rasterizer emits.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* VOID *context, // pointer to the resulting path.
|
|
* POINT *pointArray, // Points to a 28.4 array of size 'vertexCount'
|
|
* INT vertexCount, // number of points to add.
|
|
* BOOL lastSubpath // The last point in this array is the last point
|
|
* // in a closed subpath.
|
|
*
|
|
* Return Value:
|
|
* BOOL
|
|
*
|
|
* History:
|
|
*
|
|
* 10/25/2000 asecchia & ericvan
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
BOOL PathFlatteningCallback(
|
|
VOID *context,
|
|
POINT *pointArray, // Points to a 28.4 array of size 'vertexCount'
|
|
// Note that we may modify the contents!
|
|
INT vertexCount,
|
|
PathEnumerateTermination lastSubpath // Last point in the subpath.
|
|
)
|
|
{
|
|
GpPath *result = static_cast<GpPath*>(context);
|
|
GpPointF *pointcast = (GpPointF*)(pointArray);
|
|
|
|
INT count = vertexCount;
|
|
|
|
// Don't add the last point if it's closed because the rasterizer
|
|
// emitted it twice - once for the first point.
|
|
|
|
if(lastSubpath == PathEnumerateCloseSubpath)
|
|
{
|
|
count--;
|
|
}
|
|
|
|
for(INT i = 0; i < count; i++)
|
|
{
|
|
// Convert the point array to real in place.
|
|
|
|
pointcast[i].X = FIX4TOREAL(pointArray[i].x);
|
|
pointcast[i].Y = FIX4TOREAL(pointArray[i].y);
|
|
}
|
|
|
|
// Add all the edges to the path as lines.
|
|
|
|
GpStatus status = result->AddLines(pointcast, count);
|
|
|
|
if(status == Ok)
|
|
{
|
|
if(lastSubpath == PathEnumerateCloseSubpath)
|
|
{
|
|
// last point in a subpath - close it.
|
|
status = result->CloseFigure();
|
|
}
|
|
if(lastSubpath == PathEnumerateEndSubpath)
|
|
{
|
|
// last point in an open subpath.
|
|
result->StartFigure();
|
|
}
|
|
}
|
|
|
|
return (status == Ok);
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Flatten the path. The input matrix is the world to device transform.
|
|
* Our rasterizer will flatten at about 2/3 of a device pixel. In order
|
|
* to handle different flatness tolerances, we scale the path proportionally
|
|
* and pretend that it is bigger when we give it to the rasterizer for
|
|
* flattening. It flattens at 2/3 of a device pixel in this mocked up
|
|
* device space and we undo the flatness scale effectively redefining what
|
|
* size a device pixel is. This allows us to flatten to an arbitrary
|
|
* flatness tolerance.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] matrix - Specifies the transform
|
|
* When matrix is NULL, the identity matrix is used.
|
|
* [IN] flatness - the flattening tolerance
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status
|
|
*
|
|
* Created:
|
|
*
|
|
* 10/25/2000 asecchia & ericvan
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
GpPath::Flatten(
|
|
DynByteArray *flattenTypes,
|
|
DynPointFArray *flattenPoints,
|
|
const GpMatrix *matrix,
|
|
const REAL flatness
|
|
) const
|
|
{
|
|
// Enumerate the path.
|
|
|
|
FIXEDPOINTPATHENUMERATEFUNCTION pathEnumerationFunction = PathFlatteningCallback;
|
|
|
|
GpPath result;
|
|
|
|
// Calculate the scale - multiply by 16 for the rasterizer's 28.4 fixed
|
|
// point math. This 16x transform is implicitly reversed as we accumulate
|
|
// the points from the rasterizer in our callback function
|
|
|
|
REAL deviceFlatness = flatness/FlatnessDefault;
|
|
REAL flatnessInv = (16.0f)/deviceFlatness;
|
|
|
|
GpMatrix transform;
|
|
|
|
if(matrix)
|
|
{
|
|
transform = *matrix;
|
|
}
|
|
|
|
// apply the flatness transform so that we rasterize at an appropriately
|
|
// scaled resolution. We undo this part of the transform (not the 16x)
|
|
// at the end of this function.
|
|
|
|
transform.AppendScale(flatnessInv, flatnessInv);
|
|
|
|
|
|
// Do it.
|
|
|
|
if (!FixedPointPathEnumerate(
|
|
this,
|
|
&transform,
|
|
NULL,
|
|
PathEnumerateTypeFlatten, // don't automatically close open subpaths.
|
|
pathEnumerationFunction,
|
|
&result
|
|
))
|
|
{
|
|
return(OutOfMemory);
|
|
}
|
|
|
|
// undo the implicit flatness transform.
|
|
|
|
transform.Reset();
|
|
transform.Scale(deviceFlatness, deviceFlatness);
|
|
result.Transform(&transform);
|
|
|
|
// Copy the points over. We should be using a detach on the DynArrays
|
|
// because we're throwing the temporary one away.
|
|
|
|
flattenPoints->Reset(FALSE);
|
|
flattenTypes->Reset(FALSE);
|
|
flattenPoints->AddMultiple(result.GetPathPoints(), result.GetPointCount());
|
|
flattenTypes->AddMultiple(result.GetPathTypes(), result.GetPointCount());
|
|
|
|
return Ok;
|
|
}
|
|
|
|
GpStatus
|
|
GpPath::Flatten(
|
|
const GpMatrix *matrix,
|
|
const REAL flatness
|
|
)
|
|
{
|
|
GpStatus status = Ok;
|
|
|
|
// Only flatten if it has beziers.
|
|
|
|
if(HasBezier)
|
|
{
|
|
DynPointFArray flattenPoints;
|
|
DynByteArray flattenTypes;
|
|
|
|
status = Flatten(
|
|
&flattenTypes,
|
|
&flattenPoints,
|
|
matrix,
|
|
flatness
|
|
);
|
|
|
|
if(status==Ok)
|
|
{
|
|
// Copy the points over. We should be using a detach on the DynArrays
|
|
// because we're throwing the temporary one away.
|
|
|
|
Points.ReplaceWith(&flattenPoints);
|
|
Types.ReplaceWith(&flattenTypes);
|
|
|
|
// Update to reflect the changed state of the path.
|
|
|
|
HasBezier = FALSE;
|
|
InvalidateCache();
|
|
UpdateUid();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Flatten transforms the path even if it's already flat.
|
|
// Note: Transform(NULL) <=> Identity transform which is a NOP.
|
|
|
|
Transform(matrix);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Warp and flattens the control points and stores
|
|
* the results to the arrays of the flatten points.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] matrix - Specifies the transform
|
|
* [IN] destPoint - The destination quad.
|
|
* [IN] count - the number of the quad points (3 or 4).
|
|
* [IN] srcRect - the original rectangle to warp.
|
|
* [IN] warpMode - Perspective or Bilinear (default is Bilinear).
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status
|
|
*
|
|
* Created:
|
|
*
|
|
* 11/10/1999 ikkof
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
GpPath::WarpAndFlatten(
|
|
DynByteArray* flattenTypes,
|
|
DynPointFArray* flattenPoints,
|
|
const GpMatrix* matrix,
|
|
const GpPointF* destPoint,
|
|
INT count,
|
|
const GpRectF& srcRect,
|
|
WarpMode warpMode
|
|
)
|
|
{
|
|
GpXPath xpath(this, srcRect, destPoint, count, warpMode);
|
|
|
|
return xpath.Flatten(flattenTypes, flattenPoints, matrix);
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Warps and flattens the control points and transform itself to
|
|
* the flatten path.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] matrix - Specifies the transform
|
|
* The identity matrix is used when matrix is NULL.
|
|
* [IN] destPoint - The destination quad.
|
|
* [IN] count - the number of the quad points (3 or 4).
|
|
* [IN] srcRect - the original rectangle to warp.
|
|
* [IN] warpMode - Perspective or Bilinear (default is Bilinear).
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status
|
|
*
|
|
* Created:
|
|
*
|
|
* 11/10/1999 ikkof
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
GpPath::WarpAndFlattenSelf(
|
|
GpMatrix* matrix,
|
|
const GpPointF* destPoint,
|
|
INT count,
|
|
const GpRectF& srcRect,
|
|
WarpMode warpMode
|
|
)
|
|
{
|
|
GpMatrix identity; // Identity matrix
|
|
|
|
GpXPath xpath(this, srcRect, destPoint, count, warpMode);
|
|
|
|
const INT buffSize = 32;
|
|
BYTE typesBuffer[buffSize];
|
|
GpPointF pointsBuffer[buffSize];
|
|
|
|
DynByteArray flattenTypes(&typesBuffer[0], buffSize);
|
|
DynPointFArray flattenPoints(&pointsBuffer[0], buffSize);
|
|
|
|
if(matrix == NULL)
|
|
matrix = &identity; // Use the identity matrix
|
|
|
|
GpStatus status = xpath.Flatten(&flattenTypes, &flattenPoints, matrix);
|
|
|
|
if(status == Ok)
|
|
{
|
|
INT flattenCount = flattenPoints.GetCount();
|
|
Points.Reset(FALSE);
|
|
Types.Reset(FALSE);
|
|
Points.AddMultiple(flattenPoints.GetDataBuffer(), flattenCount);
|
|
Types.AddMultiple(flattenTypes.GetDataBuffer(), flattenCount);
|
|
HasBezier = FALSE;
|
|
|
|
UpdateUid();
|
|
InvalidateCache();
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* convert a 2 segment closed subpath emitted by the region conversion
|
|
* to a correct winding path.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] p - the path.
|
|
*
|
|
* Created:
|
|
*
|
|
* 09/21/2000 asecchia
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
struct PathBound
|
|
{
|
|
REAL xmin;
|
|
REAL ymin;
|
|
REAL xmax;
|
|
REAL ymax;
|
|
INT count;
|
|
GpPointF *points;
|
|
BYTE *types;
|
|
bool reverse;
|
|
|
|
void Init(INT c, GpPointF *p, BYTE *t)
|
|
{
|
|
reverse = false;
|
|
points = p;
|
|
types = t;
|
|
count = c;
|
|
}
|
|
};
|
|
|
|
void ComputeBoundingBox(
|
|
GpPathPointIterator &i,
|
|
PathBound *p
|
|
)
|
|
{
|
|
GpPointF *point = i.CurrentItem();
|
|
p->xmax = p->xmin = point->X;
|
|
p->ymax = p->ymin = point->Y;
|
|
|
|
while(!i.IsDone())
|
|
{
|
|
point = i.CurrentItem();
|
|
if(point->X < p->xmin) { p->xmin = point->X; }
|
|
if(point->X > p->xmax) { p->xmax = point->X; }
|
|
if(point->Y < p->ymin) { p->ymin = point->Y; }
|
|
if(point->Y > p->ymax) { p->ymax = point->Y; }
|
|
i.Next();
|
|
}
|
|
}
|
|
|
|
bool Contains(PathBound &pb1, PathBound &pb2)
|
|
{
|
|
return (
|
|
(pb1.xmin <= pb2.xmin) &&
|
|
(pb1.ymin <= pb2.ymin) &&
|
|
(pb1.xmax >= pb2.xmax) &&
|
|
(pb1.ymax >= pb2.ymax)
|
|
);
|
|
}
|
|
|
|
void ConvertRegionOutputToWinding(GpPath **p)
|
|
{
|
|
ASSERT(*p);
|
|
|
|
GpPathPointIterator iPoints(
|
|
(GpPointF*)(*p)->GetPathPoints(),
|
|
(BYTE*)(*p)->GetPathTypes(),
|
|
(*p)->GetPointCount()
|
|
);
|
|
|
|
GpSubpathIterator iSubpath(&iPoints);
|
|
|
|
GpPointF *points;
|
|
BYTE *types;
|
|
INT count;
|
|
GpPath *ret = new GpPath(FillModeWinding);
|
|
|
|
// if we're out of memory, simply give them back their path.
|
|
|
|
if(!ret) { return; }
|
|
|
|
GpPath *sub;
|
|
DynArray<PathBound> bounds;
|
|
PathBound pb;
|
|
|
|
// Iterate through all the subpaths culling information for the following
|
|
// algorithm. This is O(n) in the number of points.
|
|
// The information we need is the starting point for each subpath and
|
|
// the bounding box.
|
|
|
|
while(!iSubpath.IsDone())
|
|
{
|
|
count = -iSubpath.CurrentIndex();
|
|
points = iSubpath.CurrentItem();
|
|
types = iSubpath.CurrentType();
|
|
iSubpath.Next();
|
|
count += iSubpath.CurrentIndex();
|
|
|
|
GpPathPointIterator iSubpathPoint( points, types, count );
|
|
|
|
pb.Init(count, points, types);
|
|
ComputeBoundingBox( iSubpathPoint, &pb );
|
|
bounds.Add(pb);
|
|
}
|
|
|
|
// Double loop through all the subpaths figuring out the containment
|
|
// relationships.
|
|
// For every level of containment, flip the reverse bit.
|
|
// E.g. for a subpath that's contained by 5 other rectangles, start at
|
|
// false and apply 5x(!) !!!!!false == true which means flip this path.
|
|
// this is O(n^2) in the number of subpaths.
|
|
|
|
count = bounds.GetCount();
|
|
int i, j;
|
|
|
|
for(i=1; i<count; i++)
|
|
{
|
|
for(j=i-1; j>=0; j--)
|
|
{
|
|
if(Contains(bounds[i], bounds[j]))
|
|
{
|
|
bounds[j].reverse = !bounds[j].reverse;
|
|
continue;
|
|
}
|
|
|
|
if(Contains(bounds[j], bounds[i]))
|
|
{
|
|
bounds[i].reverse = !bounds[i].reverse;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now reverse all the subpaths that need to be reversed.
|
|
// Accumulate the results into the array.
|
|
|
|
for(i=0; i<count; i++)
|
|
{
|
|
sub = new GpPath(
|
|
bounds[i].points,
|
|
bounds[i].types,
|
|
bounds[i].count
|
|
);
|
|
|
|
if(!sub)
|
|
{
|
|
// if we failed to make our temporary path, stop and return
|
|
// what we've accumulated so far.
|
|
|
|
WARNING(("ran out of memory accumulating the path"));
|
|
break;
|
|
}
|
|
|
|
if(bounds[i].reverse)
|
|
{
|
|
sub->Reverse();
|
|
}
|
|
|
|
ret->AddPath(sub, FALSE);
|
|
|
|
delete sub;
|
|
}
|
|
|
|
delete *p;
|
|
*p = ret;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* GetWidenedPath. Returns a widened version of the path. The path is widened
|
|
* according to the pen and the result is transformed according to the input
|
|
* matrix. When flattening the path the matrix is used as the world to device
|
|
* transform and the flatness tolerance is applied.
|
|
*
|
|
* This function handles inset and outset pen alignments.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] pen - pen - specifies width for widening
|
|
* [IN] matrix - world to device transform.
|
|
* [IN] flatness - number of device pixels of error allowed for flattening
|
|
*
|
|
* Return:
|
|
*
|
|
* GpPath * - widened path. NULL on failure.
|
|
* - !!! this should return the path in an OUT parameter and
|
|
* - propagate the GpStatus correctly.
|
|
*
|
|
* Created:
|
|
*
|
|
* 09/31/2000 asecchia
|
|
* Rewrote it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpPath*
|
|
GpPath::GetWidenedPath(
|
|
const GpPen *pen,
|
|
const GpMatrix *matrix,
|
|
REAL flatness
|
|
) const
|
|
{
|
|
ASSERT(pen);
|
|
|
|
// Redefine a NULL input matrix to be Identity for the duration of this
|
|
// routine.
|
|
|
|
GpMatrix transform;
|
|
if(!matrix)
|
|
{
|
|
matrix = &transform;
|
|
}
|
|
|
|
DpPen *internalPen = NULL;
|
|
internalPen = const_cast<GpPen*>(pen)->GetDevicePen();
|
|
|
|
ASSERT(internalPen);
|
|
|
|
if (internalPen->PenAlignment != PenAlignmentInset)
|
|
{
|
|
// Use the standard widening code for non-inset or non-outset pen.
|
|
|
|
return GetWidenedPathInternal(
|
|
internalPen,
|
|
matrix,
|
|
flatness,
|
|
FALSE // standard pen
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// Do the Inset Pen.
|
|
|
|
// Our technique is as follows. See the inset pen spec in the
|
|
// gdiplus\specs directory.
|
|
// First, inset pen is defined as widening to the inside of the path
|
|
// which only has meaning for closed segments. Behaviour for open
|
|
// segments is unchanged (center pen).
|
|
// We widen the path at 2x the stroke width using a center pen.
|
|
// For round dash caps, we use a double-round or 'B' cap. We also
|
|
// mirror the compound line pattern across the spine of the path.
|
|
// Then we import the widened path as a region and clip against the
|
|
// original path converted to a region. What's left is a region
|
|
// which contains the widened inset pen. This is converted to a path
|
|
// and we're done.
|
|
|
|
// Copy the pen. Note that this will copy the *pointer* to the Brush
|
|
// but this is ok because the DpPen (insetPen) doesn't have a
|
|
// destructor and so won't attempt to free any state.
|
|
|
|
// We will need an insetPen for the closed subpath segments and a
|
|
// centerPen for the open subpath segments.
|
|
|
|
DpPen insetPen = *internalPen;
|
|
DpPen centerPen = *internalPen;
|
|
|
|
// Use a double width center pen and then clip off the outside creating
|
|
// a single width insetPen.
|
|
|
|
insetPen.Width *= 2.0f;
|
|
insetPen.PenAlignment = PenAlignmentCenter;
|
|
centerPen.PenAlignment = PenAlignmentCenter;
|
|
|
|
// Copy the compound array duplicating the compound array in reverse
|
|
// and rescaling back to [0,1] interval (i.e. mirror along the spine).
|
|
|
|
if( internalPen->CompoundCount > 0)
|
|
{
|
|
insetPen.CompoundArray = (REAL*)GpMalloc(
|
|
sizeof(REAL)*insetPen.CompoundCount*2
|
|
);
|
|
|
|
// Check the GpMalloc for out of memory.
|
|
|
|
if(insetPen.CompoundArray == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
// Copy the pen->CompoundArray and duplicate it in reverse (mirror).
|
|
// rescale to the interval [0, 1]
|
|
|
|
for(INT i=0; i<insetPen.CompoundCount; i++)
|
|
{
|
|
// copy and scale range [0, 1] to [0, 0.5]
|
|
|
|
insetPen.CompoundArray[i] = internalPen->CompoundArray[i]/2.0f;
|
|
|
|
// copy and scale range [0, 1] to [0.5, 1] reversed.
|
|
|
|
insetPen.CompoundArray[insetPen.CompoundCount*2-i-1] =
|
|
1.0f - internalPen->CompoundArray[i]/2.0f;
|
|
}
|
|
|
|
// we have double the number of entries now.
|
|
|
|
insetPen.CompoundCount *= 2;
|
|
}
|
|
|
|
// Create an iterator to step through each subpath.
|
|
|
|
GpPathPointIterator pathIterator(
|
|
(GpPointF*)GetPathPoints(),
|
|
(BYTE*)GetPathTypes(),
|
|
GetPointCount()
|
|
);
|
|
|
|
GpSubpathIterator subPathIterator(
|
|
&pathIterator
|
|
);
|
|
|
|
// Some temporary variables.
|
|
|
|
GpPointF *points;
|
|
BYTE *types;
|
|
INT subPathCount;
|
|
GpPath *widenedPath = NULL;
|
|
GpPath *subPath = NULL;
|
|
|
|
// Accumulate the widened sub paths in this returnPath.
|
|
|
|
GpPath *returnPath = new GpPath(FillModeWinding);
|
|
|
|
// loop while there are more subpaths and the returnPath is not NULL
|
|
// This implicitly checks that returnPath was allocated correctly.
|
|
|
|
while(returnPath && !subPathIterator.IsDone())
|
|
{
|
|
// Get the data for the current subpath.
|
|
|
|
points = subPathIterator.CurrentItem();
|
|
types = subPathIterator.CurrentType();
|
|
subPathCount = -subPathIterator.CurrentIndex();
|
|
subPathIterator.Next();
|
|
subPathCount += subPathIterator.CurrentIndex();
|
|
|
|
// Create a path object representing the current sub path.
|
|
|
|
subPath = new GpPath(points, types, subPathCount);
|
|
|
|
if(!subPath)
|
|
{
|
|
// failed the allocation.
|
|
|
|
delete returnPath;
|
|
returnPath = NULL;
|
|
break;
|
|
}
|
|
|
|
// Is this subpath closed?
|
|
|
|
BOOL isClosed = IsClosedType(types[subPathCount-1]);
|
|
|
|
// Widen the subPath with the inset pen for closed and
|
|
// center pen for open.
|
|
|
|
widenedPath = subPath->GetWidenedPathInternal(
|
|
(isClosed) ? &insetPen : ¢erPen,
|
|
matrix,
|
|
flatness,
|
|
isClosed // Inset/Outset pen?
|
|
);
|
|
|
|
// don't need the subPath anymore - we have the widened version.
|
|
|
|
delete subPath;
|
|
subPath = NULL;
|
|
|
|
// Check if the widener succeeded.
|
|
|
|
if(!widenedPath || !widenedPath->IsValid())
|
|
{
|
|
delete widenedPath;
|
|
widenedPath = NULL;
|
|
delete returnPath;
|
|
returnPath = NULL;
|
|
break;
|
|
}
|
|
|
|
if(isClosed)
|
|
{
|
|
// Region to path.
|
|
|
|
// The widenedPath has already been transformed by the widener
|
|
// according to the matrix. Use the identity to convert the
|
|
// widenedPath to a region, but use the matrix to transform the
|
|
// (still untransformed) original matrix to a region.
|
|
|
|
GpMatrix identityMatrix;
|
|
const GpMatrix *scaleMatrix = &identityMatrix;
|
|
|
|
if(matrix)
|
|
{
|
|
scaleMatrix = matrix;
|
|
}
|
|
|
|
DpRegion srcRgn(widenedPath, &identityMatrix);
|
|
DpRegion clipRgn((DpPath*)(this), scaleMatrix);// const and type cast.
|
|
|
|
// Clip the region
|
|
|
|
GpStatus clip = Ok;
|
|
|
|
if(internalPen->PenAlignment == PenAlignmentInset)
|
|
{
|
|
// Inset pen is an And operation.
|
|
|
|
clip = srcRgn.And(&clipRgn);
|
|
}
|
|
|
|
GpPath *clippedPath;
|
|
|
|
if(clip == Ok)
|
|
{
|
|
clippedPath = new GpPath(&srcRgn);
|
|
|
|
if(!clippedPath)
|
|
{
|
|
delete widenedPath;
|
|
widenedPath = NULL;
|
|
delete returnPath;
|
|
returnPath = NULL;
|
|
break;
|
|
}
|
|
|
|
ConvertRegionOutputToWinding(&clippedPath);
|
|
|
|
// Accumulate the current subpath that we've just clipped
|
|
// for inset/outset into the final result.
|
|
|
|
returnPath->AddPath(clippedPath, FALSE);
|
|
|
|
delete clippedPath;
|
|
clippedPath = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Accumulate the center pen widened path for the open
|
|
// subpath segment.
|
|
|
|
returnPath->AddPath(widenedPath, FALSE);
|
|
}
|
|
|
|
delete widenedPath;
|
|
widenedPath = NULL;
|
|
}
|
|
|
|
// clean up.
|
|
|
|
if(internalPen->CompoundCount > 0)
|
|
{
|
|
// we allocated a new piece of memory, throw it away.
|
|
// Make sure we're not trying to throw away the original pen
|
|
// CompoundArray - only free the temporary one if we created it.
|
|
|
|
ASSERT(insetPen.CompoundArray != internalPen->CompoundArray);
|
|
GpFree(insetPen.CompoundArray);
|
|
insetPen.CompoundArray = NULL;
|
|
}
|
|
|
|
return returnPath;
|
|
}
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* The sweep phase of a mark-sweep path point deletion algorithm
|
|
* This will delete all points marked with PathPointTypeInternalUse.
|
|
*
|
|
* If it deletes a start marker, it'll make the next valid point a start
|
|
* point.
|
|
*
|
|
* NOTE:
|
|
* If the algorithm encounters a closed subpath marker it will simply
|
|
* delete it. Because this algorithm is used for trimming the ends of
|
|
* open subpath segments (during endcapping), this is the desired behaviour,
|
|
* but may not be strictly correct for other uses.
|
|
*
|
|
* The points to be deleted are marked by oring in the
|
|
* PathPointTypeInternalUse flag. This flag is used by the widener as an
|
|
* internal flag and as a deletion mask for this code. These two usages
|
|
* do not (and should not) overlap.
|
|
*
|
|
* Created:
|
|
*
|
|
* 10/07/2000 asecchia
|
|
* created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
VOID GpPath::EraseMarkedSegments()
|
|
{
|
|
// Get pointers to the source buffers.
|
|
|
|
GpPointF *dstPoints = Points.GetDataBuffer();
|
|
BYTE *dstTypes = Types.GetDataBuffer();
|
|
INT count = Points.GetCount();
|
|
|
|
|
|
INT delete_count = 0;
|
|
INT i=0;
|
|
GpPointF *srcPoints = dstPoints;
|
|
BYTE *srcTypes = dstTypes;
|
|
|
|
bool deleted_start_marker = false;
|
|
|
|
while(i<count)
|
|
{
|
|
// Skip all the points marked for deletion.
|
|
|
|
if((*srcTypes) & PathPointTypeInternalUse)
|
|
{
|
|
delete_count++;
|
|
|
|
// if we ever encounter a start marker, keep track of that fact.
|
|
|
|
deleted_start_marker |=
|
|
(((*srcTypes) & PathPointTypePathTypeMask) == PathPointTypeStart);
|
|
}
|
|
else
|
|
{
|
|
// If we have deleted some stuff, move the data up.
|
|
|
|
if(srcTypes!=dstTypes)
|
|
{
|
|
*dstPoints = *srcPoints;
|
|
*dstTypes = *srcTypes;
|
|
|
|
// if we deleted a start marker in the last deletion run,
|
|
// make the next non-deleted point a start marker.
|
|
// Note: if the whole subpath is marked for deletion and
|
|
// it's the last subpath, then we won't do this code because
|
|
// we'll terminate the while loop first. This protects against
|
|
// overwriting our buffer.
|
|
|
|
if(deleted_start_marker)
|
|
{
|
|
*dstTypes &= ~PathPointTypePathTypeMask;
|
|
*dstTypes |= PathPointTypeStart;
|
|
}
|
|
}
|
|
|
|
deleted_start_marker = false;
|
|
|
|
// increment to the next element.
|
|
|
|
dstPoints++;
|
|
dstTypes++;
|
|
}
|
|
|
|
// increment these every iteration through the loop.
|
|
|
|
srcTypes++;
|
|
srcPoints++;
|
|
i++;
|
|
}
|
|
|
|
// update the DynArrays so that they reflect the new (deleted) count.
|
|
|
|
Points.AdjustCount(-delete_count);
|
|
Types.AdjustCount(-delete_count);
|
|
|
|
InvalidateCache();
|
|
UpdateUid();
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Returns a widened version of the path. This routine does not handle
|
|
* inset or outset pen alignments. The input insetPen parameter specifies
|
|
* if we should double widen the path in preparation for inset or outset
|
|
* pen modes. This has impact on dash caps and dash length.
|
|
*
|
|
* Return
|
|
*
|
|
* GpPath - the widened path. NULL if this routine fails.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] pen - pen - specifies width for widening
|
|
* [IN] matrix - world to device transform.
|
|
* [IN] flatness - number of device pixels of error allowed for flattening
|
|
* [IN] insetPen - flag specifying if inset pen is being used.
|
|
*
|
|
* Return:
|
|
*
|
|
* GpPath * - widened path. NULL on failure.
|
|
* - !!! this should return the path in an OUT parameter and
|
|
* - propagate the GpStatus correctly.
|
|
*
|
|
* Created:
|
|
*
|
|
* 10/05/2000 asecchia
|
|
* rewrote it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpPath*
|
|
GpPath::GetWidenedPathInternal(
|
|
const DpPen *pen,
|
|
const GpMatrix *matrix,
|
|
REAL flatness,
|
|
BOOL insetPen
|
|
) const
|
|
{
|
|
ASSERT(pen);
|
|
ASSERT(matrix);
|
|
ASSERT(pen->PenAlignment!=PenAlignmentInset);
|
|
|
|
GpStatus status = Ok;
|
|
|
|
GpMatrix invMatrix(*matrix);
|
|
if(Ok != invMatrix.Invert())
|
|
{
|
|
ONCE(WARNING(("GetWidenedPath: failed to invert the matrix")));
|
|
return NULL;
|
|
}
|
|
|
|
// This is a const function. We cannot modify 'this' so we clone
|
|
// the path in order to flatten it.
|
|
|
|
GpPath* path = this->Clone();
|
|
if(path == NULL) { return NULL; }
|
|
if(Ok != (status = path->Flatten(matrix, flatness)))
|
|
{
|
|
ONCE(WARNING(("GetWidenedPath: failed to flatten the path (%x)", status)));
|
|
return NULL;
|
|
}
|
|
|
|
// Undo the Flatten matrix transform so that we can widen in world space.
|
|
// This is required so that we correctly compute the device resolution
|
|
// minimum pen width for nominal pens.
|
|
|
|
path->Transform(&invMatrix);
|
|
|
|
// Do all the path decorations before widening. This is to ensure that
|
|
// the decorations have all of the original path information to operate
|
|
// on --- the widening/decoration process is lossy so they have to be
|
|
// performed in the right order.
|
|
|
|
// First apply the end caps. This decoration must be applied before
|
|
// dashing the path.
|
|
// Need to loop through all the subpaths, apply the end caps and
|
|
// fix up the path segments so they don't exit the cap incorrectly.
|
|
// put all the caps in a path for later use. We will apply these caps
|
|
// when we're done widening.
|
|
|
|
GpPath *caps = NULL;
|
|
|
|
if(GpEndCapCreator::PenNeedsEndCapCreator(pen))
|
|
{
|
|
// Create an instance of the GpEndCapCreator which will create
|
|
// our endcap aggregate path.
|
|
|
|
GpEndCapCreator ecc(
|
|
path,
|
|
const_cast<DpPen*>(pen),
|
|
matrix,
|
|
0.0f, 0.0f,
|
|
TRUE
|
|
);
|
|
|
|
// CreateCapPath will mark the points in the path for deletion if
|
|
// it's necessary to trim the path to fit the cap.
|
|
|
|
status = ecc.CreateCapPath(&caps);
|
|
if(status != Ok)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
// Remove the points marked for deletion in the cap trimming step.
|
|
|
|
path->EraseMarkedSegments();
|
|
}
|
|
|
|
// Apply the dash decorations. Note that this will bounce on an empty path.
|
|
|
|
GpPath* dashPath = NULL;
|
|
|
|
if( (pen) &&
|
|
(pen->DashStyle != DashStyleSolid) &&
|
|
(path->GetPointCount() > 0)
|
|
)
|
|
{
|
|
// the width is artificially expanded by 2 if the pen is inset.
|
|
// we need to factor this into the dash length and scale by 0.5.
|
|
|
|
dashPath = path->CreateDashedPath(
|
|
pen,
|
|
matrix,
|
|
0.0f, // not used
|
|
0.0f, // not used
|
|
(insetPen) ? 0.5f : 1.0f,
|
|
TRUE
|
|
);
|
|
|
|
// If we successfully got a dashed version of *path, delete
|
|
// the old one and return the new one.
|
|
|
|
if(dashPath)
|
|
{
|
|
delete path;
|
|
path = dashPath;
|
|
}
|
|
}
|
|
|
|
// Only do the widening if we have some points left in our
|
|
// path after trimming
|
|
|
|
if(path->GetPointCount() > 0)
|
|
{
|
|
// Create a widener object. Note that if path has no points left, this
|
|
// will bounce immediately with an invalid widener.
|
|
|
|
GpPathWidener widener(
|
|
path,
|
|
pen,
|
|
matrix,
|
|
0.0f, // not used
|
|
0.0f, // not used
|
|
TRUE, // not used
|
|
insetPen
|
|
);
|
|
|
|
// We're done with this now.
|
|
|
|
delete path;
|
|
path = NULL;
|
|
|
|
// Check if we have a valid Widener object.
|
|
|
|
if(!widener.IsValid())
|
|
{
|
|
status = OutOfMemory;
|
|
}
|
|
|
|
// Get the widened path.
|
|
|
|
if(status == Ok)
|
|
{
|
|
status = widener.Widen(&path);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
delete path;
|
|
path = caps;
|
|
caps = NULL;
|
|
}
|
|
|
|
// paranoid checking the return from the widener.
|
|
|
|
if((status == Ok) && (path != NULL))
|
|
{
|
|
// Add the endcaps to the widened path. AddPath will bounce a NULL
|
|
// caps pointer with InvalidParameter. For our purposes that is
|
|
// considered correctly handled and we continue.
|
|
|
|
path->AddPath(caps, FALSE);
|
|
|
|
if(path->IsValid())
|
|
{
|
|
// Transform into the requested destination device space.
|
|
|
|
path->Transform(matrix);
|
|
}
|
|
}
|
|
|
|
// Delete the caps before returning. If we had caps, we've copied them
|
|
// into path, otherwise caps is NULL. Or we failed to widen. Either way
|
|
// we must not leak memory.
|
|
|
|
delete caps;
|
|
caps = NULL;
|
|
|
|
return path;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* This widenes itself.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] pen - the pen.
|
|
* [IN] matrix - Specifies the transform
|
|
* [IN] dpiX - the X-resolution.
|
|
* [IN] dpiY - the Y-resolution.
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Ok if successfull.
|
|
*
|
|
* Created:
|
|
*
|
|
* 09/27/1999 ikkof
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
GpPath::Widen(
|
|
GpPen *pen,
|
|
GpMatrix *matrix,
|
|
REAL flatness
|
|
)
|
|
{
|
|
if(pen==NULL)
|
|
{
|
|
return InvalidParameter;
|
|
}
|
|
|
|
GpMatrix transform; // Identity matrix
|
|
|
|
if(matrix)
|
|
{
|
|
transform = *matrix;
|
|
}
|
|
|
|
GpPath* widenedPath = GetWidenedPath(
|
|
pen,
|
|
&transform,
|
|
flatness
|
|
);
|
|
|
|
if(widenedPath)
|
|
{
|
|
Points.Reset(FALSE);
|
|
Types.Reset(FALSE);
|
|
INT count = widenedPath->GetPointCount();
|
|
Points.AddMultiple(widenedPath->Points.GetDataBuffer(), count);
|
|
Types.AddMultiple(widenedPath->Types.GetDataBuffer(), count);
|
|
SubpathCount = widenedPath->SubpathCount;
|
|
HasBezier = widenedPath->HasBezier;
|
|
Flags = widenedPath->Flags;
|
|
FillMode = FillModeWinding;
|
|
delete widenedPath;
|
|
|
|
GpStatus status = Ok;
|
|
|
|
InvalidateCache();
|
|
UpdateUid();
|
|
|
|
return status;
|
|
}
|
|
else
|
|
{
|
|
return OutOfMemory;
|
|
}
|
|
}
|
|
|
|
// Get the flattened path.
|
|
|
|
const DpPath *
|
|
GpPath::GetFlattenedPath(
|
|
const GpMatrix* matrix,
|
|
DpEnumerationType type,
|
|
const DpPen* pen
|
|
) const
|
|
{
|
|
GpPath* flattenedPath = NULL;
|
|
|
|
if(type == Flattened)
|
|
{
|
|
flattenedPath = Clone();
|
|
|
|
if(flattenedPath)
|
|
{
|
|
GpStatus status = flattenedPath->Flatten(matrix);
|
|
|
|
if(Ok != status)
|
|
{
|
|
// Out of memory or flatten returned some other error,
|
|
// however we can't return a status code from this routine.
|
|
|
|
delete flattenedPath;
|
|
flattenedPath = NULL;
|
|
}
|
|
}
|
|
}
|
|
else if(type == Widened)
|
|
{
|
|
flattenedPath = GetWidenedPath(
|
|
GpPen::GetPen(pen),
|
|
matrix,
|
|
FlatnessDefault
|
|
);
|
|
}
|
|
|
|
return flattenedPath;
|
|
}
|
|
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Checks if the given point in World coordinate is inside of
|
|
* the path. The matrix is used to render path in specific resolution.
|
|
* Usually, Graphics's World to Device matrix is used. If matrix is NULL,
|
|
* the identity matrix is used.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] point - A test point in World coordinate
|
|
* [OUT] isVisible - TRUE is the test point is inside of the path.
|
|
* [IN] matrix - A matrix to render path. Identity is used if NULL.
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Ok if successfull.
|
|
*
|
|
* Created:
|
|
*
|
|
* 10/05/1999 ikkof
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
GpPath::IsVisible(
|
|
GpPointF* point,
|
|
BOOL* isVisible,
|
|
GpMatrix* matrix)
|
|
{
|
|
GpMatrix m;
|
|
|
|
if(matrix)
|
|
m = *matrix;
|
|
|
|
GpRegion rgn(this);
|
|
|
|
if(rgn.IsValid())
|
|
return rgn.IsVisible(point, &m, isVisible);
|
|
|
|
*isVisible = FALSE;
|
|
return GenericError;
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Checks if the given point in World coordinate is inside of
|
|
* the path outline. The matrix is used to render path in specific resolution.
|
|
* Usually, Graphics's World to Device matrix is used. If matrix is NULL,
|
|
* the identity matrix is used.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] point - A test point in World coordinate
|
|
* [OUT] isVisible - TRUE is the test point is inside of the path.
|
|
* [IN] pen - A pen to draw the outline.
|
|
* [IN] matrix - A matrix to render path. Identity is used if NULL.
|
|
* [IN] dpiX - x-resolution of the device.
|
|
* [IN] dpiY - y-resolution of the device.
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Ok if successfull.
|
|
*
|
|
* Created:
|
|
*
|
|
* 10/05/1999 ikkof
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
GpPath::IsOutlineVisible(
|
|
GpPointF* point,
|
|
BOOL* isVisible,
|
|
GpPen* pen,
|
|
GpMatrix* matrix,
|
|
REAL dpiX,
|
|
REAL dpiY
|
|
)
|
|
{
|
|
if ((dpiX <= 0) || (dpiY <= 0))
|
|
{
|
|
dpiX = Globals::DesktopDpiX;
|
|
dpiY = Globals::DesktopDpiY;
|
|
}
|
|
|
|
// If the given pen is not a solid line,
|
|
// clone the pen and set its dash type to Solid.
|
|
// We do line hit testing in solid lines.
|
|
|
|
GpPen* pen1 = NULL;
|
|
if(pen && pen->GetDashStyle() != DashStyleSolid)
|
|
{
|
|
pen1 = pen->Clone();
|
|
if(pen1)
|
|
pen1->SetDashStyle(DashStyleSolid);
|
|
}
|
|
else
|
|
pen1 = pen;
|
|
|
|
if(pen1 == NULL)
|
|
{
|
|
*isVisible = FALSE;
|
|
return Ok;
|
|
}
|
|
|
|
// Create a widened path in the transformed coordinates.
|
|
|
|
GpPath* widenedPath = GetWidenedPath(
|
|
pen1,
|
|
matrix,
|
|
FlatnessDefault
|
|
);
|
|
|
|
if(pen1 != pen)
|
|
delete pen1;
|
|
|
|
GpStatus status = Ok;
|
|
|
|
if(widenedPath)
|
|
{
|
|
// Since the widened path is already transformed, we have to
|
|
// transform the given point.
|
|
|
|
GpPointF transformedPoint = *point;
|
|
if(matrix)
|
|
matrix->Transform(&transformedPoint);
|
|
|
|
status = widenedPath->IsVisible(&transformedPoint, isVisible, NULL);
|
|
delete widenedPath;
|
|
}
|
|
else
|
|
{
|
|
*isVisible = FALSE;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
// Is the current dash segment a line segment?
|
|
// If false it's a space segment.
|
|
|
|
inline bool IsLineSegment(GpIterator<REAL> &dashIt)
|
|
{
|
|
// line segment starts on even indices.
|
|
|
|
return bool( !(dashIt.CurrentIndex() & 0x1) );
|
|
}
|
|
|
|
// Emit a line segment if it is not degenerate.
|
|
// Return true if emitted, false if degenerate
|
|
|
|
bool EmitLineSegment(
|
|
GpPathPointIterator &dstPath,
|
|
GpPointF p0,
|
|
GpPointF p1,
|
|
bool isLineStart
|
|
)
|
|
{
|
|
GpPointF *currentPoint;
|
|
BYTE *currentType;
|
|
|
|
if( (REALABS(p0.X-p1.X) < REAL_EPSILON) &&
|
|
(REALABS(p0.Y-p1.Y) < REAL_EPSILON) )
|
|
{
|
|
// Don't emit a line segment if it has zero length.
|
|
return false;
|
|
}
|
|
|
|
// If the last emitted line ends at the same point that this next
|
|
// one starts, we don't need a new start record.
|
|
|
|
if(isLineStart)
|
|
{
|
|
// start point.
|
|
currentPoint = dstPath.CurrentItem();
|
|
*currentPoint = p0;
|
|
currentType = dstPath.CurrentType();
|
|
*currentType = PathPointTypeStart | PathPointTypeDashMode;
|
|
|
|
dstPath.Next();
|
|
}
|
|
|
|
// end point.
|
|
currentPoint = dstPath.CurrentItem();
|
|
*currentPoint = p1;
|
|
currentType = dstPath.CurrentType();
|
|
*currentType = PathPointTypeLine | PathPointTypeDashMode;
|
|
|
|
dstPath.Next();
|
|
|
|
return true;
|
|
}
|
|
|
|
INT
|
|
getDashData(
|
|
BYTE* newTypes,
|
|
GpPointF* newPts,
|
|
INT estimateCount,
|
|
REAL penWidth,
|
|
REAL dashOffset,
|
|
const REAL* dashArray,
|
|
INT dashCount,
|
|
const BYTE* types,
|
|
const GpPointF* points,
|
|
INT numOfPoints,
|
|
BOOL isClosed,
|
|
const REAL* distances
|
|
)
|
|
{
|
|
ASSERT(estimateCount >= numOfPoints);
|
|
ASSERT(types && points);
|
|
|
|
// Code assumes first point != last point for closed paths. If first
|
|
// point == last point, decrease point count
|
|
if (isClosed && numOfPoints &&
|
|
points[0].X == points[numOfPoints-1].X &&
|
|
points[0].Y == points[numOfPoints-1].Y)
|
|
{
|
|
numOfPoints--;
|
|
}
|
|
|
|
|
|
if(!newTypes || !newPts)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Make the iterators.
|
|
|
|
GpArrayIterator<GpPointF> pathIterator(
|
|
const_cast<GpPointF*>(points),
|
|
numOfPoints
|
|
);
|
|
GpArrayIterator<REAL> pathBaseDistance(
|
|
const_cast<REAL*>(distances),
|
|
numOfPoints
|
|
);
|
|
|
|
GpPathPointIterator dstPath(newPts, newTypes, estimateCount);
|
|
|
|
GpArrayIterator<REAL> dashBaseIterator(
|
|
const_cast<REAL*>(dashArray),
|
|
dashCount
|
|
);
|
|
|
|
// Compute the length of the dash
|
|
|
|
REAL dashLength = 0.0f;
|
|
while(!dashBaseIterator.IsDone())
|
|
{
|
|
dashLength += *(dashBaseIterator.CurrentItem());
|
|
dashBaseIterator.Next();
|
|
}
|
|
ASSERT(dashLength > -REAL_EPSILON);
|
|
|
|
|
|
// Do the offset initialization.
|
|
|
|
dashBaseIterator.SeekFirst();
|
|
|
|
REAL distance = GpModF(dashOffset, dashLength);
|
|
REAL delta;
|
|
|
|
// Compute the position in the dash array corresponding to the
|
|
// specified offset.
|
|
|
|
while(!dashBaseIterator.IsDone())
|
|
{
|
|
delta = *(dashBaseIterator.CurrentItem());
|
|
if(distance < delta)
|
|
{
|
|
// set to the remaining piece of the dash.
|
|
distance = delta-distance;
|
|
break;
|
|
}
|
|
distance -= delta;
|
|
dashBaseIterator.Next();
|
|
}
|
|
|
|
// The dashIterator is now set to point to the correct
|
|
// dash for the first segment.
|
|
|
|
// These are circular arrays to repeat the dash pattern.
|
|
|
|
GpCircularIterator<REAL> dashIterator(&dashBaseIterator);
|
|
|
|
|
|
// This is the distance into the current dash segment that we're going
|
|
// to start at.
|
|
|
|
REAL currentDashLength = distance;
|
|
REAL currentSegmentLength;
|
|
|
|
GpPointF p0, p1;
|
|
GpVector2D sD; // segment direction.
|
|
|
|
// Used to track if we need to emit a segment start record.
|
|
|
|
bool emittedPathSegment = false;
|
|
|
|
if(isClosed)
|
|
{
|
|
// set up everything off the last item and then point to
|
|
// the first item to start the process.
|
|
|
|
pathBaseDistance.SeekFirst();
|
|
|
|
pathIterator.SeekLast();
|
|
p0 = *(pathIterator.CurrentItem());
|
|
|
|
pathIterator.SeekFirst();
|
|
p1 = *(pathIterator.CurrentItem());
|
|
|
|
// get the distance between the first and last points.
|
|
|
|
GpVector2D seg = p1-p0;
|
|
currentSegmentLength = seg.Norm();
|
|
}
|
|
else
|
|
{
|
|
// Get the first point in the array.
|
|
|
|
p0 = *(pathIterator.CurrentItem());
|
|
|
|
// already initialized to the first point, start on the next one.
|
|
|
|
pathIterator.Next();
|
|
pathBaseDistance.Next();
|
|
|
|
// distance between point n and point n+1 is stored in
|
|
// distance[n+1]. distance[0] is the distance between the first
|
|
// and last points.
|
|
|
|
currentSegmentLength = *(pathBaseDistance.CurrentItem());
|
|
}
|
|
|
|
// reference the distances as circular so that we can simplify the
|
|
// internal algorithm by not having to check when we query for the
|
|
// next segment in the last iteration of the loop.
|
|
|
|
GpCircularIterator<REAL> pathDistance(&pathBaseDistance);
|
|
|
|
while( !pathIterator.IsDone() )
|
|
{
|
|
if(currentDashLength > currentSegmentLength)
|
|
{
|
|
// The remaining dash segment length is longer than the remaining
|
|
// path segment length.
|
|
// Finish the path segment.
|
|
|
|
// Note that we've moved along the dash segment.
|
|
|
|
currentDashLength -= currentSegmentLength;
|
|
|
|
p1 = *(pathIterator.CurrentItem());
|
|
|
|
if(IsLineSegment(dashIterator))
|
|
{
|
|
// emit a line. Add the start record only if we didn't just
|
|
// emit a path segment. If we're emitting a series of path
|
|
// segments to complete one dash, we can't have any start
|
|
// records inbetween the segments otherwise we'll end up with
|
|
// spurious endcaps in the middle of the lines.
|
|
|
|
emittedPathSegment = EmitLineSegment(
|
|
dstPath,
|
|
p0, p1,
|
|
!emittedPathSegment
|
|
);
|
|
}
|
|
else
|
|
{
|
|
emittedPathSegment = false;
|
|
}
|
|
|
|
p0 = p1;
|
|
|
|
// Keep these two in sync.
|
|
|
|
pathDistance.Next();
|
|
pathIterator.Next();
|
|
|
|
currentSegmentLength = *(pathDistance.CurrentItem());
|
|
}
|
|
else
|
|
{
|
|
// The remaining path segment length is longer than the remaining
|
|
// dash segment length.
|
|
// Finish the dash segment.
|
|
|
|
// Compute position between start and end point of the current
|
|
// path segment where we finish with this dash segment.
|
|
|
|
ASSERT(REALABS(currentSegmentLength)>REAL_EPSILON);
|
|
sD = *(pathIterator.CurrentItem());
|
|
sD -= p0;
|
|
sD *= currentDashLength/currentSegmentLength;
|
|
|
|
// Move along the path segment by the amount left in the
|
|
// dash segment.
|
|
|
|
currentSegmentLength -= currentDashLength;
|
|
|
|
p1 = p0 + sD;
|
|
|
|
if(IsLineSegment(dashIterator))
|
|
{
|
|
// emit a line. Add the start record only if we didn't just
|
|
// emit a path segment.
|
|
|
|
EmitLineSegment(
|
|
dstPath,
|
|
p0, p1,
|
|
!emittedPathSegment
|
|
);
|
|
}
|
|
|
|
p0 = p1;
|
|
|
|
// dashIterator is circular, so this should keep wrapping through
|
|
// the dash array.
|
|
|
|
dashIterator.Next();
|
|
|
|
// Get the new dash length.
|
|
|
|
currentDashLength = *(dashIterator.CurrentItem());
|
|
emittedPathSegment = false;
|
|
}
|
|
}
|
|
|
|
INT size = dstPath.CurrentIndex();
|
|
|
|
if(!isClosed && size != 0 && numOfPoints != 0)
|
|
{
|
|
BYTE *type;
|
|
REAL halfPenWidth2 = penWidth * penWidth / 4.0f;
|
|
|
|
// Turn off dash mode for the last segment if the last
|
|
// point is very close to the last dash segment.
|
|
if (distance_squared(points[numOfPoints-1], newPts[size-1]) < halfPenWidth2)
|
|
{
|
|
dstPath.Prev();
|
|
type = dstPath.CurrentType();
|
|
*type &= ~PathPointTypeDashMode;
|
|
}
|
|
|
|
// Turn off dash mode for the first segment if the first
|
|
// point is very close to the first dash segment.
|
|
if (distance_squared(points[0], newPts[0]) < halfPenWidth2)
|
|
{
|
|
dstPath.SeekFirst();
|
|
type = dstPath.CurrentType();
|
|
*type &= ~PathPointTypeDashMode;
|
|
}
|
|
}
|
|
|
|
// return the number of entries added to the dstPath array.
|
|
|
|
return (size);
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Creates a dashed path.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] pen - This pen contains the dash info.
|
|
* [IN] matrix - The transformation where the dash patterns are calculated.
|
|
* But the dashed path is transformed back to the World
|
|
* coordinates.
|
|
* [IN] dpiX - x-resolution.
|
|
* [IN] dpiY - y-resolution.
|
|
*
|
|
* Return Value:
|
|
*
|
|
* returns a dashed path.
|
|
*
|
|
* Created:
|
|
*
|
|
* 01/27/2000 ikkof
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpPath*
|
|
GpPath::CreateDashedPath(
|
|
const GpPen* pen,
|
|
const GpMatrix* matrix,
|
|
REAL dpiX,
|
|
REAL dpiY,
|
|
REAL dashScale,
|
|
BOOL needDashCaps
|
|
) const
|
|
{
|
|
if(pen == NULL)
|
|
return NULL;
|
|
|
|
DpPen* dpPen = ((GpPen* ) pen)->GetDevicePen();
|
|
|
|
return CreateDashedPath(dpPen, matrix, dpiX, dpiY, dashScale, needDashCaps);
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Returns TRUE if the given points have non-horizontal or non-vertical
|
|
* edges.
|
|
*
|
|
*
|
|
* Created:
|
|
*
|
|
* 04/07/2000 ikkof
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
inline
|
|
BOOL
|
|
hasDiagonalEdges(
|
|
GpPointF* points,
|
|
INT count
|
|
)
|
|
{
|
|
if(!points || count <= 1)
|
|
return FALSE;
|
|
|
|
GpPointF *curPt, *nextPt;
|
|
curPt = points;
|
|
nextPt = points + 1;
|
|
|
|
BOOL foundDiagonal = FALSE;
|
|
INT i = 1;
|
|
|
|
while(!foundDiagonal && i < count)
|
|
{
|
|
if((curPt->X == nextPt->X) || (curPt->Y == nextPt->Y))
|
|
{
|
|
// This is either horizontal or vertical edges.
|
|
// Go to the next edge.
|
|
|
|
curPt++;
|
|
nextPt++;
|
|
i++;
|
|
}
|
|
else
|
|
foundDiagonal = TRUE;
|
|
}
|
|
|
|
return foundDiagonal;
|
|
}
|
|
|
|
GpPath*
|
|
GpPath::CreateDashedPath(
|
|
const DpPen* dpPen,
|
|
const GpMatrix* matrix,
|
|
REAL dpiX,
|
|
REAL dpiY,
|
|
REAL dashScale,
|
|
BOOL needDashCaps
|
|
) const
|
|
{
|
|
FPUStateSaver::AssertMode();
|
|
|
|
|
|
GpPointF* points = Points.GetDataBuffer();
|
|
INT numOfPoints = GetPointCount();
|
|
|
|
if(dpPen == NULL)
|
|
return NULL;
|
|
|
|
if(
|
|
dpPen->DashStyle == DashStyleSolid ||
|
|
dpPen->DashCount == 0 ||
|
|
dpPen->DashArray == NULL
|
|
)
|
|
return NULL;
|
|
|
|
REAL penWidth = dpPen->Width;
|
|
GpUnit unit = dpPen->Unit;
|
|
BOOL isWorldUnit = TRUE;
|
|
|
|
REAL dashUnit;
|
|
{
|
|
// The minimum pen width
|
|
REAL minimumPenWidth = 1.0f;
|
|
|
|
if(REALABS(dashScale-0.5f) < REAL_EPSILON)
|
|
{
|
|
minimumPenWidth = 4.0f;
|
|
}
|
|
|
|
if(unit != UnitWorld)
|
|
{
|
|
isWorldUnit = FALSE;
|
|
penWidth = ::GetDeviceWidth(penWidth, unit, dpiX);
|
|
|
|
// Prevent the extremely thin line and dashes.
|
|
|
|
dashUnit = max(penWidth, minimumPenWidth);
|
|
}
|
|
else
|
|
{
|
|
REAL majorR, minorR;
|
|
|
|
// Calculate the device width.
|
|
|
|
::GetMajorAndMinorAxis(&majorR, &minorR, matrix);
|
|
REAL maxWidth = penWidth*majorR;
|
|
REAL minWidth = penWidth*minorR;
|
|
|
|
// If the device width becomes less than 1, strech the penWidth
|
|
// so that the device width becomes 1.
|
|
// If we're doing the inset pen, then the path is scaled up double
|
|
// in size and we need to scale by the inverse.
|
|
// Also, the minimum pen width needs to be 2 not 1 in this case
|
|
// because we will remove half the line width. dashScale is 1/2
|
|
// in this case so we divide by it.
|
|
|
|
dashUnit = penWidth;
|
|
|
|
if(maxWidth < minimumPenWidth)
|
|
{
|
|
dashUnit = minimumPenWidth/majorR;
|
|
}
|
|
}
|
|
}
|
|
|
|
dashUnit *= dashScale;
|
|
|
|
GpMatrix mat, invMat;
|
|
|
|
if(matrix)
|
|
{
|
|
mat = *matrix;
|
|
invMat = mat;
|
|
}
|
|
|
|
if(invMat.IsInvertible())
|
|
{
|
|
invMat.Invert();
|
|
}
|
|
else
|
|
{
|
|
WARNING(("Inverse matrix does not exist."));
|
|
|
|
return NULL;
|
|
}
|
|
|
|
INT dashCount = dpPen->DashCount;
|
|
REAL* dashArray = (REAL*) GpMalloc(dashCount*sizeof(REAL));
|
|
if(dashArray)
|
|
{
|
|
GpMemcpy(dashArray, dpPen->DashArray, dashCount*sizeof(REAL));
|
|
|
|
// Adjust the dash interval according the stroke width.
|
|
|
|
for(INT i = 0; i < dashCount; i++)
|
|
{
|
|
dashArray[i] *= dashUnit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
GpPath* newPath = Clone();
|
|
|
|
if(newPath && newPath->IsValid())
|
|
{
|
|
// Flatten in the resolution given by the matrix.
|
|
|
|
newPath->Flatten(&mat);
|
|
|
|
if(isWorldUnit)
|
|
{
|
|
// Transform back to the World Unit.
|
|
// When the pen is in WorldUnit, transform the path
|
|
// before detDashData() is called.
|
|
|
|
newPath->Transform(&invMat);
|
|
}
|
|
|
|
BYTE *types = newPath->Types.GetDataBuffer();
|
|
points = newPath->Points.GetDataBuffer();
|
|
numOfPoints = newPath->GetPointCount();
|
|
|
|
GpPointF* grad = (GpPointF*) GpMalloc((numOfPoints + 1)*sizeof(GpPointF));
|
|
REAL* distances = (REAL*) GpMalloc((numOfPoints + 1)*sizeof(REAL));
|
|
|
|
if(grad == NULL || distances == NULL)
|
|
{
|
|
GpFree(grad);
|
|
GpFree(dashArray);
|
|
delete newPath;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
// Calculate the distance of each segment.
|
|
|
|
INT i;
|
|
|
|
REAL dashLength = 0;
|
|
|
|
for(i = 0; i < dashCount; i++)
|
|
dashLength += dashArray[i];
|
|
|
|
// Make sure count is an even number.
|
|
// !!! [asecchia] this looks completely bogus.
|
|
// surely we should have ASSERTed that this is true at this point
|
|
// and ensured that it was true by parameter validation at the API?
|
|
|
|
if(dashCount & 0x01)
|
|
dashCount ++;
|
|
|
|
// Compute the dash adjustment for the dash cap length here. This
|
|
// is outside of the subpath loop so it only gets computed once
|
|
// and therefore doesn't get applied to further subpath segments
|
|
// multiple times.
|
|
|
|
if (needDashCaps)
|
|
{
|
|
GpPen *gppen = GpPen::GetPen(dpPen);
|
|
if (gppen != NULL)
|
|
{
|
|
gppen->AdjustDashArrayForCaps(
|
|
dashUnit,
|
|
dashArray,
|
|
dashCount
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
DynByteArray dashTypes;
|
|
DynPointFArray dashPoints;
|
|
|
|
BYTE* newTypes = NULL;
|
|
GpPointF* newPts = NULL;
|
|
|
|
DpPathIterator iter(points, types, numOfPoints);
|
|
|
|
INT startIndex, endIndex;
|
|
BOOL isClosed;
|
|
REALD totalLength = 0;
|
|
INT totalCount = 0;
|
|
BOOL isSingleSubpath = iter.GetSubpathCount() == 1;
|
|
|
|
while(iter.NextSubpath(&startIndex, &endIndex, &isClosed))
|
|
{
|
|
GpPointF startPt, lastPt, nextPt;
|
|
REAL dx, dy;
|
|
REALD length;
|
|
startPt = points[startIndex];
|
|
lastPt = startPt;
|
|
|
|
totalLength = 0;
|
|
INT k = 0;
|
|
INT segmentCount = endIndex - startIndex + 1;
|
|
|
|
CalculateGradientArray(grad, distances,
|
|
points + startIndex, segmentCount);
|
|
|
|
for(i = 1; i < segmentCount; i++)
|
|
totalLength += distances[i];
|
|
|
|
if(isClosed)
|
|
totalLength += distances[0];
|
|
|
|
// Estimate the required points.
|
|
|
|
INT estimateCount
|
|
= GpCeiling(TOREAL(totalLength*dashCount/dashLength))
|
|
+ numOfPoints;
|
|
|
|
// For extra caution, multiply by 2.
|
|
|
|
estimateCount <<= 1;
|
|
|
|
// Allocate new types and buffers
|
|
|
|
if(newTypes)
|
|
{
|
|
BYTE* newTypes1 = (BYTE*) GpRealloc(
|
|
newTypes,
|
|
estimateCount*sizeof(BYTE));
|
|
if(newTypes1)
|
|
newTypes = newTypes1;
|
|
else
|
|
goto cleanUp;
|
|
}
|
|
else
|
|
{
|
|
newTypes = (BYTE*) GpMalloc(estimateCount*sizeof(BYTE));
|
|
if(!newTypes)
|
|
goto cleanUp;
|
|
}
|
|
|
|
if(newPts)
|
|
{
|
|
GpPointF* newPts1 = (GpPointF*) GpRealloc(
|
|
newPts,
|
|
estimateCount*sizeof(GpPointF));
|
|
if(newPts1)
|
|
newPts = newPts1;
|
|
else
|
|
goto cleanUp;
|
|
}
|
|
else
|
|
{
|
|
newPts = (GpPointF*) GpMalloc(estimateCount*sizeof(GpPointF));
|
|
if(!newPts)
|
|
goto cleanUp;
|
|
}
|
|
|
|
// Adjust the dash offset if necessary.
|
|
|
|
REAL dashCapOffsetAdjustment = 0.0f;
|
|
if (needDashCaps)
|
|
{
|
|
GpPen *gppen = GpPen::GetPen(dpPen);
|
|
if ((gppen != NULL) && isClosed)
|
|
{
|
|
// Fix for Whistler Bug 178774
|
|
// Since dash caps are no longer 'inset' when they are
|
|
// rendered, it is possible that on closed paths, the dash caps
|
|
// on the start and end of a closed path will overlap. This
|
|
// offset will leave sufficient space for the two caps. However,
|
|
// this fix is not bullet-proof. It will *always* work if the
|
|
// Dash Offset is 0. However, if it is non-zero, it is possible
|
|
// that the offset will counter-act the adjustment and there
|
|
// will be some dash overlap at the start/end of closed paths.
|
|
// I believe this is acceptable since VISIO 2000, Office9 and
|
|
// PhotoDraw 2000 v2 also have the collision problem.
|
|
// The real solution is to enforce a minimum spacing between the
|
|
// start and end or merge the start/end segments if they collide.
|
|
|
|
dashCapOffsetAdjustment =
|
|
2.0f * gppen->GetDashCapInsetLength(dashUnit);
|
|
}
|
|
}
|
|
|
|
INT newCount = getDashData(
|
|
newTypes,
|
|
newPts,
|
|
estimateCount,
|
|
penWidth,
|
|
// Shouldn't the offset be scaled dashUnit instead of penWidth?
|
|
dpPen->DashOffset * penWidth - dashCapOffsetAdjustment,
|
|
dashArray,
|
|
dashCount,
|
|
types + startIndex,
|
|
points + startIndex,
|
|
endIndex - startIndex + 1,
|
|
isClosed,
|
|
distances
|
|
);
|
|
|
|
if(newCount)
|
|
{
|
|
dashTypes.AddMultiple(newTypes, newCount);
|
|
dashPoints.AddMultiple(newPts, newCount);
|
|
}
|
|
|
|
}
|
|
|
|
totalCount = dashPoints.GetCount();
|
|
if(totalCount > 0)
|
|
{
|
|
GpPathData pathData;
|
|
pathData.Count = totalCount;
|
|
pathData.Types = dashTypes.GetDataBuffer();
|
|
pathData.Points = dashPoints.GetDataBuffer();
|
|
|
|
newPath->SetPathData(&pathData);
|
|
|
|
if(!isWorldUnit)
|
|
{
|
|
// Transform back to the World Unit.
|
|
// When the pen is in WorldUnit, it is already transformed
|
|
// before detDashData() is called.
|
|
|
|
newPath->Transform(&invMat);
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
delete newPath;
|
|
newPath = NULL;
|
|
}
|
|
|
|
GpFree(newTypes);
|
|
GpFree(newPts);
|
|
GpFree(distances);
|
|
GpFree(grad);
|
|
GpFree(dashArray);
|
|
|
|
return newPath;
|
|
|
|
cleanUp:
|
|
GpFree(newTypes);
|
|
GpFree(newPts);
|
|
GpFree(distances);
|
|
GpFree(grad);
|
|
GpFree(dashArray);
|
|
delete newPath;
|
|
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
GpFree(dashArray);
|
|
|
|
if(newPath)
|
|
delete newPath;
|
|
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description
|
|
*
|
|
* ComputeWindingModeOutline
|
|
* (so called RemoveSelfIntersections)
|
|
*
|
|
* This computes the winding mode fill outline for the path. It's designed to
|
|
* produce a path that will look the same as a winding mode fill if it's
|
|
* filled with an alternate fill.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* matrix - world to device matrix - used for flattening.
|
|
* flatness - number of device pixels of error in the flattening tolerance.
|
|
* - Pass FlatnessDefault for default behaviour.
|
|
*
|
|
* Return Value:
|
|
*
|
|
* GpStatus
|
|
*
|
|
* History:
|
|
*
|
|
* 06/16/1999 t-wehunt
|
|
* Created it.
|
|
* 10/31/2000 asecchia
|
|
* rewrote, rename.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
GpPath::ComputeWindingModeOutline(
|
|
const GpMatrix *matrix,
|
|
REAL flatness,
|
|
BOOL *wereIntersectsRemoved
|
|
)
|
|
{
|
|
PathSelfIntersectRemover corrector;
|
|
DynPointFArray newPoints; // Array that will hold the new points.
|
|
DynIntArray polyCounts; // Array that will hold the numPoints for each
|
|
// new polygon.
|
|
INT numPolys; // count of new polygons created
|
|
INT numPoints; // count of new points created
|
|
GpStatus status = Ok; // holds return status of commmands
|
|
|
|
// Must clone the path because this could fail while we're accumulating
|
|
// the new path and we'd end up returning an invalid path.
|
|
// If we return InvalidParameter or some other failure code, we'll return
|
|
// with the original path intact.
|
|
|
|
GpPath *path = Clone();
|
|
|
|
if(path == NULL)
|
|
{
|
|
return OutOfMemory;
|
|
}
|
|
|
|
status = path->Flatten(matrix, flatness);
|
|
if(Ok != status)
|
|
{
|
|
goto Done;
|
|
}
|
|
|
|
INT pointCount = path->GetPointCount();
|
|
GpPointF *pathPts = const_cast<GpPointF*>(path->GetPathPoints());
|
|
BYTE *pathTypes = const_cast<BYTE*>(path->GetPathTypes());
|
|
|
|
if (pointCount == 0)
|
|
{
|
|
goto Done;
|
|
}
|
|
|
|
// Add the subpaths to the Path corrector
|
|
INT ptIndex=0; // ptIndex tracks the current index in the array of points.
|
|
INT count=0; // the size of the current subpath.
|
|
|
|
// Init the corrector with the number of points we will be adding.
|
|
if ((status = corrector.Init(pointCount)) != Ok)
|
|
{
|
|
goto Done;
|
|
}
|
|
|
|
while (ptIndex < pointCount)
|
|
{
|
|
if (pathTypes[ptIndex] == PathPointTypeStart && ptIndex != 0)
|
|
{
|
|
// Add the next subpath to the PathCorrector. the start index of the subpath is
|
|
// determined using the current index minus the current subPath size.
|
|
if ((status =
|
|
corrector.AddPolygon(pathPts + ptIndex-count, count)) != Ok)
|
|
{
|
|
goto Done;
|
|
}
|
|
// set count to 1 since this is the first point in the new subpath
|
|
count = 1;
|
|
} else
|
|
{
|
|
count++;
|
|
}
|
|
ptIndex++;
|
|
}
|
|
// Add the last subpath that is implicitly ended by the last point.
|
|
if (ptIndex != 0)
|
|
{
|
|
// Add the next subpath to the PathCorrector. the start index of the subpath is
|
|
// determined using the current index minus the current subPath size.
|
|
if ((status =
|
|
corrector.AddPolygon(pathPts + ptIndex-count, count)) != Ok)
|
|
{
|
|
goto Done;
|
|
}
|
|
}
|
|
|
|
if ((status = corrector.RemoveSelfIntersects()) != Ok)
|
|
{
|
|
goto Done;
|
|
}
|
|
|
|
if ((status = corrector.GetNewPoints(&newPoints, &polyCounts)) != Ok)
|
|
{
|
|
goto Done;
|
|
}
|
|
|
|
// clear out the old path data so we can replace with the newly corrected one.
|
|
path->Reset();
|
|
|
|
// Now that we have the corrected path, add it back.
|
|
GpPointF *curPoints = newPoints.GetDataBuffer();
|
|
for (INT i=0;i<polyCounts.GetCount();i++)
|
|
{
|
|
if(polyCounts[i] > 1)
|
|
{
|
|
if ((status = path->AddPolygon(curPoints,polyCounts[i])) != Ok)
|
|
{
|
|
goto Done;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WARNING(("degenerate polygon created by the SelfIntersectRemover"));
|
|
}
|
|
|
|
curPoints += polyCounts[i];
|
|
}
|
|
|
|
if (wereIntersectsRemoved != NULL)
|
|
{
|
|
*wereIntersectsRemoved = corrector.PathWasModified();
|
|
}
|
|
|
|
|
|
// Clear the state in the path. There is only one failure path following
|
|
// this and it leaves the path in an invalid state anyway, so we can do
|
|
// Reset here. Reset ensures that the cache is invalidated and the
|
|
// UID is recomputed.
|
|
|
|
Reset();
|
|
|
|
// Swap the path data pointers - free the old ones.
|
|
|
|
if(Ok != Points.ReplaceWith((DynArray<GpPointF> *) (&path->Points)) ||
|
|
Ok != Types.ReplaceWith((DynArray<BYTE> *) (&path->Types)))
|
|
{
|
|
// If the final commit fails due to low memory, we are hosed,
|
|
// we wiped out some of our path data and can't convert from the
|
|
// local copy, so set our status to invalid and fail the call.
|
|
|
|
SetValid(FALSE);
|
|
status = OutOfMemory;
|
|
goto Done;
|
|
}
|
|
|
|
// Need to remember the subpath count.
|
|
|
|
SubpathCount = path->SubpathCount;
|
|
|
|
Done:
|
|
|
|
delete path;
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
VOID DpPath::InitDefaultState(GpFillMode fillMode)
|
|
{
|
|
HasBezier = FALSE;
|
|
FillMode = fillMode;
|
|
Flags = PossiblyNonConvex;
|
|
IsSubpathActive = FALSE;
|
|
SubpathCount = 0;
|
|
|
|
Types.Reset(FALSE); // FALSE - don't free the memory
|
|
Points.Reset(FALSE); // FALSE - don't free the memory
|
|
|
|
SetValid(TRUE);
|
|
UpdateUid();
|
|
}
|
|
|
|
DpPath::DpPath(const DpPath* path)
|
|
{
|
|
if(path)
|
|
{
|
|
HasBezier = path->HasBezier;
|
|
FillMode = path->FillMode;
|
|
Flags = path->Flags;
|
|
IsSubpathActive = path->IsSubpathActive;
|
|
SubpathCount = path->SubpathCount;
|
|
|
|
BYTE *types = path->Types.GetDataBuffer();
|
|
GpPointF* points = path->Points.GetDataBuffer();
|
|
INT count = path->GetPointCount();
|
|
|
|
SetValid((count == 0) || ((Types.AddMultiple(types, count) == Ok) &&
|
|
(Points.AddMultiple(points, count) == Ok)));
|
|
}
|
|
else
|
|
SetValid(FALSE);
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Offset all path points by the specified amount
|
|
*
|
|
* Arguments:
|
|
*
|
|
* dx, dy - Amount to offset along x- and y- direction
|
|
*
|
|
* Return Value:
|
|
*
|
|
* NONE
|
|
*
|
|
\**************************************************************************/
|
|
|
|
VOID
|
|
DpPath::Offset(
|
|
REAL dx,
|
|
REAL dy
|
|
)
|
|
{
|
|
ASSERT(IsValid());
|
|
|
|
INT count = GetPointCount();
|
|
GpPointF* pts = Points.GetDataBuffer();
|
|
|
|
if (count > 0)
|
|
{
|
|
UpdateUid();
|
|
}
|
|
while (count--)
|
|
{
|
|
pts->X += dx;
|
|
pts->Y += dy;
|
|
pts++;
|
|
}
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Create a driver DpPath class.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] fillMode - Specify the path fill mode
|
|
*
|
|
* Return Value:
|
|
*
|
|
* IsValid() is FALSE if failure.
|
|
*
|
|
* History:
|
|
*
|
|
* 12/08/1998 andrewgo
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
DpPath::DpPath(
|
|
const GpPointF *points,
|
|
INT count,
|
|
GpPointF *stackPoints,
|
|
BYTE *stackTypes,
|
|
INT stackCount,
|
|
GpFillMode fillMode,
|
|
DpPathFlags pathFlags
|
|
) : Types(stackTypes, stackCount), Points(stackPoints, stackCount)
|
|
{
|
|
ASSERT((fillMode == FillModeAlternate) ||
|
|
(fillMode == FillModeWinding));
|
|
|
|
InitDefaultState(fillMode);
|
|
Flags = pathFlags;
|
|
|
|
// We can call this method with no points, just to set up
|
|
// the stackPoints/stackTypes
|
|
|
|
if (count > 0)
|
|
{
|
|
BYTE *types;
|
|
|
|
if ((types = Types.AddMultiple(count)) != NULL)
|
|
{
|
|
*types++ = PathPointTypeStart;
|
|
GpMemset(types, PathPointTypeLine, count - 1);
|
|
SetValid(Points.AddMultiple(points, count) == Ok);
|
|
|
|
if(IsValid())
|
|
{
|
|
IsSubpathActive = TRUE;
|
|
SubpathCount = 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetValid(FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Close the currently active subpath in a path object
|
|
*
|
|
* Arguments:
|
|
*
|
|
* NONE
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* History:
|
|
*
|
|
* 01/15/1999 ikkof
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
DpPath::CloseFigure()
|
|
{
|
|
ASSERT(IsValid());
|
|
|
|
// Check if there is an active subpath
|
|
|
|
if (IsSubpathActive)
|
|
{
|
|
// If so, mark the last point as the end of a subpath
|
|
|
|
Types.Last() |= PathPointTypeCloseSubpath;
|
|
StartFigure();
|
|
}
|
|
|
|
return Ok;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Close all open subpaths in a path object
|
|
*
|
|
* Arguments:
|
|
*
|
|
* NONE
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Status code
|
|
*
|
|
* History:
|
|
*
|
|
* 01/15/1999 ikkof
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
DpPath::CloseFigures()
|
|
{
|
|
ASSERT(IsValid());
|
|
|
|
// Go through all path points.
|
|
// Notice that the loop index starts from 1 below.
|
|
|
|
INT i, count = GetPointCount();
|
|
BYTE* types = Types.GetDataBuffer();
|
|
|
|
for (i=1; i < count; i++)
|
|
{
|
|
if (types[i] == PathPointTypeStart)
|
|
types[i-1] |= PathPointTypeCloseSubpath;
|
|
}
|
|
|
|
if (count > 1)
|
|
types[count-1] |= PathPointTypeCloseSubpath;
|
|
|
|
StartFigure();
|
|
return Ok;
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Calculates the bounds of a path
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [OUT] bounds - Specify the place to stick the bounds
|
|
* [IN] matrix - Matrix used to transform the bounds
|
|
* [IN] pen - the pen data.
|
|
* [IN] dpiX, dpiY - the resolution of x and y directions.
|
|
*
|
|
* Return Value:
|
|
*
|
|
* NONE
|
|
*
|
|
* History:
|
|
*
|
|
* 12/08/1998 andrewgo
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
GpPath::GetBounds(
|
|
GpRect *bounds,
|
|
const GpMatrix *matrix,
|
|
const DpPen* pen,
|
|
REAL dpiX,
|
|
REAL dpiY
|
|
) const
|
|
{
|
|
if(bounds == NULL)
|
|
return InvalidParameter;
|
|
|
|
GpRectF boundsF;
|
|
|
|
FPUStateSaver fpuState;
|
|
|
|
GpStatus status = GetBounds(&boundsF, matrix, pen, dpiX, dpiY);
|
|
|
|
if(status == Ok)
|
|
status = BoundsFToRect(&boundsF, bounds);
|
|
|
|
return status;
|
|
}
|
|
|
|
VOID
|
|
GpPath::CalcCacheBounds() const
|
|
{
|
|
|
|
INT count = GetPointCount();
|
|
GpPointF *point = Points.GetDataBuffer();
|
|
|
|
if(count <= 1)
|
|
{
|
|
ResetCacheBounds();
|
|
return;
|
|
}
|
|
|
|
REAL left, right, top, bottom;
|
|
|
|
left = point->X;
|
|
right = left;
|
|
top = point->Y;
|
|
bottom = top;
|
|
|
|
INT i;
|
|
for (i = 1, point++; i < count; i++, point++)
|
|
{
|
|
if (point->X < left)
|
|
{
|
|
left = point->X;
|
|
}
|
|
else if (point->X > right)
|
|
{
|
|
right = point->X;
|
|
}
|
|
|
|
if (point->Y < top)
|
|
{
|
|
top = point->Y;
|
|
}
|
|
else if (point->Y > bottom)
|
|
{
|
|
bottom = point->Y;
|
|
}
|
|
}
|
|
CacheBounds.X = left;
|
|
CacheBounds.Width = right - left;
|
|
CacheBounds.Y = top;
|
|
CacheBounds.Height = bottom - top;
|
|
|
|
if(CacheBounds.Width < POINTF_EPSILON && CacheBounds.Height < POINTF_EPSILON)
|
|
{
|
|
ResetCacheBounds();
|
|
return;
|
|
}
|
|
|
|
CacheFlags = kCacheBoundsValid;
|
|
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Calculates the sharpest angle in a path.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* NONE
|
|
*
|
|
* Return Value:
|
|
*
|
|
* NONE
|
|
*
|
|
* History:
|
|
*
|
|
* 10/04/2000 asecchia
|
|
* Created it.
|
|
*
|
|
* Remarks:
|
|
*
|
|
* This is an expensive function, if it's ever used in a performance
|
|
* critical scenario it should be recoded to use the dot product of the
|
|
* segments and perform the angle comparison in the cosine domain.
|
|
* The cost of normalizing the vectors should be cheaper than the
|
|
* atan algorithm used below.
|
|
*
|
|
*
|
|
\**************************************************************************/
|
|
|
|
VOID
|
|
GpPath::CalcSharpestAngle() const
|
|
{
|
|
if(CacheFlags & kSharpestAngleValid)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UpdateCacheBounds();
|
|
|
|
// Walk the path and find the smallest angle between two
|
|
// adjacent segments.
|
|
|
|
GpPathPointIterator pIter(
|
|
(GpPointF*)GetPathPoints(),
|
|
(BYTE*)GetPathTypes(),
|
|
GetPointCount()
|
|
);
|
|
|
|
GpSubpathIterator pSubpath(&pIter);
|
|
|
|
GpPointF *points;
|
|
BOOL isClosed;
|
|
|
|
GpPointF *p0, *p1;
|
|
GpVector2D v;
|
|
REAL lastAngle;
|
|
REAL currAngle;
|
|
REAL minAngle = 2*PI;
|
|
REAL tempAngle;
|
|
bool first = true;
|
|
INT iter, i;
|
|
|
|
|
|
while(!pSubpath.IsDone())
|
|
{
|
|
// Compute the length of the subpath.
|
|
|
|
INT startIndex = pSubpath.CurrentIndex();
|
|
points = pSubpath.CurrentItem();
|
|
pSubpath.Next();
|
|
INT elementCount = pSubpath.CurrentIndex() - startIndex;
|
|
|
|
// Work out if it's a closed subpath.
|
|
// Leave the subpath iterator in the same state.
|
|
|
|
pIter.Prev();
|
|
isClosed = (*(pIter.CurrentType()) & PathPointTypeCloseSubpath) ==
|
|
PathPointTypeCloseSubpath;
|
|
pIter.Next();
|
|
|
|
// Create a GpPointF iterator.
|
|
|
|
GpArrayIterator<GpPointF> iSubpath(points, elementCount);
|
|
GpCircularIterator<GpPointF> iCirc(&iSubpath);
|
|
|
|
// Initialize the first point.
|
|
|
|
p0 = iCirc.CurrentItem();
|
|
iCirc.Next();
|
|
iter = elementCount;
|
|
first = true;
|
|
|
|
// include the endpoint wrap if it's closed
|
|
|
|
if(isClosed)
|
|
{
|
|
iter += 2;
|
|
}
|
|
|
|
for(i = 1; i < iter; i++)
|
|
{
|
|
// Get the current point.
|
|
|
|
p1 = iCirc.CurrentItem();
|
|
|
|
// Translate to the origin and compute the angle between this line
|
|
// and the x axis.
|
|
// atan2 returns values in the -PI..PI range.
|
|
|
|
v = (*p1)-(*p0);
|
|
currAngle = (REAL)atan2(v.Y, v.X);
|
|
|
|
// If we have enough data to do an angle computation, work it out.
|
|
// We require two line segments to do a computation (3 end points).
|
|
// If it's closed, we'll loop around the subpath past the beginning
|
|
// again in order to get the right amount of points.
|
|
|
|
if( !first )
|
|
{
|
|
// reverse the direction of the last segment by adding PI and
|
|
// compute the difference.
|
|
|
|
tempAngle = lastAngle + PI; // range 0 .. 2PI
|
|
|
|
// Clamp back to the -PI..PI range
|
|
|
|
if(tempAngle > PI)
|
|
{
|
|
tempAngle -= 2*PI;
|
|
}
|
|
|
|
// Difference
|
|
|
|
tempAngle = currAngle - tempAngle;
|
|
|
|
// Clamp back to the -PI..PI range
|
|
// Note that the extremes are tempAngle either -2PI or 2PI
|
|
|
|
if(tempAngle > PI)
|
|
{
|
|
tempAngle -= 2*PI;
|
|
}
|
|
|
|
if(tempAngle < -PI)
|
|
{
|
|
tempAngle += 2*PI;
|
|
}
|
|
|
|
// new minimum angle?
|
|
// We care about angle magnitude - not sign.
|
|
|
|
if( minAngle > REALABS(tempAngle) )
|
|
{
|
|
minAngle = REALABS(tempAngle);
|
|
}
|
|
|
|
}
|
|
|
|
// iterate
|
|
|
|
first = false;
|
|
lastAngle = currAngle;
|
|
iCirc.Next();
|
|
p0 = p1;
|
|
}
|
|
}
|
|
|
|
SharpestAngle = minAngle;
|
|
CacheFlags |= kSharpestAngleValid;
|
|
}
|
|
|
|
GpStatus
|
|
GpPath::GetBounds(
|
|
GpRectF *bounds, // Resulting bounds in device-space
|
|
const GpMatrix *matrix,
|
|
const DpPen* pen,
|
|
REAL dpiX,
|
|
REAL dpiY
|
|
) const
|
|
{
|
|
if(bounds == NULL)
|
|
return InvalidParameter;
|
|
|
|
ASSERT(IsValid());
|
|
|
|
if ((dpiX <= 0) || (dpiY <= 0))
|
|
{
|
|
dpiX = Globals::DesktopDpiX;
|
|
dpiY = Globals::DesktopDpiY;
|
|
}
|
|
|
|
INT count = GetPointCount();
|
|
GpPointF *point = Points.GetDataBuffer();
|
|
|
|
if ((count == 0) || (point == NULL))
|
|
{
|
|
bounds->X = 0;
|
|
bounds->Y = 0;
|
|
bounds->Width = 0;
|
|
bounds->Height = 0;
|
|
}
|
|
else
|
|
{
|
|
REAL left, right, top, bottom;
|
|
|
|
UpdateCacheBounds();
|
|
|
|
left = CacheBounds.X;
|
|
right = left + CacheBounds.Width;
|
|
top = CacheBounds.Y;
|
|
bottom = top + CacheBounds.Height;
|
|
|
|
TransformBounds(matrix, left, top, right, bottom, bounds);
|
|
|
|
if(pen)
|
|
{
|
|
BOOL needsJoinDelta = TRUE;
|
|
|
|
if(count <= 2)
|
|
needsJoinDelta = FALSE;
|
|
|
|
GpPen* gpPen = GpPen::GetPen(pen);
|
|
|
|
// takes into account the cap width AND the pen width - JBronsk
|
|
REAL delta = gpPen->GetMaximumCapWidth(matrix, dpiX, dpiY);
|
|
|
|
if(needsJoinDelta)
|
|
{
|
|
// Since the join might be a miter type, we need to provide the
|
|
// sharpest angle in the path to see how big the join will be.
|
|
// We have the method GetSharpestAngle() that figues this out.
|
|
// But, this is really expensive since you have to iterate over
|
|
// all the points and do some trig. So, lets assume the worst
|
|
// case, which is a really sharp angle (0 rad).
|
|
const REAL sharpestAngle = 0.0f;
|
|
REAL delta1 = gpPen->GetMaximumJoinWidth(
|
|
sharpestAngle, matrix, dpiX, dpiY);
|
|
if(delta1 > delta)
|
|
delta = delta1;
|
|
}
|
|
|
|
// Only pad the bounds if there is something non-zero to pad
|
|
if (bounds->Width > REAL_EPSILON ||
|
|
bounds->Height > REAL_EPSILON)
|
|
{
|
|
bounds->X -= delta;
|
|
bounds->Y -= delta;
|
|
bounds->Width += 2*delta;
|
|
bounds->Height += 2*delta;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return Ok;
|
|
}
|
|
|
|
|
|
/*************************************************\
|
|
* AddGlyphPath
|
|
* History:
|
|
*
|
|
* Sept/23/1999 Xudong Wu [tessiew]
|
|
* Created it.
|
|
*
|
|
\************************************************/
|
|
GpStatus
|
|
GpPath::AddGlyphPath(
|
|
GpGlyphPath* glyphPath,
|
|
REAL x,
|
|
REAL y,
|
|
const GpMatrix * matrix
|
|
)
|
|
{
|
|
ASSERT(IsValid());
|
|
ASSERT(glyphPath->IsValid());
|
|
|
|
if (!IsValid() || !glyphPath->IsValid())
|
|
return InvalidParameter;
|
|
|
|
INT count = glyphPath->pointCount;
|
|
|
|
if (count == 0) // nothing to add
|
|
return Ok;
|
|
|
|
GpPointF* points = (GpPointF*) glyphPath->points;
|
|
BYTE* types = glyphPath->types;
|
|
|
|
if (glyphPath->hasBezier)
|
|
HasBezier = TRUE;
|
|
|
|
INT origCount = GetPointCount();
|
|
GpPointF* pointbuf = Points.AddMultiple(count);
|
|
BYTE* typebuf = Types.AddMultiple(count);
|
|
|
|
if (!pointbuf || !typebuf)
|
|
{
|
|
Points.SetCount(origCount);
|
|
Types.SetCount(origCount);
|
|
|
|
return OutOfMemory;
|
|
}
|
|
|
|
// apply the font xform
|
|
|
|
for (INT i = 0; i < count; i++)
|
|
{
|
|
pointbuf[i] = points[i];
|
|
if (matrix)
|
|
matrix->Transform(pointbuf + i);
|
|
pointbuf[i].X += x;
|
|
pointbuf[i].Y += y;
|
|
}
|
|
|
|
GpMemcpy(typebuf, types, count*sizeof(BYTE));
|
|
SubpathCount += glyphPath->curveCount;
|
|
UpdateUid();
|
|
InvalidateCache();
|
|
|
|
return Ok;
|
|
}
|
|
|
|
|
|
/*************************************************\
|
|
* AddString()
|
|
* History:
|
|
*
|
|
* 19th Oct 199 dbrown created
|
|
*
|
|
\************************************************/
|
|
GpStatus
|
|
GpPath::AddString(
|
|
const WCHAR *string,
|
|
INT length,
|
|
const GpFontFamily *family,
|
|
INT style,
|
|
REAL emSize,
|
|
const RectF *layoutRect,
|
|
const GpStringFormat *format
|
|
)
|
|
{
|
|
FPUStateSaver fpuState; // Guarantee initialised FP context
|
|
ASSERT(string && family && layoutRect);
|
|
|
|
GpStatus status;
|
|
GpTextImager *imager;
|
|
|
|
status = newTextImager(
|
|
string,
|
|
length,
|
|
layoutRect->Width,
|
|
layoutRect->Height,
|
|
family,
|
|
style,
|
|
emSize,
|
|
format,
|
|
NULL,
|
|
&imager,
|
|
TRUE // Allow use of simple text imager
|
|
);
|
|
|
|
if (status != Ok)
|
|
{
|
|
return status;
|
|
}
|
|
|
|
status = imager->AddToPath(this, &PointF(layoutRect->X, layoutRect->Y));
|
|
delete imager;
|
|
UpdateUid();
|
|
InvalidateCache();
|
|
return status;
|
|
}
|
|
|
|
|
|
// !!! why not convert to a DpRegion and convert it to a path the same way
|
|
// as the constructor that takes a DpRegion?
|
|
GpPath::GpPath(HRGN hRgn)
|
|
{
|
|
ASSERT((hRgn != NULL) && (GetObjectTypeInternal(hRgn) == OBJ_REGION));
|
|
|
|
InitDefaultState(FillModeWinding);
|
|
|
|
ASSERT(IsValid());
|
|
|
|
BYTE stackBuffer[1024];
|
|
|
|
// If our stack buffer is big enough, get the clipping contents
|
|
// in one gulp:
|
|
|
|
RGNDATA *regionBuffer = (RGNDATA*)&stackBuffer[0];
|
|
INT newSize = ::GetRegionData(hRgn, sizeof(stackBuffer), regionBuffer);
|
|
|
|
// The spec says that GetRegionData returns '1' in the event of
|
|
// success, but NT returns the actual number of bytes written if
|
|
// successful, and returns '0' if the buffer wasn't large enough:
|
|
|
|
if ((newSize < 1) || (newSize > sizeof(stackBuffer)))
|
|
{
|
|
// Our stack buffer wasn't big enough. Figure out the required
|
|
// size:
|
|
|
|
newSize = ::GetRegionData(hRgn, 0, NULL);
|
|
if (newSize > 1)
|
|
{
|
|
regionBuffer = (RGNDATA*)GpMalloc(newSize);
|
|
if (regionBuffer == NULL)
|
|
{
|
|
SetValid(FALSE);
|
|
return;
|
|
}
|
|
|
|
// Initialize to a decent result in the unlikely event of
|
|
// failure of GetRegionData:
|
|
|
|
regionBuffer->rdh.nCount = 0;
|
|
|
|
::GetRegionData(hRgn, newSize, regionBuffer);
|
|
}
|
|
}
|
|
|
|
// Add the rects from the region to the path
|
|
|
|
if(regionBuffer->rdh.nCount > 0)
|
|
{
|
|
if (this->AddRects((RECT*)&(regionBuffer->Buffer[0]),
|
|
regionBuffer->rdh.nCount) != Ok)
|
|
{
|
|
SetValid(FALSE);
|
|
}
|
|
}
|
|
|
|
// Free the temporary buffer if one was allocated:
|
|
|
|
if (regionBuffer != (RGNDATA*) &stackBuffer[0])
|
|
{
|
|
GpFree(regionBuffer);
|
|
}
|
|
}
|
|
|
|
// create a path from a GDI+ region
|
|
GpPath::GpPath(
|
|
const DpRegion* region
|
|
)
|
|
{
|
|
InitDefaultState(FillModeAlternate);
|
|
|
|
if (region == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
RegionToPath convertRegion;
|
|
DynPointArray pointsArray;
|
|
|
|
if (convertRegion.ConvertRegionToPath(region, pointsArray, Types))
|
|
{
|
|
int count;
|
|
int i;
|
|
GpPointF * realPoints;
|
|
GpPoint * points;
|
|
|
|
count = Types.GetCount();
|
|
|
|
if ((count <= 0) || (pointsArray.GetCount() != count) ||
|
|
(!ValidatePathTypes(Types.GetDataBuffer(), count, &SubpathCount, &HasBezier)))
|
|
{
|
|
goto NotValid;
|
|
}
|
|
// else it is valid
|
|
|
|
// add all the space for the count in the Points up front
|
|
realPoints = Points.AddMultiple(count);
|
|
if (realPoints == NULL)
|
|
{
|
|
goto NotValid;
|
|
}
|
|
|
|
// add the points, converting from int to real
|
|
points = pointsArray.GetDataBuffer();
|
|
i = 0;
|
|
do
|
|
{
|
|
realPoints[i].X = (REAL)points[i].X;
|
|
realPoints[i].Y = (REAL)points[i].Y;
|
|
} while (++i < count);
|
|
|
|
SetValid(TRUE);
|
|
|
|
// Make sure the first point is the start type.
|
|
ASSERT(Types[0] == PathPointTypeStart);
|
|
|
|
return;
|
|
}
|
|
|
|
NotValid:
|
|
WARNING(("Failed to convert a region to a path"));
|
|
this->Reset();
|
|
SetValid(FALSE);
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Returns a const pointer to the internal SubpathInfoCache. This structure
|
|
* holds the data representing the position and size of each subpath in
|
|
* the path data structures.
|
|
*
|
|
* History:
|
|
*
|
|
* 10/20/2000 asecchia
|
|
* Created.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
DynArray<GpPath::SubpathInfo> *GpPath::GetSubpathInformation() const
|
|
{
|
|
if((CacheFlags & kSubpathInfoValid) == 0)
|
|
{
|
|
ComputeSubpathInformationCache();
|
|
ASSERT((CacheFlags & kSubpathInfoValid) == kSubpathInfoValid)
|
|
}
|
|
|
|
return &SubpathInfoCache;
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Computes the Subpath information cache and marks it as valid.
|
|
* This code walks the entire path and stores the start and count for
|
|
* each subpath. It also notes if the subpath is closed or open.
|
|
*
|
|
* History:
|
|
*
|
|
* 10/20/2000 asecchia
|
|
* Created.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
VOID GpPath::ComputeSubpathInformationCache() const
|
|
{
|
|
// Get the path data:
|
|
|
|
GpPointF *points = Points.GetDataBuffer();
|
|
BYTE *types = Types.GetDataBuffer();
|
|
INT count = Points.GetCount();
|
|
|
|
// Clear out any old cached subpath state.
|
|
|
|
SubpathInfoCache.Reset();
|
|
|
|
INT i = 0; // current position in the path.
|
|
INT c = 0; // current count of the current subpath.
|
|
|
|
// <= so that we can implicitly handle the last subpath without
|
|
// duplicating the code for the inner loop.
|
|
|
|
while(i <= count)
|
|
{
|
|
// i==count means we hit the end - and potentially need to look at
|
|
// the last subpath. Otherwise look at the most recent subpath if
|
|
// we find a new start marker.
|
|
|
|
if( ((i==count) || IsStartType(types[i])) && (i != 0))
|
|
{
|
|
// Found a subpath.
|
|
|
|
SubpathInfo subpathInfo;
|
|
|
|
subpathInfo.StartIndex = i-c;
|
|
subpathInfo.Count = c;
|
|
subpathInfo.IsClosed = IsClosedType(types[i-1]);
|
|
|
|
SubpathInfoCache.Add(subpathInfo);
|
|
|
|
// We're actually on the first point of the next subpath.
|
|
// (or we're about to terminate the loop)
|
|
|
|
c = 1;
|
|
}
|
|
else
|
|
{
|
|
c++;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
// Mark the subpath information cache as valid.
|
|
|
|
CacheFlags |= kSubpathInfoValid;
|
|
}
|
|
|