5543 lines
146 KiB
C++
5543 lines
146 KiB
C++
|
/**************************************************************************\
|
||
|
*
|
||
|
* Copyright (c) 1999 - 2000 Microsoft Corporation
|
||
|
*
|
||
|
* Module Name:
|
||
|
*
|
||
|
* PathWidener.cpp
|
||
|
*
|
||
|
* Abstract:
|
||
|
*
|
||
|
* Implementation of the GpPathWidener class
|
||
|
*
|
||
|
* Revision History:
|
||
|
*
|
||
|
* 11/23/99 ikkof
|
||
|
* Created it
|
||
|
*
|
||
|
\**************************************************************************/
|
||
|
|
||
|
#include "precomp.hpp"
|
||
|
|
||
|
// 4*(REALSQRT(2.0) - 1)/3
|
||
|
#define U_CIR ((REAL)(0.552284749))
|
||
|
|
||
|
|
||
|
|
||
|
// Define DEBUG_PATHWIDENER if debugging is necessary.
|
||
|
|
||
|
//#define DEBUG_PATHWIDENER
|
||
|
|
||
|
GpStatus ReversePath(INT count,GpPointF* points,BYTE* types);
|
||
|
|
||
|
const BOOL USE_POLYGON_JOIN = FALSE;
|
||
|
|
||
|
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
|
||
|
);
|
||
|
|
||
|
GpStatus
|
||
|
CalculateGradientArray(
|
||
|
GpPointF* grad,
|
||
|
REAL* distances,
|
||
|
const GpPointF* points,
|
||
|
INT count
|
||
|
);
|
||
|
|
||
|
GpStatus
|
||
|
GetMajorAndMinorAxis(
|
||
|
REAL* majorR,
|
||
|
REAL* minorR,
|
||
|
const GpMatrix* matrix
|
||
|
);
|
||
|
|
||
|
enum
|
||
|
{
|
||
|
WideningClosed = 1,
|
||
|
WideningFirstType = 2,
|
||
|
WideningLastType = 4,
|
||
|
WideningLastPointSame = 8,
|
||
|
WideningNeedsToAdjustNormals = 16,
|
||
|
WideningUseBevelJoinInside = 32
|
||
|
};
|
||
|
|
||
|
enum GpTurningDirection
|
||
|
{
|
||
|
NotTurning = 0,
|
||
|
TurningBack = 1,
|
||
|
TurningRight = 2,
|
||
|
TurningLeft = 3,
|
||
|
NotMoving = -1
|
||
|
};
|
||
|
|
||
|
GpTurningDirection
|
||
|
getJoin(
|
||
|
GpLineJoin lineJoin,
|
||
|
const GpPointF& point,
|
||
|
const GpPointF& grad1,
|
||
|
const GpPointF& grad2,
|
||
|
const GpPointF& norm1,
|
||
|
const GpPointF& norm2,
|
||
|
REAL leftWidth,
|
||
|
REAL rightWidth,
|
||
|
INT *leftCount,
|
||
|
GpPointF *leftPoints,
|
||
|
BOOL *leftInside,
|
||
|
INT *rightCount,
|
||
|
GpPointF *rightPoints,
|
||
|
BOOL *rightInside,
|
||
|
BOOL needsToAdjustNormals,
|
||
|
REAL miterLimit2
|
||
|
);
|
||
|
|
||
|
/**************************************************************************\
|
||
|
*
|
||
|
* Function Description:
|
||
|
*
|
||
|
* This reverses the path data.
|
||
|
*
|
||
|
* Arguments:
|
||
|
*
|
||
|
* [IN] count - the number of points.
|
||
|
* [IN/OUT] points - the data points to be reversed.
|
||
|
* [IN/OUT] types - the data types to be reversed.
|
||
|
*
|
||
|
* Return Value:
|
||
|
*
|
||
|
* Status
|
||
|
*
|
||
|
\**************************************************************************/
|
||
|
|
||
|
GpStatus
|
||
|
ReversePath(
|
||
|
INT count,
|
||
|
GpPointF* points,
|
||
|
BYTE* types
|
||
|
)
|
||
|
{
|
||
|
DpPathTypeIterator iter(types, count);
|
||
|
|
||
|
if(!iter.IsValid())
|
||
|
return InvalidParameter;
|
||
|
|
||
|
INT startIndex, endIndex;
|
||
|
BOOL isClosed;
|
||
|
BOOL isStartDashMode, isEndDashMode;
|
||
|
BOOL wasMarkerEnd = FALSE;
|
||
|
|
||
|
INT i;
|
||
|
|
||
|
while(iter.NextSubpath(&startIndex, &endIndex, &isClosed))
|
||
|
{
|
||
|
if((types[startIndex] & PathPointTypeDashMode) != 0)
|
||
|
isStartDashMode = TRUE;
|
||
|
else
|
||
|
isStartDashMode = FALSE;
|
||
|
if((types[endIndex] & PathPointTypeDashMode) != 0)
|
||
|
isEndDashMode = TRUE;
|
||
|
else
|
||
|
isEndDashMode = FALSE;
|
||
|
|
||
|
BOOL isMarkerEnd
|
||
|
= (types[endIndex] & PathPointTypePathMarker) != 0;
|
||
|
|
||
|
BYTE startType = types[startIndex]; // Save the first type.
|
||
|
|
||
|
// Shift type points.
|
||
|
|
||
|
for(i = startIndex + 1; i <= endIndex; i++)
|
||
|
{
|
||
|
types[i - 1] = types[i];
|
||
|
}
|
||
|
|
||
|
// Clear the close subpapth flag for original type (now at endIndex - 1).
|
||
|
|
||
|
if(endIndex > 0)
|
||
|
types[endIndex - 1] &= ~PathPointTypeCloseSubpath;
|
||
|
|
||
|
types[endIndex] = PathPointTypeStart;
|
||
|
|
||
|
if(isStartDashMode)
|
||
|
types[startIndex] |= PathPointTypeDashMode;
|
||
|
else
|
||
|
types[startIndex] &= ~PathPointTypeDashMode;
|
||
|
|
||
|
if(isEndDashMode)
|
||
|
types[endIndex] |= PathPointTypeDashMode;
|
||
|
else
|
||
|
types[endIndex] &= ~PathPointTypeDashMode;
|
||
|
|
||
|
// Add the dash and close flag.
|
||
|
|
||
|
if(isClosed)
|
||
|
types[startIndex] |= PathPointTypeCloseSubpath;
|
||
|
else
|
||
|
types[startIndex] &= ~PathPointTypeCloseSubpath;
|
||
|
|
||
|
// Shift the marker flag by 1 from the original position.
|
||
|
// This means we have to shift by 2 since the types array
|
||
|
// was shifted by -1.
|
||
|
|
||
|
for(i = endIndex; i >= startIndex + 2; i--)
|
||
|
{
|
||
|
if(types[i - 2] & PathPointTypePathMarker)
|
||
|
types[i] |= PathPointTypePathMarker;
|
||
|
else
|
||
|
types[i] &= ~PathPointTypePathMarker;
|
||
|
}
|
||
|
|
||
|
// Shift Marker flag from the startIndex.
|
||
|
|
||
|
if(startType & PathPointTypePathMarker)
|
||
|
types[startIndex + 1] |= PathPointTypePathMarker;
|
||
|
else
|
||
|
types[startIndex + 1] &= ~PathPointTypePathMarker;
|
||
|
|
||
|
// Shift Marker flag from the end of the previous subpath.
|
||
|
|
||
|
if(wasMarkerEnd)
|
||
|
types[startIndex] |= PathPointTypePathMarker;
|
||
|
else
|
||
|
types[startIndex] &= ~PathPointTypePathMarker;
|
||
|
|
||
|
wasMarkerEnd = isMarkerEnd;
|
||
|
|
||
|
// Keep the location of the internal flag. So we must
|
||
|
// shift back by 1.
|
||
|
|
||
|
for(i = endIndex; i >= startIndex + 1; i--)
|
||
|
{
|
||
|
if(types[i - 1] & PathPointTypeInternalUse)
|
||
|
types[i] |= PathPointTypeInternalUse;
|
||
|
else
|
||
|
types[i] &= ~PathPointTypeInternalUse;
|
||
|
}
|
||
|
if(startType & PathPointTypeInternalUse)
|
||
|
types[startIndex] |= PathPointTypeInternalUse;
|
||
|
else
|
||
|
types[startIndex] &= ~PathPointTypeInternalUse;
|
||
|
}
|
||
|
|
||
|
// Reverse the points and types data.
|
||
|
|
||
|
INT halfCount = count/2;
|
||
|
for(i = 0; i < halfCount; i++)
|
||
|
{
|
||
|
GpPointF tempPt;
|
||
|
BYTE tempType;
|
||
|
|
||
|
tempPt = points[count - 1 - i];
|
||
|
tempType = types[count - 1 - i];
|
||
|
points[count - 1 - i] = points[i];
|
||
|
types[count -1 - i] = types[i];
|
||
|
points[i] = tempPt;
|
||
|
types[i] = tempType;
|
||
|
}
|
||
|
|
||
|
#ifdef DEBUG_PATHWIDENER
|
||
|
DpPathTypeIterator iter2(types, count);
|
||
|
|
||
|
if(!iter2.IsValid())
|
||
|
{
|
||
|
WARNING(("ReversePath: failed."));
|
||
|
return GenericError;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
return Ok;
|
||
|
}
|
||
|
|
||
|
/**************************************************************************\
|
||
|
*
|
||
|
* Function Description:
|
||
|
*
|
||
|
* This combines the two open segments each of which is continuous.
|
||
|
* The data is returned to points1 and types1. They must be allocated
|
||
|
* at least the array size of count1 + count2.
|
||
|
*
|
||
|
* Arguments:
|
||
|
*
|
||
|
* [IN] count1 - the number of points of the first path.
|
||
|
* [IN/OUT] points1 - the first path points.
|
||
|
* [IN/OUT] types1 - the first path types.
|
||
|
* [IN] forward1 - the direction of the first path. TRUE if forward.
|
||
|
* [IN] count2 - the number of points of the second path.
|
||
|
* [IN] points2 - the second path points.
|
||
|
* [IN] types2 - the second path types.
|
||
|
* [IN] forward2 - the direction of the second path. TRUE if forward.
|
||
|
*
|
||
|
* Return Value:
|
||
|
*
|
||
|
* The total number of points of the combined path.
|
||
|
*
|
||
|
\**************************************************************************/
|
||
|
|
||
|
INT
|
||
|
combineTwoOpenSegments(
|
||
|
INT count1,
|
||
|
GpPointF* points1,
|
||
|
BYTE* types1,
|
||
|
BOOL forward1,
|
||
|
INT count2,
|
||
|
GpPointF* points2,
|
||
|
BYTE* types2,
|
||
|
BOOL forward2
|
||
|
)
|
||
|
{
|
||
|
GpStatus status = Ok;
|
||
|
|
||
|
if(
|
||
|
count1 < 0 || !points1 || !types1 ||
|
||
|
count2 < 0 || !points2 || !types2
|
||
|
)
|
||
|
return 0;
|
||
|
|
||
|
if(!forward1 && count1 > 0)
|
||
|
{
|
||
|
status = ::ReversePath(count1, points1, types1);
|
||
|
if(status != Ok)
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if(!forward2 && count2 > 0)
|
||
|
{
|
||
|
status = ::ReversePath(count2, points2, types2);
|
||
|
if(status != Ok)
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
INT offset = 0;
|
||
|
|
||
|
if(count1 > 0 && count2 > 0)
|
||
|
{
|
||
|
if(REALABS(points1[count1 - 1].X - points2[0].X) +
|
||
|
REALABS(points1[count1 - 1].Y - points2[0].Y)
|
||
|
< POINTF_EPSILON
|
||
|
)
|
||
|
offset = 1;
|
||
|
}
|
||
|
|
||
|
if(count2 - offset > 0)
|
||
|
{
|
||
|
GpMemcpy(
|
||
|
points1 + count1,
|
||
|
points2 + offset,
|
||
|
(count2 - offset)*sizeof(GpPointF)
|
||
|
);
|
||
|
GpMemcpy(
|
||
|
types1 + count1,
|
||
|
types2 + offset,
|
||
|
count2 - offset
|
||
|
);
|
||
|
}
|
||
|
|
||
|
BYTE saveType = types1[0];
|
||
|
types1[0] = PathPointTypeLine |
|
||
|
(saveType & ~PathPointTypePathTypeMask);
|
||
|
|
||
|
// Make sure the first path is not closed.
|
||
|
|
||
|
if(count1 > 0)
|
||
|
{
|
||
|
if(types1[count1 - 1] & PathPointTypeCloseSubpath)
|
||
|
types1[count1 - 1] &= ~PathPointTypeCloseSubpath;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Set the first point type of the second path correctly.
|
||
|
|
||
|
if(offset == 0)
|
||
|
{
|
||
|
saveType = types1[count1];
|
||
|
types1[count1] = PathPointTypeLine |
|
||
|
(saveType & ~PathPointTypePathTypeMask);
|
||
|
}
|
||
|
|
||
|
// Make sure this path is not closed.
|
||
|
|
||
|
INT total = count1 + count2 - offset;
|
||
|
|
||
|
if(total > 0)
|
||
|
{
|
||
|
if(types1[total - 1] & PathPointTypeCloseSubpath)
|
||
|
types1[total - 1] &= ~PathPointTypeCloseSubpath;
|
||
|
}
|
||
|
|
||
|
return total;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**************************************************************************\
|
||
|
*
|
||
|
* Function Description:
|
||
|
*
|
||
|
* This combines the two closed segments each of which is made of closed
|
||
|
* segments.
|
||
|
* The data is returned to points1 and types1. They must be allocated
|
||
|
* at least the array size of count1 + count2.
|
||
|
*
|
||
|
* Arguments:
|
||
|
*
|
||
|
* [IN] count1 - the number of points of the first path.
|
||
|
* [IN/OUT] points1 - the first path points.
|
||
|
* [IN/OUT] types1 - the first path types.
|
||
|
* [IN] forward1 - the direction of the first path. TRUE if forward.
|
||
|
* [IN] count2 - the number of points of the second path.
|
||
|
* [IN] points2 - the second path points.
|
||
|
* [IN] types2 - the second path types.
|
||
|
* [IN] forward2 - the direction of the second path. TRUE if forward.
|
||
|
*
|
||
|
* Return Value:
|
||
|
*
|
||
|
* The total number of points of the combined path.
|
||
|
*
|
||
|
\**************************************************************************/
|
||
|
|
||
|
INT
|
||
|
combineClosedSegments(
|
||
|
INT count1,
|
||
|
GpPointF* points1,
|
||
|
BYTE* types1,
|
||
|
BOOL forward1,
|
||
|
INT count2,
|
||
|
GpPointF* points2,
|
||
|
BYTE* types2,
|
||
|
BOOL forward2
|
||
|
)
|
||
|
{
|
||
|
GpStatus status = Ok;
|
||
|
|
||
|
if(
|
||
|
count1 < 0 || !points1 || !types1 ||
|
||
|
count2 < 0 || !points2 || !types2
|
||
|
)
|
||
|
return 0;
|
||
|
|
||
|
if(count1 == 0 && count2 == 0)
|
||
|
return 0;
|
||
|
|
||
|
if(!forward1 && count1 > 0)
|
||
|
{
|
||
|
status = ::ReversePath(count1, points1, types1);
|
||
|
if(status != Ok)
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if(!forward2 && count2 > 0)
|
||
|
{
|
||
|
status = ::ReversePath(count2, points2, types2);
|
||
|
if(status != Ok)
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// Make sure the first path is closed.
|
||
|
|
||
|
types1[0] = PathPointTypeStart;
|
||
|
if(count1 > 0)
|
||
|
{
|
||
|
if((types1[count1 - 1] & PathPointTypeCloseSubpath) == 0)
|
||
|
types1[count1 - 1] |= PathPointTypeCloseSubpath;
|
||
|
}
|
||
|
|
||
|
INT total = count1 + count2;
|
||
|
|
||
|
if(count2 > 0)
|
||
|
{
|
||
|
GpMemcpy(points1 + count1, points2, count2*sizeof(GpPointF));
|
||
|
GpMemcpy(types1 + count1, types2, count2);
|
||
|
BYTE saveType = types1[count1];
|
||
|
types1[count1] = PathPointTypeStart |
|
||
|
(saveType & ~PathPointTypePathTypeMask);
|
||
|
|
||
|
// Make sure the second path is closed.
|
||
|
|
||
|
types1[total - 1] |= PathPointTypeCloseSubpath;
|
||
|
}
|
||
|
|
||
|
return total;
|
||
|
}
|
||
|
|
||
|
/**************************************************************************\
|
||
|
*
|
||
|
* Function Description:
|
||
|
*
|
||
|
* This combines the two data points. This is a general algorithm.
|
||
|
* The output buffers (points and types) can be the same as the
|
||
|
* first input buffers (points1 and types1). In that case, both
|
||
|
* buffers must be allocated at least to the array size of
|
||
|
* count1 + count2.
|
||
|
*
|
||
|
* Arguments:
|
||
|
*
|
||
|
* [IN] count - the allocated number of points (>= count1 + count2).
|
||
|
* [OUT] points - the combined data points.
|
||
|
* [OUT] types - the combined data types.
|
||
|
* [IN] count1 - the number of points of the first path.
|
||
|
* [IN] points1 - the first path points.
|
||
|
* [IN] types1 - the first path types.
|
||
|
* [IN] forward1 - the direction of the first path. TRUE if forward.
|
||
|
* [IN] count2 - the number of points of the second path.
|
||
|
* [IN] points2 - the second path points.
|
||
|
* [IN] types2 - the second path types.
|
||
|
* [IN] forward2 - the direction of the second path. TRUE if forward.
|
||
|
* [IN] connect - TRUE if the second line needs to be connected.
|
||
|
*
|
||
|
* Return Value:
|
||
|
*
|
||
|
* The total number of points of the combined path.
|
||
|
*
|
||
|
\**************************************************************************/
|
||
|
|
||
|
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
|
||
|
)
|
||
|
{
|
||
|
if(!points || !types || count < count1 + count2
|
||
|
|| count1 < 0 || !points1 || !types1
|
||
|
|| count2 < 0 || !points2 || !types2)
|
||
|
return 0;
|
||
|
|
||
|
// Check if the returning buffers are the same as the
|
||
|
// first input buffers.
|
||
|
|
||
|
INT resultCount = 0;
|
||
|
if(points != points1 || types != types1)
|
||
|
{
|
||
|
if(points == points1 || types == types1)
|
||
|
{
|
||
|
// The both output buffer must be different.
|
||
|
// If either of them is the same, don't combine
|
||
|
// the path.
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if(count1 > 0)
|
||
|
{
|
||
|
// Copy the first path.
|
||
|
|
||
|
DpPathIterator iter1(points1, types1, count1);
|
||
|
|
||
|
if(!iter1.IsValid())
|
||
|
return 0;
|
||
|
|
||
|
resultCount = iter1.Enumerate(points, types, count1);
|
||
|
|
||
|
if(resultCount <= 0)
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Both output buffers are the same as the first output
|
||
|
// buffers.
|
||
|
|
||
|
resultCount = count1;
|
||
|
}
|
||
|
|
||
|
GpStatus status = Ok;
|
||
|
BOOL path1Closed;
|
||
|
|
||
|
if(!forward1 && resultCount > 0)
|
||
|
{
|
||
|
status = ::ReversePath(resultCount, points, types);
|
||
|
if(status != Ok)
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if(count2 <= 0)
|
||
|
{
|
||
|
// No need to add the second path.
|
||
|
|
||
|
return resultCount;
|
||
|
}
|
||
|
|
||
|
// Regard the empty path as a closed path.
|
||
|
|
||
|
path1Closed = TRUE;
|
||
|
|
||
|
if(resultCount > 0)
|
||
|
{
|
||
|
// Check the last point of path1.
|
||
|
|
||
|
if((types[resultCount - 1] & PathPointTypeCloseSubpath))
|
||
|
path1Closed = TRUE;
|
||
|
else
|
||
|
path1Closed = FALSE;
|
||
|
}
|
||
|
|
||
|
INT totalCount = 0;
|
||
|
totalCount += resultCount;
|
||
|
|
||
|
DpPathIterator iter2(points2, types2, count2);
|
||
|
|
||
|
if(!iter2.IsValid())
|
||
|
return 0;
|
||
|
|
||
|
GpPointF* pts2 = points + resultCount;
|
||
|
BYTE* typs2 = types + resultCount;
|
||
|
|
||
|
resultCount = iter2.Enumerate(pts2, typs2, count2);
|
||
|
|
||
|
if(resultCount <= 0)
|
||
|
return 0;
|
||
|
|
||
|
if(!forward2)
|
||
|
{
|
||
|
status = ::ReversePath(resultCount, pts2, typs2);
|
||
|
if(status != Ok)
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// Check if the first subpath of path2 is closed or not.
|
||
|
|
||
|
BOOL path2Closed;
|
||
|
|
||
|
DpPathTypeIterator iter3(typs2, resultCount);
|
||
|
if(!iter3.IsValid())
|
||
|
return 0;
|
||
|
|
||
|
INT startIndex, endIndex;
|
||
|
iter3.NextSubpath(&startIndex, &endIndex, &path2Closed);
|
||
|
|
||
|
BYTE saveType= typs2[0];
|
||
|
|
||
|
if(path1Closed || path2Closed)
|
||
|
{
|
||
|
typs2[0] = PathPointTypeStart |
|
||
|
(saveType & ~PathPointTypePathTypeMask);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Both paths are opened.
|
||
|
|
||
|
if(connect)
|
||
|
{
|
||
|
typs2[0] = PathPointTypeLine |
|
||
|
(saveType & ~PathPointTypePathTypeMask);
|
||
|
|
||
|
// Check if the end point of path1 and the start point of path2
|
||
|
// are the same. If so, skip this point.
|
||
|
|
||
|
if(REALABS(pts2[-1].X - pts2[0].X)
|
||
|
+ REALABS(pts2[-1].Y - pts2[0].Y) < POINTF_EPSILON)
|
||
|
{
|
||
|
for(INT i = 0; i < resultCount - 1; i++)
|
||
|
{
|
||
|
pts2[i] = pts2[i + 1];
|
||
|
typs2[i] = typs2[i + 1];
|
||
|
}
|
||
|
resultCount--;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
typs2[0] = PathPointTypeStart |
|
||
|
(saveType & ~PathPointTypePathTypeMask);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
totalCount += resultCount;
|
||
|
|
||
|
return totalCount;
|
||
|
}
|
||
|
|
||
|
/**************************************************************************\
|
||
|
*
|
||
|
* Function Description:
|
||
|
*
|
||
|
* Removes the degenerate points and copy only non-degenerate points.
|
||
|
* It is assumed that points and types array are allocated so that
|
||
|
* they can hold at least "count" number of elements.
|
||
|
*
|
||
|
* Arguments:
|
||
|
*
|
||
|
* [IN] pathType - the type of the path data to be added.
|
||
|
* [OUT] points - the copied data points.
|
||
|
* [OUT] types - the copied data types.
|
||
|
* [IN] dataPoints - the original data points.
|
||
|
* [IN] count - the number of the original data points.
|
||
|
* [IN/OUT] lastPt - the last point.
|
||
|
*
|
||
|
* Return Value:
|
||
|
*
|
||
|
* The total number of copied points.
|
||
|
*
|
||
|
\**************************************************************************/
|
||
|
|
||
|
INT copyNonDegeneratePoints(
|
||
|
BYTE pathType,
|
||
|
GpPointF* points,
|
||
|
BYTE* types,
|
||
|
const GpPointF* dataPoints,
|
||
|
const BYTE* dataTypes,
|
||
|
INT count,
|
||
|
GpPointF* lastPt
|
||
|
)
|
||
|
{
|
||
|
GpPointF nextPt;
|
||
|
INT addedCount = 0;
|
||
|
|
||
|
if(pathType == PathPointTypeLine)
|
||
|
{
|
||
|
// Add only the different points.
|
||
|
|
||
|
for(INT i = 0; i < count; i++)
|
||
|
{
|
||
|
nextPt = *dataPoints++;
|
||
|
|
||
|
if( (REALABS(nextPt.X - lastPt->X) > REAL_EPSILON) ||
|
||
|
(REALABS(nextPt.Y - lastPt->Y) > REAL_EPSILON) )
|
||
|
{
|
||
|
*points++ = nextPt;
|
||
|
*lastPt = nextPt;
|
||
|
addedCount++;
|
||
|
}
|
||
|
}
|
||
|
if(addedCount > 0)
|
||
|
{
|
||
|
GpMemset(types, pathType, addedCount);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// In case of Bezier, we need to do
|
||
|
// degenerate case test for future.
|
||
|
|
||
|
addedCount = count;
|
||
|
|
||
|
if(addedCount > 0)
|
||
|
{
|
||
|
if(dataTypes)
|
||
|
{
|
||
|
GpMemcpy(types, dataTypes, addedCount);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GpMemset(types, pathType, addedCount);
|
||
|
}
|
||
|
|
||
|
GpMemcpy(
|
||
|
points,
|
||
|
dataPoints,
|
||
|
addedCount*sizeof(GpPointF)
|
||
|
);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
addedCount = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return addedCount;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**************************************************************************\
|
||
|
*
|
||
|
* Function Description:
|
||
|
*
|
||
|
* This calculates the major and minor radius of an oval
|
||
|
* when the unit cricle is transformed by the given matrix.
|
||
|
* For further details, see ikkof's notes on Pen Transform.
|
||
|
*
|
||
|
* Arguments:
|
||
|
*
|
||
|
* [OUT] majorR - the major radius.
|
||
|
* [OUT] minorR - the minor radius.
|
||
|
* [IN] matrix - the matrix to transform the unit circle.
|
||
|
*
|
||
|
* Return Value:
|
||
|
*
|
||
|
* Status
|
||
|
*
|
||
|
* 01/28/00 ikkof
|
||
|
* Created it
|
||
|
*
|
||
|
\**************************************************************************/
|
||
|
|
||
|
GpStatus
|
||
|
GetMajorAndMinorAxis(REAL* majorR, REAL* minorR, const GpMatrix* matrix)
|
||
|
{
|
||
|
if(matrix == NULL)
|
||
|
{
|
||
|
// Regard this as an identity matrix.
|
||
|
*majorR = 1;
|
||
|
*minorR = 1;
|
||
|
return Ok;
|
||
|
}
|
||
|
|
||
|
REAL m11 = matrix->GetM11();
|
||
|
REAL m12 = matrix->GetM12();
|
||
|
REAL m21 = matrix->GetM21();
|
||
|
REAL m22 = matrix->GetM22();
|
||
|
|
||
|
REAL d1 = ((m11*m11 + m12*m12) - (m21*m21 + m22*m22))/2;
|
||
|
REAL d2 = m11*m21 + m12*m22;
|
||
|
REAL D = d1*d1 + d2*d2;
|
||
|
if(D > 0)
|
||
|
D = REALSQRT(D);
|
||
|
|
||
|
REAL r0 = (m11*m11 + m12*m12 + m21*m21 + m22*m22)/2;
|
||
|
|
||
|
REAL r1 = REALSQRT(r0 + D);
|
||
|
REAL r2 = REALSQRT(r0 - D);
|
||
|
|
||
|
// They should be positive numbers. Prevent the floating
|
||
|
// point underflow.
|
||
|
|
||
|
if(r1 <= CPLX_EPSILON)
|
||
|
r1 = CPLX_EPSILON;
|
||
|
if(r2 <= CPLX_EPSILON)
|
||
|
r2 = CPLX_EPSILON;
|
||
|
|
||
|
*majorR = r1;
|
||
|
*minorR = r2;
|
||
|
|
||
|
return Ok;
|
||
|
}
|
||
|
|
||
|
VOID
|
||
|
GpPathWidener::Initialize(
|
||
|
const GpPointF* points,
|
||
|
const BYTE* types,
|
||
|
INT count,
|
||
|
const DpPen* pen,
|
||
|
const GpMatrix* matrix,
|
||
|
REAL dpiX, // These parameters are not really used
|
||
|
REAL dpiY, //
|
||
|
BOOL isAntiAliased, // This one is definitely not used.
|
||
|
BOOL isInsetPen
|
||
|
)
|
||
|
{
|
||
|
SetValid(FALSE);
|
||
|
|
||
|
Inset1 = 0;
|
||
|
Inset2 = 0;
|
||
|
NeedsToTransform = FALSE;
|
||
|
IsAntiAliased = isAntiAliased;
|
||
|
|
||
|
// nothing to widen, so return an invalid widener.
|
||
|
|
||
|
if( (!pen) || (count == 0) )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Pen = pen;
|
||
|
GpUnit unit = Pen->Unit;
|
||
|
InsetPenMode = isInsetPen;
|
||
|
|
||
|
if(unit == UnitWorld)
|
||
|
NeedsToTransform = FALSE;
|
||
|
else
|
||
|
NeedsToTransform = TRUE;
|
||
|
|
||
|
GpMatrix penTrans = ((DpPen*) Pen)->Xform;
|
||
|
BOOL hasPenTransform = FALSE;
|
||
|
|
||
|
if(!NeedsToTransform && !penTrans.IsTranslate())
|
||
|
{
|
||
|
hasPenTransform = TRUE;
|
||
|
penTrans.RemoveTranslation();
|
||
|
}
|
||
|
|
||
|
if(matrix)
|
||
|
XForm = *matrix; // Otherwise XForm remains Identity.
|
||
|
|
||
|
if(hasPenTransform)
|
||
|
{
|
||
|
XForm.Prepend(penTrans);
|
||
|
}
|
||
|
|
||
|
|
||
|
DpiX = dpiX;
|
||
|
DpiY = dpiY;
|
||
|
|
||
|
// 0 means use the Desktop DPI
|
||
|
|
||
|
if ((REALABS(DpiX) < REAL_EPSILON) ||
|
||
|
(REALABS(DpiY) < REAL_EPSILON) )
|
||
|
{
|
||
|
DpiX = Globals::DesktopDpiX;
|
||
|
DpiY = Globals::DesktopDpiY;
|
||
|
}
|
||
|
|
||
|
|
||
|
StrokeWidth = Pen->Width;
|
||
|
|
||
|
if(!NeedsToTransform)
|
||
|
{
|
||
|
REAL majorR, minorR;
|
||
|
|
||
|
::GetMajorAndMinorAxis(&majorR, &minorR, &XForm);
|
||
|
MaximumWidth = StrokeWidth*majorR;
|
||
|
MinimumWidth = StrokeWidth*minorR;
|
||
|
UnitScale = min (majorR, minorR);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
UnitScale = ::GetDeviceWidth(1.0f, unit, dpiX);
|
||
|
StrokeWidth = UnitScale * StrokeWidth;
|
||
|
MinimumWidth = StrokeWidth;
|
||
|
}
|
||
|
|
||
|
OriginalStrokeWidth = StrokeWidth;
|
||
|
|
||
|
// Set minimum width to 1.0 (plus a bit for possible precision errors),
|
||
|
// so that narrow width pens don't end up leaving gaps in the line.
|
||
|
REAL minWidth = 1.000001f;
|
||
|
|
||
|
if(InsetPenMode)
|
||
|
{
|
||
|
minWidth *= 2.0f;
|
||
|
|
||
|
// Dashes smaller than a pixel are dropping out entirely in inset
|
||
|
// pen because of the rasterizer pixel level clipping that is taking
|
||
|
// place. We increase the minimum width of dashed lines making them
|
||
|
// roughly 4.0f. This also helps address the weird moire aliasing
|
||
|
// effects with the really small dash-dot round lines.
|
||
|
|
||
|
if(Pen->DashStyle != DashStyleSolid)
|
||
|
{
|
||
|
minWidth *= 2.0f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(!NeedsToTransform)
|
||
|
{
|
||
|
if(MinimumWidth < minWidth)
|
||
|
{
|
||
|
NeedsToTransform = TRUE;
|
||
|
StrokeWidth = minWidth;
|
||
|
MaximumWidth = minWidth;
|
||
|
MinimumWidth = minWidth;
|
||
|
|
||
|
// Ignore the pen transform.
|
||
|
|
||
|
XForm.Reset();
|
||
|
if(matrix)
|
||
|
XForm = *matrix;
|
||
|
|
||
|
hasPenTransform = FALSE;
|
||
|
penTrans.Reset();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
InvXForm = XForm;
|
||
|
if(InvXForm.IsInvertible())
|
||
|
{
|
||
|
InvXForm.Invert();
|
||
|
|
||
|
if(hasPenTransform)
|
||
|
penTrans.Invert();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WARNING(("The matrix is degenerate for path widening constructor."));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const GpPointF* points1 = points;
|
||
|
GpPointF pointBuffer[32];
|
||
|
GpPointF* points2 = pointBuffer;
|
||
|
|
||
|
if((hasPenTransform && !NeedsToTransform)|| (NeedsToTransform && !XForm.IsIdentity()))
|
||
|
{
|
||
|
if(count > 32)
|
||
|
{
|
||
|
points2 = (GpPointF*) GpMalloc(count*sizeof(GpPointF));
|
||
|
}
|
||
|
|
||
|
if(points2)
|
||
|
{
|
||
|
GpMemcpy(points2, points, count*sizeof(GpPointF));
|
||
|
|
||
|
if(hasPenTransform && !NeedsToTransform)
|
||
|
{
|
||
|
// Apply the inverse transform of Pen.
|
||
|
|
||
|
penTrans.Transform(points2, count);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Transform to the device coordinates.
|
||
|
|
||
|
XForm.Transform(points2, count);
|
||
|
}
|
||
|
|
||
|
points1 = points2;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WARNING(("Not enough memory for path widening constructor."));
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
DpPathIterator iter(points1, types, count);
|
||
|
|
||
|
if(!iter.IsValid())
|
||
|
{
|
||
|
if(points2 != pointBuffer)
|
||
|
{
|
||
|
GpFree(points2);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Make sure given poins are not degenerate.
|
||
|
|
||
|
BOOL degenerate = TRUE;
|
||
|
INT k = 1;
|
||
|
|
||
|
while(degenerate && k < count)
|
||
|
{
|
||
|
if(points1[k-1].X != points1[k].X || points1[k-1].Y != points1[k].Y)
|
||
|
degenerate = FALSE;
|
||
|
k++;
|
||
|
}
|
||
|
if(degenerate)
|
||
|
{
|
||
|
if(points2 != pointBuffer)
|
||
|
{
|
||
|
GpFree(points2);
|
||
|
}
|
||
|
WARNING(("Input data is degenerate for widening."));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
GpStatus status = Ok;
|
||
|
INT startIndex, endIndex;
|
||
|
BOOL isClosed;
|
||
|
INT dataCount = 0;
|
||
|
BYTE* ctrTypes = NULL;
|
||
|
GpPointF* ctrPoints = NULL;
|
||
|
GpPointF lastPt, nextPt;
|
||
|
|
||
|
while(iter.NextSubpath(&startIndex, &endIndex, &isClosed) && status == Ok)
|
||
|
{
|
||
|
INT typeStartIndex, typeEndIndex;
|
||
|
BYTE pathType;
|
||
|
BOOL isFirstPoint = TRUE;
|
||
|
|
||
|
while(iter.NextPathType(&pathType, &typeStartIndex, &typeEndIndex)
|
||
|
&& status == Ok)
|
||
|
{
|
||
|
INT segmentCount;
|
||
|
const GpPointF* dataPoints = NULL;
|
||
|
const BYTE* dataTypes = NULL;
|
||
|
|
||
|
nextPt = points1[typeStartIndex];
|
||
|
|
||
|
switch(pathType)
|
||
|
{
|
||
|
case PathPointTypeStart:
|
||
|
break;
|
||
|
|
||
|
case PathPointTypeBezier:
|
||
|
|
||
|
// The path must be flattened before calling widen.
|
||
|
|
||
|
ASSERT(FALSE);
|
||
|
break;
|
||
|
|
||
|
case PathPointTypeLine:
|
||
|
default: // And all other types are treated as line points.
|
||
|
|
||
|
// Get the data for the Line segment.
|
||
|
|
||
|
segmentCount = typeEndIndex - typeStartIndex + 1;
|
||
|
dataPoints = points1 + typeStartIndex;
|
||
|
dataTypes = NULL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if(status == Ok && pathType != PathPointTypeStart)
|
||
|
{
|
||
|
// Allocate memory for CenterTypes and CenterPoints.
|
||
|
|
||
|
status = CenterTypes.ReserveSpace(segmentCount);
|
||
|
if(status == Ok)
|
||
|
status = CenterPoints.ReserveSpace(segmentCount);
|
||
|
|
||
|
if(status == Ok)
|
||
|
{
|
||
|
ctrTypes = CenterTypes.GetDataBuffer();
|
||
|
ctrPoints = CenterPoints.GetDataBuffer();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ctrTypes = NULL;
|
||
|
ctrPoints = NULL;
|
||
|
}
|
||
|
|
||
|
if(ctrTypes && ctrPoints)
|
||
|
{
|
||
|
BYTE nextType;
|
||
|
|
||
|
INT count = CenterTypes.GetCount();
|
||
|
ctrTypes += count;
|
||
|
ctrPoints += count;
|
||
|
|
||
|
dataCount = 0;
|
||
|
|
||
|
// Add the first point.
|
||
|
|
||
|
if(isFirstPoint)
|
||
|
{
|
||
|
// We must check the dash mode
|
||
|
// for the first point of the subpath.
|
||
|
|
||
|
nextType = PathPointTypeStart;
|
||
|
if(iter.IsDashMode(typeStartIndex))
|
||
|
nextType |= PathPointTypeDashMode;
|
||
|
else
|
||
|
nextType &= ~PathPointTypeDashMode;
|
||
|
|
||
|
*ctrTypes++ = nextType;
|
||
|
*ctrPoints++ = nextPt;
|
||
|
lastPt = nextPt;
|
||
|
isFirstPoint = FALSE;
|
||
|
dataCount++;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Don't copy the first
|
||
|
// if it is the same as the last point.
|
||
|
|
||
|
if(lastPt.X != nextPt.X || lastPt.Y != nextPt.Y)
|
||
|
{
|
||
|
// We don't have to check dash mode
|
||
|
// for the intermediate points.
|
||
|
|
||
|
nextType = PathPointTypeLine;
|
||
|
|
||
|
*ctrTypes++ = nextType;
|
||
|
*ctrPoints++ = nextPt;
|
||
|
lastPt = nextPt;
|
||
|
dataCount++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Add the remaining points.
|
||
|
|
||
|
segmentCount--;
|
||
|
dataPoints++;
|
||
|
if(dataTypes)
|
||
|
dataTypes++;
|
||
|
INT addedCount = copyNonDegeneratePoints(
|
||
|
pathType,
|
||
|
ctrPoints,
|
||
|
ctrTypes,
|
||
|
dataPoints,
|
||
|
dataTypes,
|
||
|
segmentCount,
|
||
|
&lastPt);
|
||
|
dataCount += addedCount;
|
||
|
|
||
|
CenterTypes.AdjustCount(dataCount);
|
||
|
CenterPoints.AdjustCount(dataCount);
|
||
|
}
|
||
|
else
|
||
|
status = OutOfMemory;
|
||
|
}
|
||
|
lastPt = points1[typeEndIndex];
|
||
|
}
|
||
|
|
||
|
if(status == Ok)
|
||
|
{
|
||
|
ctrTypes = CenterTypes.GetDataBuffer();
|
||
|
dataCount = CenterTypes.GetCount();
|
||
|
|
||
|
if(isClosed)
|
||
|
ctrTypes[dataCount - 1] |= PathPointTypeCloseSubpath;
|
||
|
else
|
||
|
ctrTypes[dataCount - 1] &= ~PathPointTypeCloseSubpath;
|
||
|
|
||
|
// We must check the dash mode for the last
|
||
|
// point of the subpath.
|
||
|
|
||
|
if(iter.IsDashMode(endIndex))
|
||
|
ctrTypes[dataCount - 1] |= PathPointTypeDashMode;
|
||
|
else
|
||
|
ctrTypes[dataCount - 1] &= ~PathPointTypeDashMode;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(points2 != pointBuffer)
|
||
|
{
|
||
|
GpFree(points2);
|
||
|
}
|
||
|
|
||
|
if(status == Ok)
|
||
|
{
|
||
|
ctrPoints = CenterPoints.GetDataBuffer();
|
||
|
ctrTypes = CenterTypes.GetDataBuffer();
|
||
|
dataCount = CenterPoints.GetCount();
|
||
|
|
||
|
Iterator.SetData(ctrPoints, ctrTypes, dataCount);
|
||
|
SetValid(Iterator.IsValid());
|
||
|
|
||
|
#ifdef DEBUG_PATHWIDENER
|
||
|
|
||
|
if(!IsValid())
|
||
|
{
|
||
|
WARNING(("PathWidener is invalid."));
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**************************************************************************\
|
||
|
*
|
||
|
* Function Description:
|
||
|
*
|
||
|
* Calculates the unit gradient vectors of points as array of
|
||
|
* (count + 1). All the memories must be allocated and be checked
|
||
|
* by the caller.
|
||
|
*
|
||
|
* The first element of the gradient is from the end point to the
|
||
|
* the start point. If the end point is identical to the start point,
|
||
|
* the previous point is used.
|
||
|
* The last element of the gradient is from the start point to the end
|
||
|
* point. If the start point is identical to the end point, the next
|
||
|
* point is used.
|
||
|
* If distances array is not NULL, this returns the distance of each
|
||
|
* segments.
|
||
|
*
|
||
|
* Arguments:
|
||
|
*
|
||
|
* [OUT] grad - The gradient array of (count + 1) elements.
|
||
|
* [OUT] distances - The distance array of (count + 1) elements or NULL.
|
||
|
* [IN] points - The given points of count elements.
|
||
|
* [IN] count - The number of given points.
|
||
|
*
|
||
|
* Return Value:
|
||
|
*
|
||
|
* Status
|
||
|
*
|
||
|
\**************************************************************************/
|
||
|
|
||
|
GpStatus
|
||
|
CalculateGradientArray(
|
||
|
GpPointF* grad,
|
||
|
REAL* distances,
|
||
|
const GpPointF* points,
|
||
|
INT count
|
||
|
)
|
||
|
{
|
||
|
GpPointF* grad1 = grad;
|
||
|
REAL* distances1 = distances;
|
||
|
const GpPointF* points1 = points;
|
||
|
|
||
|
// Go to the starting point of this subpath.
|
||
|
|
||
|
GpPointF startPt, endPt, lastPt, nextPt;
|
||
|
|
||
|
startPt = *points1;
|
||
|
|
||
|
INT i = count - 1;
|
||
|
BOOL different = FALSE;
|
||
|
points1 += i; // Go to the end point.
|
||
|
|
||
|
while(i > 0 && !different)
|
||
|
{
|
||
|
endPt = *points1--;
|
||
|
|
||
|
if(endPt.X != startPt.X || endPt.Y != startPt.Y)
|
||
|
different = TRUE;
|
||
|
|
||
|
i--;
|
||
|
}
|
||
|
|
||
|
if(!different)
|
||
|
{
|
||
|
// All points are the same.
|
||
|
|
||
|
WARNING(("Trying to calculate the gradients for degenerate points."));
|
||
|
return GenericError;
|
||
|
}
|
||
|
|
||
|
points1 = points;
|
||
|
lastPt = endPt;
|
||
|
|
||
|
i = 0;
|
||
|
|
||
|
while(i <= count)
|
||
|
{
|
||
|
REAL dx, dy, d;
|
||
|
|
||
|
if(i < count)
|
||
|
nextPt = *points1++;
|
||
|
else
|
||
|
nextPt = startPt;
|
||
|
|
||
|
dx = nextPt.X - lastPt.X;
|
||
|
dy = nextPt.Y - lastPt.Y;
|
||
|
d = dx*dx + dy*dy;
|
||
|
|
||
|
if(d > 0)
|
||
|
{
|
||
|
d = REALSQRT(d);
|
||
|
dx /= d;
|
||
|
dy /= d;
|
||
|
}
|
||
|
grad1->X = dx;
|
||
|
grad1->Y = dy;
|
||
|
|
||
|
// Record the distance only when the given distance array is not NULL.
|
||
|
|
||
|
if(distances)
|
||
|
*distances1++ = d;
|
||
|
|
||
|
grad1++;
|
||
|
lastPt = nextPt;
|
||
|
i++;
|
||
|
}
|
||
|
|
||
|
// Make sure the last gradient is not 0.
|
||
|
|
||
|
grad1 = grad + count;
|
||
|
if(grad1->X == 0 && grad1->Y == 0)
|
||
|
{
|
||
|
// The start and end point are the same. Find
|
||
|
// the next non-zero gradient.
|
||
|
|
||
|
i = 1;
|
||
|
grad1 = grad + i;
|
||
|
|
||
|
while(i < count)
|
||
|
{
|
||
|
if(grad1->X != 0 || grad1->Y != 0)
|
||
|
{
|
||
|
grad[count] = *grad1;
|
||
|
|
||
|
if(distances)
|
||
|
distances[count] = distances[i];
|
||
|
break;
|
||
|
}
|
||
|
i++;
|
||
|
grad1++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return Ok;
|
||
|
}
|
||
|
|
||
|
/**************************************************************************\
|
||
|
*
|
||
|
* Function Description:
|
||
|
*
|
||
|
* Calculates the normal gradient of points between startIndex
|
||
|
* and endIndex. The last element of the gradient is from endIndex
|
||
|
* to startIndex. If the next point is identical to the previous point,
|
||
|
* the gradient is set to (0, 0).
|
||
|
*
|
||
|
* Arguments:
|
||
|
*
|
||
|
* [IN] startIndex - the starting index.
|
||
|
* [IN] endIndex - the ending index.
|
||
|
*
|
||
|
* Return Value:
|
||
|
*
|
||
|
* Status
|
||
|
*
|
||
|
\**************************************************************************/
|
||
|
|
||
|
GpStatus
|
||
|
GpPathWidener::CalculateGradients(INT startIndex, INT endIndex)
|
||
|
{
|
||
|
GpPointF* points0 = CenterPoints.GetDataBuffer();
|
||
|
INT count = endIndex - startIndex + 1;
|
||
|
|
||
|
if(!points0 || count <= 0)
|
||
|
return GenericError;
|
||
|
|
||
|
Gradients.Reset(FALSE);
|
||
|
|
||
|
GpPointF* grad = Gradients.AddMultiple(count + 1);
|
||
|
|
||
|
if(!grad)
|
||
|
return OutOfMemory;
|
||
|
|
||
|
GpPointF* points = points0 + startIndex;
|
||
|
|
||
|
return CalculateGradientArray(grad, NULL, points, count);
|
||
|
}
|
||
|
|
||
|
|
||
|
GpStatus
|
||
|
GpPathWidener::CalculateNormals(
|
||
|
REAL leftWidth,
|
||
|
REAL rightWidth
|
||
|
)
|
||
|
{
|
||
|
NeedsToAdjustNormals = FALSE;
|
||
|
|
||
|
INT count = Gradients.GetCount();
|
||
|
GpPointF* grad0 = Gradients.GetDataBuffer();
|
||
|
|
||
|
if(count <= 0)
|
||
|
{
|
||
|
WARNING(("Gradients must be calculated\n"
|
||
|
"before the normals are calculated."));
|
||
|
return GenericError;
|
||
|
}
|
||
|
|
||
|
Normals.Reset(FALSE);
|
||
|
GpPointF* norm0 = Normals.AddMultiple(count);
|
||
|
|
||
|
if(!norm0)
|
||
|
return OutOfMemory;
|
||
|
|
||
|
|
||
|
GpPointF* norm = norm0;
|
||
|
GpPointF* grad = grad0;
|
||
|
|
||
|
// Calculate the left normals.
|
||
|
|
||
|
INT i;
|
||
|
|
||
|
for(i = 0; i < count; i++)
|
||
|
{
|
||
|
norm->X = grad->Y;
|
||
|
norm->Y = - grad->X;
|
||
|
norm++;
|
||
|
grad++;
|
||
|
}
|
||
|
|
||
|
if(IsAntiAliased)
|
||
|
return Ok;
|
||
|
|
||
|
// Check if the minimum width is less than 1.0.
|
||
|
|
||
|
REAL width = REALABS(leftWidth - rightWidth);
|
||
|
|
||
|
if(width*MinimumWidth >= 1.0f)
|
||
|
return Ok;
|
||
|
|
||
|
NeedsToAdjustNormals = TRUE;
|
||
|
|
||
|
if(!NeedsToTransform)
|
||
|
{
|
||
|
// Transform to the device space.
|
||
|
// When NeedsToTransform is TRUE, the gradient is already
|
||
|
// calculated in the device coordinates.
|
||
|
|
||
|
if(!XForm.IsIdentity())
|
||
|
XForm.VectorTransform(norm0, count);
|
||
|
}
|
||
|
|
||
|
// Set the minimum line width to be just over 1.0
|
||
|
REAL criteria = 1.00005f;
|
||
|
REAL value;
|
||
|
|
||
|
if(width > 0)
|
||
|
value = criteria/width;
|
||
|
else
|
||
|
value = criteria*MinimumWidth/1.0f;
|
||
|
|
||
|
norm = norm0;
|
||
|
|
||
|
for(i = 0; i < count; i++)
|
||
|
{
|
||
|
REAL xx = REALABS(norm->X);
|
||
|
REAL yy = REALABS(norm->Y);
|
||
|
|
||
|
if(xx >= yy)
|
||
|
{
|
||
|
if(width*xx < criteria)
|
||
|
{
|
||
|
if(norm->X >= 0)
|
||
|
norm->X = value;
|
||
|
else
|
||
|
norm->X = - value;
|
||
|
|
||
|
norm->Y = 0;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(width*yy < criteria)
|
||
|
{
|
||
|
if(norm->Y >= 0)
|
||
|
norm->Y = value;
|
||
|
else
|
||
|
norm->Y = - value;
|
||
|
|
||
|
norm->X = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
norm++;
|
||
|
}
|
||
|
|
||
|
if(!NeedsToTransform)
|
||
|
{
|
||
|
// Transform back to the world space in case of
|
||
|
// a non fixed width pen.
|
||
|
|
||
|
if(!InvXForm.IsIdentity())
|
||
|
InvXForm.VectorTransform(norm0, count);
|
||
|
}
|
||
|
|
||
|
return Ok;
|
||
|
}
|
||
|
|
||
|
GpStatus
|
||
|
GpPathWidener::Widen(GpPath **path)
|
||
|
{
|
||
|
// Must be a pointer to a path pointer that is NULL.
|
||
|
|
||
|
ASSERT(path != NULL);
|
||
|
ASSERT(*path == NULL);
|
||
|
|
||
|
GpStatus status = Ok;
|
||
|
|
||
|
// Get the widened path points and types into the
|
||
|
// dynarray objects.
|
||
|
|
||
|
DynPointFArray widenedPoints;
|
||
|
DynByteArray widenedTypes;
|
||
|
|
||
|
status = Widen(
|
||
|
&widenedPoints,
|
||
|
&widenedTypes
|
||
|
);
|
||
|
|
||
|
if(status != Ok) { return status; }
|
||
|
|
||
|
// Remove the internal flag.
|
||
|
|
||
|
INT pathCount = widenedTypes.GetCount();
|
||
|
BYTE* pathTypes = widenedTypes.GetDataBuffer();
|
||
|
for(INT i = 0; i < pathCount; i++, pathTypes++)
|
||
|
{
|
||
|
if(*pathTypes & PathPointTypeInternalUse)
|
||
|
{
|
||
|
*pathTypes &= ~PathPointTypeInternalUse;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If everything worked, create a new path
|
||
|
// from the widened points.
|
||
|
|
||
|
if(status == Ok)
|
||
|
{
|
||
|
*path = new GpPath(
|
||
|
widenedPoints.GetDataBuffer(),
|
||
|
widenedTypes.GetDataBuffer(),
|
||
|
widenedPoints.GetCount(),
|
||
|
FillModeWinding
|
||
|
);
|
||
|
|
||
|
if(*path == NULL) { status = OutOfMemory; }
|
||
|
}
|
||
|
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
GpStatus
|
||
|
GpPathWidener::Widen(
|
||
|
DynPointFArray* widenedPoints,
|
||
|
DynByteArray* widenedTypes
|
||
|
)
|
||
|
{
|
||
|
FPUStateSaver::AssertMode();
|
||
|
|
||
|
if(!IsValid() || !widenedPoints || !widenedTypes)
|
||
|
{
|
||
|
return InvalidParameter;
|
||
|
}
|
||
|
|
||
|
const GpPointF* centerPoints = CenterPoints.GetDataBuffer();
|
||
|
const BYTE* centerTypes = CenterTypes.GetDataBuffer();
|
||
|
INT centerCount = CenterPoints.GetCount();
|
||
|
|
||
|
INT startIndex, endIndex;
|
||
|
BOOL isClosed;
|
||
|
GpStatus status = Ok;
|
||
|
|
||
|
DynPointFArray customStartCapPoints;
|
||
|
DynPointFArray customEndCapPoints;
|
||
|
DynByteArray customStartCapTypes;
|
||
|
DynByteArray customEndCapTypes;
|
||
|
|
||
|
// Clear the data in widenedPoints and widenedTypes.
|
||
|
|
||
|
widenedPoints->Reset(FALSE);
|
||
|
widenedTypes->Reset(FALSE);
|
||
|
|
||
|
REAL width = OriginalStrokeWidth;
|
||
|
|
||
|
INT compoundCount = Pen->CompoundCount;
|
||
|
|
||
|
REAL* compoundArray = NULL;
|
||
|
INT cpCount;
|
||
|
|
||
|
if(compoundCount > 0)
|
||
|
cpCount = compoundCount;
|
||
|
else
|
||
|
cpCount = 2;
|
||
|
|
||
|
REAL compoundArray0[8];
|
||
|
|
||
|
if(cpCount <= 8)
|
||
|
compoundArray = &compoundArray0[0];
|
||
|
else
|
||
|
compoundArray = (REAL*) GpMalloc(cpCount*sizeof(REAL));
|
||
|
|
||
|
INT kk;
|
||
|
|
||
|
if(compoundArray)
|
||
|
{
|
||
|
// Don't attempt to draw a compound line that is empty, or is aliased and has
|
||
|
// more line components than the width of the line. It can be rounded out of
|
||
|
// existance
|
||
|
if(compoundCount > 0 &&
|
||
|
(IsAntiAliased || (compoundCount / 2) <= (width * UnitScale)))
|
||
|
{
|
||
|
// Don't attempt to draw a compound line that is less than 0.5 device
|
||
|
// units in width. These can disappear when rasterized, depending on
|
||
|
// what Y coordinate they fall on.
|
||
|
if ((UnitScale * width) >= 0.5f)
|
||
|
{
|
||
|
GpMemcpy(compoundArray, Pen->CompoundArray, compoundCount*sizeof(REAL));
|
||
|
|
||
|
for(kk = 0; kk < compoundCount; kk++)
|
||
|
{
|
||
|
compoundArray[kk] *= width;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
compoundArray[0] = 0;
|
||
|
compoundArray[1] = 0.5f;
|
||
|
if (cpCount > 2)
|
||
|
{
|
||
|
cpCount = 2;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
compoundArray[0] = 0;
|
||
|
compoundArray[1] = StrokeWidth;
|
||
|
if (cpCount > 2)
|
||
|
{
|
||
|
cpCount = 2;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SetValid(FALSE);
|
||
|
return OutOfMemory;
|
||
|
}
|
||
|
|
||
|
|
||
|
REAL left0, right0;
|
||
|
BOOL isCenteredPen = FALSE;
|
||
|
BOOL needsToFlip = FALSE;
|
||
|
|
||
|
if(Pen->PenAlignment == PenAlignmentInset)
|
||
|
{
|
||
|
// Check if the coordinates are flipped.
|
||
|
// If the determinant of the transform matrix is negative,
|
||
|
// the coordinate system is flipped.
|
||
|
|
||
|
if(XForm.IsInvertible() && XForm.GetDeterminant() < 0)
|
||
|
needsToFlip = TRUE;
|
||
|
}
|
||
|
|
||
|
// OriginalStrokeWidth is required for the compound lines, but we're
|
||
|
// widening now and StrokeWidth == max(OriginalStrokeWidth, MinimumWidth)
|
||
|
// which is the value we need to widen at in order to avoid dropping out
|
||
|
// lines.
|
||
|
|
||
|
width = StrokeWidth;
|
||
|
|
||
|
switch(Pen->PenAlignment)
|
||
|
{
|
||
|
case PenAlignmentInset:
|
||
|
if(!needsToFlip)
|
||
|
left0 = 0; // Same as right align.
|
||
|
else
|
||
|
left0 = width; // Same as left align.
|
||
|
break;
|
||
|
|
||
|
case PenAlignmentCenter:
|
||
|
default:
|
||
|
left0 = width/2;
|
||
|
isCenteredPen = TRUE;
|
||
|
}
|
||
|
|
||
|
right0 = left0 - width;
|
||
|
|
||
|
REAL startInset0 = 0, endInset0 = 0;
|
||
|
GpCustomLineCap* customStartCap = NULL;
|
||
|
GpCustomLineCap* customEndCap = NULL;
|
||
|
|
||
|
while(Iterator.NextSubpath(&startIndex, &endIndex, &isClosed)
|
||
|
&& status == Ok)
|
||
|
{
|
||
|
GpLineCap startCap = Pen->StartCap;
|
||
|
GpLineCap endCap = Pen->EndCap;
|
||
|
BYTE startType = centerTypes[startIndex];
|
||
|
BYTE endType = centerTypes[endIndex];
|
||
|
|
||
|
GpLineCapMode startCapMode = LineCapDefaultMode;
|
||
|
GpLineCapMode endCapMode = LineCapDefaultMode;
|
||
|
|
||
|
if(startType & PathPointTypeDashMode)
|
||
|
{
|
||
|
startCap = Pen->DashCap;
|
||
|
startCapMode = LineCapDashMode;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// If the start cap is one of the anchor caps, default the widener
|
||
|
// to using the dashcap for the startCap.
|
||
|
|
||
|
if(((startCap & LineCapAnchorMask) != 0) ||
|
||
|
(startCap == LineCapCustom))
|
||
|
{
|
||
|
startCap = Pen->DashCap;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(endType & PathPointTypeDashMode)
|
||
|
{
|
||
|
endCap = Pen->DashCap;
|
||
|
endCapMode = LineCapDashMode;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// If the end cap is one of the anchor caps, default the widener
|
||
|
// to using the dashcap for the endCap.
|
||
|
|
||
|
if(((endCap & LineCapAnchorMask) != 0) ||
|
||
|
(endCap == LineCapCustom))
|
||
|
{
|
||
|
endCap = Pen->DashCap;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(InsetPenMode)
|
||
|
{
|
||
|
// Inset pen only supports these caps.
|
||
|
|
||
|
if(endCap != LineCapRound && endCap != LineCapFlat)
|
||
|
{
|
||
|
endCap = LineCapFlat;
|
||
|
}
|
||
|
if(startCap != LineCapRound && startCap != LineCapFlat)
|
||
|
{
|
||
|
startCap = LineCapFlat;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Inset1 = Inset2 = 0.0f;
|
||
|
if(startCap == LineCapSquare)
|
||
|
{
|
||
|
Inset1 = -0.5f*StrokeWidth;
|
||
|
}
|
||
|
if(endCap == LineCapSquare)
|
||
|
{
|
||
|
Inset2 = -0.5f*StrokeWidth;
|
||
|
}
|
||
|
|
||
|
status = CalculateGradients(startIndex, endIndex);
|
||
|
|
||
|
kk = 0;
|
||
|
|
||
|
BOOL isCompoundLine = FALSE;
|
||
|
GpLineCap startCap1 = startCap;
|
||
|
GpLineCap endCap1 = endCap;
|
||
|
|
||
|
if(cpCount > 2)
|
||
|
{
|
||
|
// Don't add the caps for the individual
|
||
|
// compound line.
|
||
|
|
||
|
isCompoundLine = TRUE;
|
||
|
startCap1 = LineCapFlat;
|
||
|
endCap1 = LineCapFlat;
|
||
|
}
|
||
|
|
||
|
while(kk < cpCount && status == Ok)
|
||
|
{
|
||
|
REAL leftWidth = left0 - compoundArray[kk];
|
||
|
REAL rightWidth = left0 - compoundArray[kk + 1];
|
||
|
|
||
|
if(REALABS(leftWidth-rightWidth)>REAL_EPSILON)
|
||
|
{
|
||
|
status = CalculateNormals(leftWidth, rightWidth);
|
||
|
if(status != Ok) { break; }
|
||
|
|
||
|
// Check if we can use the Bevel join for inside lines.
|
||
|
|
||
|
BOOL useBevelJoinInside = isCenteredPen && !isCompoundLine;
|
||
|
|
||
|
if(USE_POLYGON_JOIN)
|
||
|
{
|
||
|
status = SetPolygonJoin(leftWidth, rightWidth, FALSE);
|
||
|
}
|
||
|
|
||
|
status = WidenSubpath(
|
||
|
widenedPoints,
|
||
|
widenedTypes,
|
||
|
leftWidth,
|
||
|
rightWidth,
|
||
|
startIndex,
|
||
|
endIndex,
|
||
|
isClosed,
|
||
|
startCap1,
|
||
|
endCap1,
|
||
|
useBevelJoinInside
|
||
|
);
|
||
|
|
||
|
Iterator.RewindSubpath();
|
||
|
}
|
||
|
|
||
|
kk += 2;
|
||
|
}
|
||
|
// Add the compound line caps if necessary.
|
||
|
|
||
|
if(status == Ok && isCompoundLine && !isClosed)
|
||
|
{
|
||
|
status = AddCompoundCaps(
|
||
|
widenedPoints,
|
||
|
widenedTypes,
|
||
|
left0,
|
||
|
right0,
|
||
|
startIndex,
|
||
|
endIndex,
|
||
|
startCap,
|
||
|
endCap
|
||
|
);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
if(status != Ok) { return status; }
|
||
|
|
||
|
GpPointF* pts;
|
||
|
INT count;
|
||
|
|
||
|
if(!NeedsToTransform)
|
||
|
{
|
||
|
GpMatrix penTrans = ((DpPen*) Pen)->Xform;
|
||
|
BOOL hasPenTransform = FALSE;
|
||
|
|
||
|
if(!penTrans.IsTranslate())
|
||
|
{
|
||
|
hasPenTransform = TRUE;
|
||
|
penTrans.RemoveTranslation();
|
||
|
|
||
|
pts = widenedPoints->GetDataBuffer();
|
||
|
count = widenedPoints->GetCount();
|
||
|
penTrans.Transform(pts, count);
|
||
|
}
|
||
|
}
|
||
|
else if(!InvXForm.IsIdentity())
|
||
|
{
|
||
|
// Case of the Fixed width pen.
|
||
|
|
||
|
pts = widenedPoints->GetDataBuffer();
|
||
|
count = widenedPoints->GetCount();
|
||
|
InvXForm.Transform(pts, count);
|
||
|
}
|
||
|
|
||
|
#ifdef DEBUG_PATHWIDENER
|
||
|
|
||
|
if(status == Ok)
|
||
|
{
|
||
|
DpPathTypeIterator iter2(widenedTypes->GetDataBuffer(),
|
||
|
widenedTypes->GetCount());
|
||
|
|
||
|
if(!iter2.IsValid())
|
||
|
{
|
||
|
WARNING(("Widening result is not valid."));
|
||
|
status = GenericError;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
if(status == Ok)
|
||
|
SetValid(TRUE);
|
||
|
|
||
|
if(compoundArray != &compoundArray0[0])
|
||
|
GpFree(compoundArray);
|
||
|
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
|
||
|
GpStatus
|
||
|
GpPathWidener::WidenSubpath(
|
||
|
DynPointFArray* widenedPoints,
|
||
|
DynByteArray* widenedTypes,
|
||
|
REAL leftWidth,
|
||
|
REAL rightWidth,
|
||
|
INT startIndex,
|
||
|
INT endIndex,
|
||
|
BOOL isClosed,
|
||
|
GpLineCap startCap,
|
||
|
GpLineCap endCap,
|
||
|
BOOL useBevelJoinInside
|
||
|
)
|
||
|
{
|
||
|
const GpPointF* centerPoints = CenterPoints.GetDataBuffer();
|
||
|
const BYTE* centerTypes = CenterTypes.GetDataBuffer();
|
||
|
INT centerCount = CenterPoints.GetCount();
|
||
|
|
||
|
GpLineJoin lineJoin = Pen->Join;
|
||
|
REAL miterLimit2 = Pen->MiterLimit;
|
||
|
miterLimit2 *= miterLimit2;
|
||
|
|
||
|
INT typeStartIndex, typeEndIndex;
|
||
|
BYTE pathType;
|
||
|
BOOL isFirstType = TRUE;
|
||
|
BOOL isLastType = FALSE;
|
||
|
BOOL isLastPointSame;
|
||
|
GpPointF startPt, endPt;
|
||
|
|
||
|
startPt = centerPoints[startIndex];
|
||
|
endPt = centerPoints[endIndex];
|
||
|
|
||
|
if(startPt.X != endPt.X || startPt.Y != endPt.Y)
|
||
|
isLastPointSame = FALSE;
|
||
|
else
|
||
|
isLastPointSame = TRUE;
|
||
|
|
||
|
// Reset the left and right buffers.
|
||
|
|
||
|
LeftTypes.Reset(FALSE);
|
||
|
LeftPoints.Reset(FALSE);
|
||
|
RightTypes.Reset(FALSE);
|
||
|
RightPoints.Reset(FALSE);
|
||
|
|
||
|
INT subpathCount = endIndex - startIndex + 1;
|
||
|
INT joinMultiplier = 2;
|
||
|
|
||
|
if(lineJoin == LineJoinRound)
|
||
|
joinMultiplier = 7;
|
||
|
|
||
|
GpStatus status = LeftTypes.ReserveSpace(joinMultiplier*subpathCount);
|
||
|
if(status == Ok)
|
||
|
status = LeftPoints.ReserveSpace(joinMultiplier*subpathCount);
|
||
|
if(status == Ok)
|
||
|
status = RightTypes.ReserveSpace(joinMultiplier*subpathCount);
|
||
|
if(status == Ok)
|
||
|
status = RightPoints.ReserveSpace(joinMultiplier*subpathCount);
|
||
|
|
||
|
if(status != Ok)
|
||
|
return status;
|
||
|
|
||
|
// Get Gradient data buffer.
|
||
|
|
||
|
GpPointF *grad0, *norm0;
|
||
|
|
||
|
grad0 = Gradients.GetDataBuffer();
|
||
|
norm0 = Normals.GetDataBuffer();
|
||
|
|
||
|
// Get Left and Right data buffers.
|
||
|
|
||
|
GpPointF* leftPoints0 = LeftPoints.GetDataBuffer();
|
||
|
BYTE* leftTypes0 = LeftTypes.GetDataBuffer();
|
||
|
GpPointF* rightPoints0 = RightPoints.GetDataBuffer();
|
||
|
BYTE* rightTypes0 = RightTypes.GetDataBuffer();
|
||
|
|
||
|
GpPointF lastPt, nextPt;
|
||
|
GpPointF leftEndPt, rightEndPt;
|
||
|
|
||
|
INT leftCount = 0, rightCount = 0;
|
||
|
|
||
|
INT flag = 0;
|
||
|
|
||
|
if(isClosed)
|
||
|
flag |= WideningClosed;
|
||
|
if(isFirstType)
|
||
|
flag |= WideningFirstType;
|
||
|
if(isLastType)
|
||
|
flag |= WideningLastType;
|
||
|
if(isLastPointSame)
|
||
|
flag |= WideningLastPointSame;
|
||
|
if(NeedsToAdjustNormals)
|
||
|
flag |= WideningNeedsToAdjustNormals;
|
||
|
if(useBevelJoinInside)
|
||
|
flag |= WideningUseBevelJoinInside;
|
||
|
|
||
|
const GpPointF* dataPoints = centerPoints + startIndex;
|
||
|
INT dataCount = endIndex - startIndex + 1;
|
||
|
|
||
|
REAL firstInsets[2], lastInsets[2];
|
||
|
|
||
|
// Never inset more than the length of the line for the first inset, and
|
||
|
// never more than the amount left on the line after the first inset
|
||
|
// has been applied. This can result in odd endcaps and dashcaps when you
|
||
|
// have a line that ends in the middle of a short dash segment.
|
||
|
|
||
|
REAL linelength = REALSQRT(
|
||
|
distance_squared(
|
||
|
centerPoints[startIndex],
|
||
|
centerPoints[endIndex]
|
||
|
)
|
||
|
);
|
||
|
|
||
|
firstInsets[0] = min(Inset1, linelength);
|
||
|
firstInsets[1] = min(Inset1, linelength);
|
||
|
lastInsets[0] = min(Inset2, linelength-firstInsets[0]);
|
||
|
lastInsets[1] = min(Inset2, linelength-firstInsets[1]);
|
||
|
|
||
|
WidenFirstPoint(
|
||
|
leftWidth,
|
||
|
rightWidth,
|
||
|
lineJoin,
|
||
|
miterLimit2,
|
||
|
leftPoints0,
|
||
|
leftTypes0,
|
||
|
&leftCount,
|
||
|
rightPoints0,
|
||
|
rightTypes0,
|
||
|
&rightCount,
|
||
|
&leftEndPt,
|
||
|
&rightEndPt,
|
||
|
grad0,
|
||
|
norm0,
|
||
|
dataPoints,
|
||
|
dataCount,
|
||
|
&lastPt,
|
||
|
&firstInsets[0],
|
||
|
flag
|
||
|
);
|
||
|
|
||
|
// Iterate through all subtypes in the current subpath.
|
||
|
|
||
|
while(Iterator.NextPathType(&pathType, &typeStartIndex, &typeEndIndex)
|
||
|
&& status == Ok)
|
||
|
{
|
||
|
// Offset index from the current subpath.
|
||
|
|
||
|
INT offsetIndex = typeStartIndex - startIndex;
|
||
|
GpPointF* grad = grad0 + offsetIndex;
|
||
|
GpPointF* norm = norm0 + offsetIndex;
|
||
|
|
||
|
// Get the starting data buffers of the current subtypes.
|
||
|
|
||
|
dataPoints = centerPoints + typeStartIndex;
|
||
|
dataCount = typeEndIndex - typeStartIndex + 1;
|
||
|
|
||
|
// Get the starting buffers for the left and right data.
|
||
|
|
||
|
GpPointF* leftPoints = leftPoints0 + leftCount;
|
||
|
BYTE* leftTypes = leftTypes0 + leftCount;
|
||
|
GpPointF* rightPoints = rightPoints0 + rightCount;
|
||
|
BYTE* rightTypes = rightTypes0 + rightCount;
|
||
|
|
||
|
INT addedLeftCount = 0, addedRightCount = 0;
|
||
|
|
||
|
if(pathType != PathPointTypeStart)
|
||
|
{
|
||
|
if(typeEndIndex == endIndex)
|
||
|
isLastType = TRUE;
|
||
|
|
||
|
flag = 0;
|
||
|
|
||
|
if(isClosed)
|
||
|
flag |= WideningClosed;
|
||
|
if(isFirstType)
|
||
|
flag |= WideningFirstType;
|
||
|
if(isLastType)
|
||
|
flag |= WideningLastType;
|
||
|
if(isLastPointSame)
|
||
|
flag |= WideningLastPointSame;
|
||
|
if(NeedsToAdjustNormals)
|
||
|
flag |= WideningNeedsToAdjustNormals;
|
||
|
if(useBevelJoinInside)
|
||
|
flag |= WideningUseBevelJoinInside;
|
||
|
|
||
|
status = WidenEachPathType(
|
||
|
pathType,
|
||
|
leftWidth,
|
||
|
rightWidth,
|
||
|
lineJoin,
|
||
|
miterLimit2,
|
||
|
leftPoints,
|
||
|
leftTypes,
|
||
|
&addedLeftCount,
|
||
|
rightPoints,
|
||
|
rightTypes,
|
||
|
&addedRightCount,
|
||
|
grad,
|
||
|
norm,
|
||
|
dataPoints,
|
||
|
dataCount,
|
||
|
&lastPt,
|
||
|
&lastInsets[0],
|
||
|
flag
|
||
|
);
|
||
|
|
||
|
leftCount += addedLeftCount;
|
||
|
rightCount += addedRightCount;
|
||
|
if(isFirstType && (leftCount > 0 || rightCount > 0))
|
||
|
isFirstType = FALSE;
|
||
|
}
|
||
|
lastPt = centerPoints[typeEndIndex];
|
||
|
|
||
|
}
|
||
|
|
||
|
if(status == Ok)
|
||
|
{
|
||
|
LeftTypes.SetCount(leftCount);
|
||
|
LeftPoints.SetCount(leftCount);
|
||
|
RightTypes.SetCount(rightCount);
|
||
|
RightPoints.SetCount(rightCount);
|
||
|
}
|
||
|
else
|
||
|
return status;
|
||
|
|
||
|
GpPointF startPoint, endPoint;
|
||
|
GpPointF startGrad, endGrad;
|
||
|
GpPointF startNorm, endNorm;
|
||
|
|
||
|
startPoint = *(centerPoints + startIndex);
|
||
|
endPoint = *(centerPoints + endIndex);
|
||
|
startGrad = grad0[1];
|
||
|
endGrad = grad0[endIndex - startIndex];
|
||
|
startNorm = norm0[1];
|
||
|
endNorm = norm0[endIndex - startIndex];
|
||
|
|
||
|
status = SetCaps(
|
||
|
startCap,
|
||
|
endCap,
|
||
|
startPoint,
|
||
|
startGrad,
|
||
|
startNorm,
|
||
|
endPoint,
|
||
|
endGrad,
|
||
|
endNorm,
|
||
|
leftWidth,
|
||
|
rightWidth,
|
||
|
centerPoints + startIndex,
|
||
|
endIndex - startIndex + 1
|
||
|
);
|
||
|
|
||
|
status = CombineSubpathOutlines(
|
||
|
widenedPoints,
|
||
|
widenedTypes,
|
||
|
isClosed
|
||
|
);
|
||
|
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
GpStatus
|
||
|
GpPathWidener::SetPolygonJoin(
|
||
|
REAL leftWidth,
|
||
|
REAL rightWidth,
|
||
|
BOOL isAntialiased
|
||
|
)
|
||
|
{
|
||
|
// This codes is intended to non-pen transform and in WorldUnit for now.
|
||
|
|
||
|
REAL minimumWidth = MinimumWidth;
|
||
|
if(leftWidth - rightWidth < StrokeWidth)
|
||
|
minimumWidth = (leftWidth - rightWidth)/StrokeWidth;
|
||
|
|
||
|
const INT maxPolyCount = 8;
|
||
|
INT count = 0;
|
||
|
GpPointF points[maxPolyCount];
|
||
|
REAL grads[maxPolyCount];
|
||
|
|
||
|
JoinPolygonPoints.Reset(FALSE);
|
||
|
JoinPolygonAngles.Reset(FALSE);
|
||
|
|
||
|
// Define Hobby's polygon.
|
||
|
|
||
|
if(minimumWidth < 1.06)
|
||
|
{
|
||
|
count = 4;
|
||
|
points[0].X = 0.0f; points[0].Y = -0.5f;
|
||
|
points[1].X = 0.5f; points[1].Y = 0.0f;
|
||
|
points[2].X = 0.0f; points[2].Y = 0.5f;
|
||
|
points[3].X = -0.5f; points[3].Y = 0.0f;
|
||
|
}
|
||
|
else if(minimumWidth < 1.5)
|
||
|
{
|
||
|
count = 4;
|
||
|
points[0].X = -0.5f; points[0].Y = -0.5f;
|
||
|
points[1].X = 0.5f; points[1].Y = -0.5f;
|
||
|
points[2].X = 0.5f; points[2].Y = 0.5f;
|
||
|
points[3].X = -0.5f; points[3].Y = 0.5f;
|
||
|
}
|
||
|
else if(minimumWidth < 1.77)
|
||
|
{
|
||
|
count = 4;
|
||
|
points[0].X = 0.0f; points[0].Y = -1.0f;
|
||
|
points[1].X = 1.0f; points[1].Y = 0.0f;
|
||
|
points[2].X = 0.0f; points[2].Y = 1.0f;
|
||
|
points[3].X = -1.0f; points[3].Y = 0.0f;
|
||
|
}
|
||
|
else if(minimumWidth < 2.02)
|
||
|
{
|
||
|
count = 6;
|
||
|
points[0].X = -0.5f; points[0].Y = -1.0f;
|
||
|
points[1].X = 0.5f; points[1].Y = -1.0f;
|
||
|
points[2].X = 1.0f; points[2].Y = 0.0f;
|
||
|
points[3].X = 0.5f; points[3].Y = 1.0f;
|
||
|
points[4].X = -0.5f; points[4].Y = 1.0f;
|
||
|
points[5].X = -1.0f; points[5].Y = 0.0f;
|
||
|
}
|
||
|
else if(minimumWidth < 2.48)
|
||
|
{
|
||
|
count = 8;
|
||
|
points[0].X = -0.5f; points[0].Y = -1.0f;
|
||
|
points[1].X = 0.5f; points[1].Y = -1.0f;
|
||
|
points[2].X = 1.0f; points[2].Y = -0.5f;
|
||
|
points[3].X = 1.0f; points[3].Y = 0.5f;
|
||
|
points[4].X = 0.5f; points[4].Y = 1.0f;
|
||
|
points[5].X = -0.5f; points[5].Y = 1.0f;
|
||
|
points[6].X = -1.0f; points[6].Y = 0.5f;
|
||
|
points[7].X = -1.0f; points[7].Y = -0.5f;
|
||
|
}
|
||
|
else if(minimumWidth < 2.5)
|
||
|
{
|
||
|
count = 4;
|
||
|
points[0].X = -1.0f; points[0].Y = -1.0f;
|
||
|
points[1].X = 1.0f; points[1].Y = -1.0f;
|
||
|
points[2].X = 1.0f; points[2].Y = 1.0f;
|
||
|
points[3].X = -1.0f; points[3].Y = 1.0f;
|
||
|
}
|
||
|
else if(minimumWidth < 2.91)
|
||
|
{
|
||
|
count = 8;
|
||
|
points[0].X = 0.0f; points[0].Y = -1.5f;
|
||
|
points[1].X = 1.0f; points[1].Y = -1.0f;
|
||
|
points[2].X = 1.5f; points[2].Y = 0.0f;
|
||
|
points[3].X = 1.0f; points[3].Y = 1.0f;
|
||
|
points[4].X = 0.0f; points[4].Y = 1.5f;
|
||
|
points[5].X = -1.0f; points[5].Y = 1.0f;
|
||
|
points[6].X = -1.5f; points[6].Y = 0.0f;
|
||
|
points[7].X = -1.0f; points[7].Y = -1.0f;
|
||
|
}
|
||
|
else
|
||
|
count = 0;
|
||
|
|
||
|
if(count > 0)
|
||
|
{
|
||
|
GpPointF dP;
|
||
|
|
||
|
for(INT i = 0; i < count - 1; i++)
|
||
|
{
|
||
|
dP = points[i + 1] - points[i];
|
||
|
GetFastAngle(&grads[i], dP);
|
||
|
}
|
||
|
|
||
|
dP = points[0] - points[count - 1];
|
||
|
GetFastAngle(&grads[count - 1], dP);
|
||
|
|
||
|
|
||
|
|
||
|
REAL lastAngle = grads[0];
|
||
|
REAL nextAngle;
|
||
|
|
||
|
/*
|
||
|
// Find out the smallest gradient.
|
||
|
|
||
|
INT i0 = 0;
|
||
|
for(i = 1; i < count; i++)
|
||
|
{
|
||
|
nextAngle = grads[i];
|
||
|
if(nextAngle < lastAngle)
|
||
|
i0 = i;
|
||
|
lastAngle = nextAngle;
|
||
|
}
|
||
|
|
||
|
// Rearrange so that the polygon starts with the smallest
|
||
|
// gradient.
|
||
|
|
||
|
if(i0 > 1)
|
||
|
{
|
||
|
GpPointF tempPointsBuff[maxPolyCount];
|
||
|
REAL tempGradsBuff[maxPolyCount];
|
||
|
|
||
|
GpMemcpy(&tempPointsBuff[0], &points[0], i0*sizeof(GpPointF));
|
||
|
GpMemcpy(&tempGradsBuff[0], &grads[0], i0);
|
||
|
GpMemcpy(&points[0],
|
||
|
&points[i0], (count - i0)*sizeof(GpPointF));
|
||
|
GpMemcpy(&grads[0], &grads[i0], count - i0);
|
||
|
GpMemcpy(&points[count - i0], &tempPointsBuff[0],
|
||
|
i0*sizeof(GpPointF));
|
||
|
GpMemcpy(&grads[count - i0], &tempGradsBuff[0], i0);
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
BOOL monotonic = TRUE;
|
||
|
i = 1;
|
||
|
lastAngle = grads[0];
|
||
|
|
||
|
while(monotonic && i < count)
|
||
|
{
|
||
|
nextAngle = grads[i];
|
||
|
if(nextAngle < lastAngle)
|
||
|
monotonic = FALSE;
|
||
|
i++;
|
||
|
lastAngle = nextAngle;
|
||
|
}
|
||
|
|
||
|
ASSERTMSG(monotonic, ("Polygon for join is not concave."));
|
||
|
}
|
||
|
|
||
|
if(count > 0)
|
||
|
{
|
||
|
JoinPolygonPoints.AddMultiple(&points[0], count);
|
||
|
JoinPolygonAngles.AddMultiple(&grads[0], count);
|
||
|
}
|
||
|
|
||
|
return Ok;
|
||
|
}
|
||
|
|
||
|
INT getVertexID(const GpPointF& vector, BOOL forLeftEdge, INT count, const REAL* grads)
|
||
|
{
|
||
|
INT left, right, middle;
|
||
|
REAL angle = 0.0f;
|
||
|
|
||
|
GetFastAngle(&angle, vector);
|
||
|
|
||
|
if(!forLeftEdge)
|
||
|
{
|
||
|
angle += 4;
|
||
|
if(angle >= 8)
|
||
|
angle -= 8;
|
||
|
}
|
||
|
|
||
|
if(angle <= grads[0])
|
||
|
return 0;
|
||
|
|
||
|
if(angle >= grads[count - 1])
|
||
|
return count - 1;
|
||
|
|
||
|
INT i = 1;
|
||
|
|
||
|
while(angle >= grads[i] && i < count)
|
||
|
{
|
||
|
i++;
|
||
|
}
|
||
|
|
||
|
return i - 1;
|
||
|
}
|
||
|
|
||
|
GpStatus
|
||
|
GpPathWidener::AddCompoundCaps(
|
||
|
DynPointFArray* widenedPoints,
|
||
|
DynByteArray* widenedTypes,
|
||
|
REAL leftWidth,
|
||
|
REAL rightWidth,
|
||
|
INT startIndex,
|
||
|
INT endIndex,
|
||
|
GpLineCap startCap,
|
||
|
GpLineCap endCap
|
||
|
)
|
||
|
{
|
||
|
const GpPointF* centerPoints = CenterPoints.GetDataBuffer();
|
||
|
const BYTE* centerTypes = CenterTypes.GetDataBuffer();
|
||
|
INT centerCount = CenterPoints.GetCount();
|
||
|
const GpPointF* grad0 = Gradients.GetDataBuffer();
|
||
|
const GpPointF* norm0 = Normals.GetDataBuffer();
|
||
|
|
||
|
GpPointF startPoint, endPoint;
|
||
|
GpPointF startGrad, endGrad;
|
||
|
GpPointF startNorm, endNorm;
|
||
|
|
||
|
startPoint = *(centerPoints + startIndex);
|
||
|
endPoint = *(centerPoints + endIndex);
|
||
|
startGrad = grad0[1];
|
||
|
endGrad = grad0[endIndex - startIndex];
|
||
|
startNorm = norm0[1];
|
||
|
endNorm = norm0[endIndex - startIndex];
|
||
|
|
||
|
GpStatus status;
|
||
|
status = SetCaps(
|
||
|
startCap,
|
||
|
endCap,
|
||
|
startPoint,
|
||
|
startGrad,
|
||
|
startNorm,
|
||
|
endPoint,
|
||
|
endGrad,
|
||
|
endNorm,
|
||
|
leftWidth,
|
||
|
rightWidth,
|
||
|
centerPoints + startIndex,
|
||
|
endIndex - startIndex + 1
|
||
|
);
|
||
|
|
||
|
status = CombineClosedCaps(
|
||
|
widenedPoints,
|
||
|
widenedTypes,
|
||
|
&CapPoints1,
|
||
|
&CapPoints2,
|
||
|
&CapTypes1,
|
||
|
&CapTypes2
|
||
|
);
|
||
|
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
GpStatus
|
||
|
GpPathWidener::SetCaps(
|
||
|
GpLineCap startCap,
|
||
|
GpLineCap endCap,
|
||
|
const GpPointF& startPoint,
|
||
|
const GpPointF& startGrad,
|
||
|
const GpPointF& startNorm,
|
||
|
const GpPointF& endPoint,
|
||
|
const GpPointF& endGrad,
|
||
|
const GpPointF& endNorm,
|
||
|
REAL leftWidth,
|
||
|
REAL rightWidth,
|
||
|
const GpPointF *points,
|
||
|
INT pointCount
|
||
|
)
|
||
|
{
|
||
|
GpStatus status = Ok;
|
||
|
|
||
|
CapPoints1.Reset(FALSE);
|
||
|
CapTypes1.Reset(FALSE);
|
||
|
CapPoints2.Reset(FALSE);
|
||
|
CapTypes2.Reset(FALSE);
|
||
|
|
||
|
switch(startCap)
|
||
|
{
|
||
|
case LineCapRound:
|
||
|
if(InsetPenMode)
|
||
|
{
|
||
|
status = SetDoubleRoundCap(
|
||
|
startPoint,
|
||
|
startGrad,
|
||
|
TRUE,
|
||
|
leftWidth,
|
||
|
rightWidth
|
||
|
);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
status = SetRoundCap(
|
||
|
startPoint,
|
||
|
startGrad,
|
||
|
TRUE,
|
||
|
leftWidth,
|
||
|
rightWidth
|
||
|
);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case LineCapTriangle:
|
||
|
ASSERT(!InsetPenMode);
|
||
|
status = SetTriangleCap(startPoint, startGrad, TRUE, leftWidth, rightWidth, points, pointCount);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
// Flat cap.
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
switch(endCap)
|
||
|
{
|
||
|
case LineCapRound:
|
||
|
if(InsetPenMode)
|
||
|
{
|
||
|
status = SetDoubleRoundCap(
|
||
|
endPoint,
|
||
|
endGrad,
|
||
|
FALSE,
|
||
|
leftWidth,
|
||
|
rightWidth
|
||
|
);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
status = SetRoundCap(
|
||
|
endPoint,
|
||
|
endGrad,
|
||
|
FALSE,
|
||
|
leftWidth,
|
||
|
rightWidth
|
||
|
);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case LineCapTriangle:
|
||
|
ASSERT(!InsetPenMode);
|
||
|
status = SetTriangleCap(endPoint, endGrad, FALSE, leftWidth, rightWidth, points, pointCount);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
// Flat cap.
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
VOID modifyEdges(
|
||
|
GpPointF* leftPoints,
|
||
|
BYTE* leftTypes,
|
||
|
INT* leftCount,
|
||
|
INT* leftOffset,
|
||
|
GpPointF* rightPoints,
|
||
|
BYTE* rightTypes,
|
||
|
INT* rightCount,
|
||
|
INT* rightOffset,
|
||
|
GpPointF* grad,
|
||
|
INT gradCount
|
||
|
)
|
||
|
{
|
||
|
INT leftOffset1 = 0;
|
||
|
INT rightOffset1 = 0;
|
||
|
INT leftCount0 = *leftCount;
|
||
|
INT rightCount0 = *rightCount;
|
||
|
INT leftCount1 = leftCount0;
|
||
|
INT rightCount1 = rightCount0;
|
||
|
|
||
|
if(gradCount > 2)
|
||
|
{
|
||
|
GpPointF firstGrad = grad[1];
|
||
|
GpPointF lastGrad = grad[gradCount - 2];
|
||
|
|
||
|
GpPointF dP;
|
||
|
if(leftCount0 > 2)
|
||
|
{
|
||
|
dP.X = leftPoints[1].X - leftPoints[0].X;
|
||
|
dP.Y = leftPoints[1].Y - leftPoints[0].Y;
|
||
|
if(dP.X*firstGrad.X + dP.Y*firstGrad.Y < 0)
|
||
|
{
|
||
|
leftPoints[0] = leftPoints[1];
|
||
|
}
|
||
|
dP.X = leftPoints[leftCount0 - 1].X
|
||
|
- leftPoints[leftCount0 - 2].X;
|
||
|
dP.Y = leftPoints[leftCount0 - 1].Y
|
||
|
- leftPoints[leftCount0 - 2].Y;
|
||
|
if(dP.X*lastGrad.X + dP.Y*lastGrad.Y < 0)
|
||
|
{
|
||
|
leftPoints[leftCount0 - 1]
|
||
|
= leftPoints[leftCount0 - 2];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(rightCount0 > 2)
|
||
|
{
|
||
|
dP.X = rightPoints[1].X - rightPoints[0].X;
|
||
|
dP.Y = rightPoints[1].Y - rightPoints[0].Y;
|
||
|
if(dP.X*firstGrad.X + dP.Y*firstGrad.Y < 0)
|
||
|
{
|
||
|
rightPoints[0] = rightPoints[1];
|
||
|
}
|
||
|
dP.X = rightPoints[rightCount0 - 1].X
|
||
|
- rightPoints[rightCount0 - 2].X;
|
||
|
dP.Y = rightPoints[rightCount0 - 1].Y
|
||
|
- rightPoints[rightCount0 - 2].Y;
|
||
|
if(dP.X*lastGrad.X + dP.Y*lastGrad.Y < 0)
|
||
|
{
|
||
|
rightPoints[rightCount0 - 1]
|
||
|
= rightPoints[rightCount0 - 2];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
*leftCount = leftCount1;
|
||
|
*leftOffset = leftOffset1;
|
||
|
*rightCount = rightCount1;
|
||
|
*rightOffset = rightOffset1;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**************************************************************************\
|
||
|
*
|
||
|
* Function Description:
|
||
|
*
|
||
|
* Combines left path, right path, start cap, and end cap.
|
||
|
*
|
||
|
* Arguments:
|
||
|
*
|
||
|
* [OUT] windedPoints - Output point data.
|
||
|
* [OUT] widnedTypes - Output type data.
|
||
|
* [IN] isClosed - TRUE is the current suppat is closed.
|
||
|
* [IN] closeStartCap - TRUE if the start cap needs to be closed.
|
||
|
* [IN] closeEndCap - TRUE if the end cap needs to be closed.
|
||
|
*
|
||
|
* Return Value:
|
||
|
*
|
||
|
* Status
|
||
|
*
|
||
|
\**************************************************************************/
|
||
|
|
||
|
GpStatus
|
||
|
GpPathWidener::CombineSubpathOutlines(
|
||
|
DynPointFArray* widenedPoints,
|
||
|
DynByteArray* widenedTypes,
|
||
|
BOOL isClosed,
|
||
|
BOOL closeStartCap,
|
||
|
BOOL closeEndCap
|
||
|
)
|
||
|
{
|
||
|
GpStatus status = Ok;
|
||
|
|
||
|
INT startCapCount = CapPoints1.GetCount();
|
||
|
GpPointF* startCapPoints = CapPoints1.GetDataBuffer();
|
||
|
BYTE* startCapTypes = CapTypes1.GetDataBuffer();
|
||
|
INT endCapCount = CapPoints2.GetCount();
|
||
|
GpPointF* endCapPoints = CapPoints2.GetDataBuffer();
|
||
|
BYTE* endCapTypes = CapTypes2.GetDataBuffer();
|
||
|
|
||
|
BYTE* leftTypes;
|
||
|
GpPointF* leftPoints;
|
||
|
BYTE* rightTypes;
|
||
|
GpPointF* rightPoints;
|
||
|
INT leftCount, rightCount;
|
||
|
|
||
|
leftCount = LeftPoints.GetCount();
|
||
|
leftTypes = LeftTypes.GetDataBuffer();
|
||
|
leftPoints = LeftPoints.GetDataBuffer();
|
||
|
rightCount = RightPoints.GetCount();
|
||
|
rightTypes = RightTypes.GetDataBuffer();
|
||
|
rightPoints = RightPoints.GetDataBuffer();
|
||
|
|
||
|
if(!isClosed)
|
||
|
{
|
||
|
GpPointF *grad = Gradients.GetDataBuffer();
|
||
|
INT gradCount = Gradients.GetCount();
|
||
|
INT leftOffset, rightOffset;
|
||
|
|
||
|
modifyEdges(leftPoints, leftTypes, &leftCount, &leftOffset,
|
||
|
rightPoints, rightTypes, &rightCount, &rightOffset,
|
||
|
grad, gradCount);
|
||
|
|
||
|
leftPoints += leftOffset;
|
||
|
leftTypes += leftOffset;
|
||
|
rightPoints += rightOffset;
|
||
|
rightTypes += rightOffset;
|
||
|
}
|
||
|
|
||
|
status = widenedPoints->ReserveSpace(
|
||
|
leftCount + rightCount + startCapCount + endCapCount + 2);
|
||
|
if(status == Ok)
|
||
|
status = widenedTypes->ReserveSpace(
|
||
|
leftCount + rightCount + startCapCount + endCapCount + 2);
|
||
|
|
||
|
GpPointF* wPts = NULL;
|
||
|
BYTE* wTypes = NULL;
|
||
|
|
||
|
if(status == Ok)
|
||
|
{
|
||
|
wPts = widenedPoints->GetDataBuffer();
|
||
|
wTypes = widenedTypes->GetDataBuffer();
|
||
|
}
|
||
|
|
||
|
if(wPts && wTypes)
|
||
|
{
|
||
|
// Set the pointers to the current location.
|
||
|
|
||
|
INT count0 = widenedPoints->GetCount();
|
||
|
wPts += count0;
|
||
|
wTypes += count0;
|
||
|
|
||
|
INT resultCount;
|
||
|
BOOL isStartCapClosed = FALSE;
|
||
|
BOOL isEndCapClosed = FALSE;
|
||
|
|
||
|
if(isClosed)
|
||
|
{
|
||
|
leftTypes[leftCount - 1] |= PathPointTypeCloseSubpath;
|
||
|
rightTypes[rightCount - 1] |= PathPointTypeCloseSubpath;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(startCapCount > 0)
|
||
|
{
|
||
|
if(!closeStartCap)
|
||
|
{
|
||
|
if(startCapTypes[startCapCount - 1] & PathPointTypeCloseSubpath)
|
||
|
isStartCapClosed = TRUE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Force the start cap to be closed.
|
||
|
|
||
|
startCapTypes[startCapCount - 1] |= PathPointTypeCloseSubpath;
|
||
|
isStartCapClosed = TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(endCapCount > 0)
|
||
|
{
|
||
|
if(!closeEndCap)
|
||
|
{
|
||
|
if(endCapTypes[endCapCount - 1] & PathPointTypeCloseSubpath)
|
||
|
isEndCapClosed = TRUE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Force the end cap to be closed.
|
||
|
|
||
|
endCapTypes[endCapCount - 1] |= PathPointTypeCloseSubpath;
|
||
|
isEndCapClosed = TRUE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(isClosed || (startCapCount == 0 && endCapCount == 0))
|
||
|
{
|
||
|
BOOL connect = TRUE;
|
||
|
resultCount =
|
||
|
::CombinePaths(leftCount + rightCount, wPts, wTypes,
|
||
|
leftCount, leftPoints, leftTypes, TRUE,
|
||
|
rightCount, rightPoints, rightTypes, FALSE,
|
||
|
connect);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
resultCount = leftCount;
|
||
|
|
||
|
if(leftCount > 0)
|
||
|
{
|
||
|
GpMemcpy(wPts, leftPoints, leftCount*sizeof(GpPointF));
|
||
|
GpMemcpy(wTypes, leftTypes, leftCount);
|
||
|
}
|
||
|
|
||
|
if(endCapCount > 0 && !isEndCapClosed)
|
||
|
{
|
||
|
resultCount =
|
||
|
combineTwoOpenSegments(
|
||
|
resultCount, wPts, wTypes, TRUE,
|
||
|
endCapCount, endCapPoints, endCapTypes, TRUE);
|
||
|
}
|
||
|
|
||
|
if(rightCount > 0)
|
||
|
{
|
||
|
resultCount =
|
||
|
combineTwoOpenSegments(
|
||
|
resultCount, wPts, wTypes, TRUE,
|
||
|
rightCount, rightPoints, rightTypes, FALSE);
|
||
|
}
|
||
|
|
||
|
if(startCapCount > 0 && !isStartCapClosed)
|
||
|
{
|
||
|
resultCount =
|
||
|
combineTwoOpenSegments(
|
||
|
resultCount, wPts, wTypes, TRUE,
|
||
|
startCapCount, startCapPoints, startCapTypes, TRUE);
|
||
|
}
|
||
|
|
||
|
wTypes[0] = PathPointTypeStart;
|
||
|
}
|
||
|
|
||
|
if(resultCount > 0)
|
||
|
{
|
||
|
// If the original subpath is open, the combined path needs to be
|
||
|
// closed. If the original path is closed, the left and
|
||
|
// right paths are already closed.
|
||
|
|
||
|
if(!isClosed)
|
||
|
{
|
||
|
wTypes[resultCount - 1] |= PathPointTypeCloseSubpath;
|
||
|
|
||
|
// Add the closed caps.
|
||
|
|
||
|
if(endCapCount > 0 && isEndCapClosed)
|
||
|
{
|
||
|
resultCount =
|
||
|
combineClosedSegments(
|
||
|
resultCount, wPts, wTypes, TRUE,
|
||
|
endCapCount, endCapPoints, endCapTypes, TRUE);
|
||
|
}
|
||
|
|
||
|
if(startCapCount > 0 && isStartCapClosed)
|
||
|
{
|
||
|
resultCount =
|
||
|
combineClosedSegments(
|
||
|
resultCount, wPts, wTypes, TRUE,
|
||
|
startCapCount, startCapPoints, startCapTypes, TRUE);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
widenedPoints->AdjustCount(resultCount);
|
||
|
widenedTypes->AdjustCount(resultCount);
|
||
|
}
|
||
|
else
|
||
|
status = GenericError;
|
||
|
}
|
||
|
else
|
||
|
status = OutOfMemory;
|
||
|
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
/**************************************************************************\
|
||
|
*
|
||
|
* Function Description:
|
||
|
*
|
||
|
* Combines the closed cap paths.
|
||
|
*
|
||
|
* Arguments:
|
||
|
*
|
||
|
* [OUT] windedPoints - Output point data.
|
||
|
* [OUT] widnedTypes - Output type data.
|
||
|
*
|
||
|
* Return Value:
|
||
|
*
|
||
|
* Status
|
||
|
*
|
||
|
\**************************************************************************/
|
||
|
|
||
|
GpStatus
|
||
|
GpPathWidener::CombineClosedCaps(
|
||
|
DynPointFArray* widenedPoints,
|
||
|
DynByteArray* widenedTypes,
|
||
|
DynPointFArray *daStartCapPoints,
|
||
|
DynPointFArray *daEndCapPoints,
|
||
|
DynByteArray *daStartCapTypes,
|
||
|
DynByteArray *daEndCapTypes
|
||
|
)
|
||
|
{
|
||
|
GpStatus status = Ok;
|
||
|
|
||
|
INT startCapCount = daStartCapPoints->GetCount();
|
||
|
GpPointF* startCapPoints = daStartCapPoints->GetDataBuffer();
|
||
|
BYTE* startCapTypes = daStartCapTypes->GetDataBuffer();
|
||
|
INT endCapCount = daEndCapPoints->GetCount();
|
||
|
GpPointF* endCapPoints = daEndCapPoints->GetDataBuffer();
|
||
|
BYTE* endCapTypes = daEndCapTypes->GetDataBuffer();
|
||
|
|
||
|
if(startCapCount == 0 && endCapCount == 0)
|
||
|
{
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
status = widenedPoints->ReserveSpace(startCapCount + endCapCount);
|
||
|
if(status == Ok)
|
||
|
status = widenedTypes->ReserveSpace(startCapCount + endCapCount);
|
||
|
|
||
|
GpPointF* wPts = NULL;
|
||
|
BYTE* wTypes = NULL;
|
||
|
|
||
|
if(status == Ok)
|
||
|
{
|
||
|
wPts = widenedPoints->GetDataBuffer();
|
||
|
wTypes = widenedTypes->GetDataBuffer();
|
||
|
}
|
||
|
else
|
||
|
status = OutOfMemory;
|
||
|
|
||
|
if(status == Ok && wPts && wTypes)
|
||
|
{
|
||
|
INT count0 = widenedPoints->GetCount();
|
||
|
|
||
|
// Make sure the previous path is closed.
|
||
|
|
||
|
if(count0 > 0)
|
||
|
wTypes[count0 - 1] |= PathPointTypeCloseSubpath;
|
||
|
|
||
|
// Set the pointers to the current location.
|
||
|
|
||
|
wPts += count0;
|
||
|
wTypes += count0;
|
||
|
|
||
|
INT resultCount = 0;
|
||
|
|
||
|
if(startCapCount > 0)
|
||
|
{
|
||
|
// Force the start cap to be closed.
|
||
|
|
||
|
startCapTypes[startCapCount - 1] |= PathPointTypeCloseSubpath;
|
||
|
resultCount =
|
||
|
combineClosedSegments(
|
||
|
resultCount, wPts, wTypes, TRUE,
|
||
|
startCapCount, startCapPoints, startCapTypes, TRUE);
|
||
|
}
|
||
|
|
||
|
if(endCapCount > 0)
|
||
|
{
|
||
|
// Force the end cap to be closed.
|
||
|
|
||
|
endCapTypes[endCapCount - 1] |= PathPointTypeCloseSubpath;
|
||
|
resultCount =
|
||
|
combineClosedSegments(
|
||
|
resultCount, wPts, wTypes, TRUE,
|
||
|
endCapCount, endCapPoints, endCapTypes, TRUE);
|
||
|
}
|
||
|
|
||
|
widenedPoints->AdjustCount(resultCount);
|
||
|
widenedTypes->AdjustCount(resultCount);
|
||
|
}
|
||
|
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
GpTurningDirection
|
||
|
getTurningDirection(
|
||
|
REAL* crossProduct,
|
||
|
const GpPointF& grad1,
|
||
|
const GpPointF& grad2
|
||
|
)
|
||
|
{
|
||
|
ASSERT(crossProduct);
|
||
|
|
||
|
GpTurningDirection direction = NotTurning;
|
||
|
*crossProduct = 0;
|
||
|
|
||
|
// Handle the degenerate cases.
|
||
|
|
||
|
GpPointF v;
|
||
|
|
||
|
if(( (REALABS(grad1.X) < REAL_EPSILON) &&
|
||
|
(REALABS(grad1.Y) < REAL_EPSILON) ) ||
|
||
|
( (REALABS(grad2.X) < REAL_EPSILON) &&
|
||
|
(REALABS(grad2.Y) < REAL_EPSILON) )
|
||
|
)
|
||
|
{
|
||
|
return NotTurning;
|
||
|
}
|
||
|
|
||
|
// Handle the case of straight or nearly straight lines.
|
||
|
// The following constant is completely bogus - we need a number here
|
||
|
// and we're fairly certain it must be small. Probably a better estimate
|
||
|
// would be some fraction of a device pixel over the length of the line -
|
||
|
// if we can figure out how big that is.
|
||
|
|
||
|
const REAL gradErr = 0.00001f;
|
||
|
|
||
|
if(distance_squared(grad1, grad2) < gradErr)
|
||
|
{
|
||
|
direction = NotTurning;
|
||
|
|
||
|
return direction;
|
||
|
}
|
||
|
|
||
|
// Calculate the cross product.
|
||
|
|
||
|
REAL cross = grad1.X*grad2.Y - grad1.Y*grad2.X;
|
||
|
|
||
|
// When it comes here, the lines are turning.
|
||
|
// Get the turning direction.
|
||
|
|
||
|
if (REALABS(cross) <= REAL_EPSILON)
|
||
|
{
|
||
|
direction = TurningBack;
|
||
|
cross = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(cross > 0)
|
||
|
{
|
||
|
direction = TurningRight;
|
||
|
}
|
||
|
else // if(cross < 0)
|
||
|
{
|
||
|
direction = TurningLeft;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
*crossProduct = cross;
|
||
|
|
||
|
return direction;
|
||
|
}
|
||
|
|
||
|
/**************************************************************************\
|
||
|
*
|
||
|
* Function Description:
|
||
|
*
|
||
|
* Calculates if the miter join will exceed the miter limit.
|
||
|
*
|
||
|
* Arguments:
|
||
|
*
|
||
|
* [IN] grad1 - the unit tangent vector of the last edge.
|
||
|
* [IN] grad2 - the unit tangent vector of the current edge.
|
||
|
* [IN] miterLimit2 - the square of the Miter limit.
|
||
|
*
|
||
|
* Return Value:
|
||
|
*
|
||
|
* TRUE if the miter limit of this join is exceeded
|
||
|
*
|
||
|
\**************************************************************************/
|
||
|
|
||
|
BOOL
|
||
|
getMiterExceeded(
|
||
|
const GpPointF& grad1,
|
||
|
const GpPointF& grad2,
|
||
|
REAL miterLimit2
|
||
|
)
|
||
|
{
|
||
|
REAL cross = grad1.X*grad2.Y - grad1.Y*grad2.X;
|
||
|
|
||
|
// If cross product is zero, the lines are colinear and can be
|
||
|
// turning back on themselves.
|
||
|
if (REALABS(cross) <= REAL_EPSILON)
|
||
|
{
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
// Get the normal direction for the miter join.
|
||
|
GpPointF v(0, 0);
|
||
|
v.X = grad1.X - grad2.X;
|
||
|
v.Y = grad1.Y - grad2.Y;
|
||
|
|
||
|
// Test the miter limit.
|
||
|
REAL test = v.X*v.X + v.Y*v.Y - cross*cross*miterLimit2;
|
||
|
|
||
|
return test > 0;
|
||
|
}
|
||
|
|
||
|
/**************************************************************************\
|
||
|
*
|
||
|
* Function Description:
|
||
|
*
|
||
|
* Calculates the vector for Miter or Bevel join. This vector represents
|
||
|
* the shift to the left along the moving direction.
|
||
|
* In case of Miter join, when the Miter join exceeds the miter limit,
|
||
|
* this returns the Bevel join.
|
||
|
*
|
||
|
* Arguments:
|
||
|
*
|
||
|
* [OUT] vector - the left shift for the Miter join. This must be
|
||
|
* allocated at least for the dimension of 2.
|
||
|
* [OUT] count - the number of join points.
|
||
|
* [IN] miterLimit2 - the square of the Miter limit.
|
||
|
* [IN] grad1 - the unit tangent vector of the last edge.
|
||
|
* [IN] grad2 - the unit tangent vector of the current edge.
|
||
|
*
|
||
|
* Return Value:
|
||
|
*
|
||
|
* Turning direction from the last edge to the current edge.
|
||
|
*
|
||
|
\**************************************************************************/
|
||
|
|
||
|
GpTurningDirection
|
||
|
getMiterBevelJoin(
|
||
|
const GpPointF& point,
|
||
|
const GpPointF& grad1,
|
||
|
const GpPointF& grad2,
|
||
|
const GpPointF& norm1,
|
||
|
const GpPointF& norm2,
|
||
|
REAL leftWidth,
|
||
|
REAL rightWidth,
|
||
|
INT *leftCount,
|
||
|
GpPointF *leftPoints,
|
||
|
BOOL* leftInside,
|
||
|
INT *rightCount,
|
||
|
GpPointF *rightPoints,
|
||
|
BOOL* rightInside,
|
||
|
BOOL needsToAdjustNormals,
|
||
|
REAL miterLimit2,
|
||
|
BOOL isMiter,
|
||
|
BOOL useBevelJoinInside
|
||
|
)
|
||
|
{
|
||
|
*leftInside = FALSE;
|
||
|
*rightInside = FALSE;
|
||
|
|
||
|
if(miterLimit2 <= 1)
|
||
|
isMiter = FALSE;
|
||
|
|
||
|
GpTurningDirection direction = NotTurning;
|
||
|
|
||
|
// Handle the degenerate cases.
|
||
|
|
||
|
GpPointF v(0, 0);
|
||
|
REAL cross;
|
||
|
|
||
|
direction = getTurningDirection(&cross, grad1, grad2);
|
||
|
|
||
|
if(direction == NotMoving)
|
||
|
{
|
||
|
*leftCount = 0;
|
||
|
*rightCount = 0;
|
||
|
return direction;
|
||
|
}
|
||
|
else if(direction == NotTurning)
|
||
|
{
|
||
|
if(norm1.X != 0 || norm1.Y != 0)
|
||
|
v = norm1;
|
||
|
else
|
||
|
v = norm2;
|
||
|
|
||
|
leftPoints[0].X = point.X + leftWidth*v.X;
|
||
|
leftPoints[0].Y = point.Y + leftWidth*v.Y;
|
||
|
*leftCount = 1;
|
||
|
|
||
|
rightPoints[0].X = point.X + rightWidth*v.X;
|
||
|
rightPoints[0].Y = point.Y + rightWidth*v.Y;
|
||
|
*rightCount = 1;
|
||
|
|
||
|
return direction;
|
||
|
}
|
||
|
|
||
|
if(cross > 0)
|
||
|
{
|
||
|
// Right Turn
|
||
|
|
||
|
// If the width is positive, this point is outside.
|
||
|
// For the zero width, we regard this as non-inside point.
|
||
|
|
||
|
if(leftWidth >= 0)
|
||
|
*leftInside = FALSE;
|
||
|
else
|
||
|
*leftInside = TRUE;
|
||
|
|
||
|
if(rightWidth >= 0)
|
||
|
*rightInside = FALSE;
|
||
|
else
|
||
|
*rightInside = TRUE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Left Turn
|
||
|
|
||
|
// If the width is negative, this point is outside.
|
||
|
// For the zero width, we regard this as non-inside point.
|
||
|
|
||
|
if(leftWidth <= 0)
|
||
|
*leftInside = FALSE;
|
||
|
else
|
||
|
*leftInside = TRUE;
|
||
|
|
||
|
if(rightWidth <= 0)
|
||
|
*rightInside = FALSE;
|
||
|
else
|
||
|
*rightInside = TRUE;
|
||
|
}
|
||
|
|
||
|
BOOL isLeftMiterJoin = FALSE, isRightMiterJoin = FALSE;
|
||
|
REAL leftShift1 = 0, rightShift1 = 0;
|
||
|
REAL leftShift2 = 0, rightShift2 = 0;
|
||
|
|
||
|
if(isMiter && cross != 0)
|
||
|
{
|
||
|
REAL test = 0;
|
||
|
|
||
|
// Get the normal direction for the miter join.
|
||
|
|
||
|
v.X = grad1.X - grad2.X;
|
||
|
v.Y = grad1.Y - grad2.Y;
|
||
|
|
||
|
// Test the miter limit.
|
||
|
|
||
|
test = v.X*v.X + v.Y*v.Y - cross*cross*miterLimit2;
|
||
|
|
||
|
if(test <= 0 )
|
||
|
{
|
||
|
// Use the miter join.
|
||
|
|
||
|
if(needsToAdjustNormals)
|
||
|
{
|
||
|
// Use adjusted normals so that aliased thin lines
|
||
|
// won't disappear.
|
||
|
|
||
|
REAL c1, c2;
|
||
|
|
||
|
c1 = norm2.X*grad2.Y - norm2.Y*grad2.X;
|
||
|
c2 = norm1.X*grad1.Y - norm1.Y*grad1.X;
|
||
|
v.X = c1*grad1.X - c2*grad2.X;
|
||
|
v.Y = c1*grad1.Y - c2*grad2.Y;
|
||
|
}
|
||
|
|
||
|
v.X /= cross;
|
||
|
v.Y /= cross;
|
||
|
|
||
|
GpPointF *outPoints, *inPoints;
|
||
|
REAL outWidth, inWidth;
|
||
|
INT *outCount, *inCount;
|
||
|
|
||
|
if(cross > 0)
|
||
|
{
|
||
|
// When a miter join is used, set the inside flag to
|
||
|
// FALSE since there is no overlap.
|
||
|
|
||
|
isLeftMiterJoin = TRUE;
|
||
|
*leftInside = FALSE;
|
||
|
|
||
|
if(useBevelJoinInside)
|
||
|
{
|
||
|
if(*rightInside)
|
||
|
isRightMiterJoin = FALSE;
|
||
|
else
|
||
|
{
|
||
|
// When the right edges are outside,
|
||
|
// we cannot use Bevel join since
|
||
|
// Bevel join shape will actually appear.
|
||
|
|
||
|
isRightMiterJoin = TRUE;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// When a miter join is used, set the inside flag to
|
||
|
// FALSE since there is no overlap.
|
||
|
|
||
|
isRightMiterJoin = TRUE;
|
||
|
*rightInside = FALSE;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// When a miter join is used, set the inside flag to
|
||
|
// FALSE since there is no overlap.
|
||
|
|
||
|
isRightMiterJoin = TRUE;
|
||
|
*rightInside = FALSE;
|
||
|
|
||
|
if(useBevelJoinInside)
|
||
|
{
|
||
|
if(*leftInside)
|
||
|
isLeftMiterJoin = FALSE;
|
||
|
else
|
||
|
{
|
||
|
// When the right edges are outside,
|
||
|
// we cannot use Bevel join since
|
||
|
// Bevel join shape will actually appear.
|
||
|
|
||
|
isLeftMiterJoin = TRUE;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// When a miter join is used, set the inside flag to
|
||
|
// FALSE since there is no overlap.
|
||
|
|
||
|
isLeftMiterJoin = TRUE;
|
||
|
*leftInside = FALSE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// The turn is too sharp and it exceeds the miter limit.
|
||
|
// We must chop off the miter join tips.
|
||
|
|
||
|
REAL n1n1 = 1, n2n2 = 1, g1n1 = 0, g2n2 = 0;
|
||
|
|
||
|
if(needsToAdjustNormals)
|
||
|
{
|
||
|
n1n1 = norm1.X*norm1.X + norm1.Y*norm1.Y;
|
||
|
n2n2 = norm2.X*norm2.X + norm2.Y*norm2.Y;
|
||
|
g1n1 = grad1.X*norm1.X + grad1.Y*norm1.Y;
|
||
|
g2n2 = grad2.X*norm2.X + grad2.Y*norm2.Y;
|
||
|
}
|
||
|
|
||
|
if(miterLimit2 > max(n1n1, n2n2))
|
||
|
{
|
||
|
if(*leftInside == FALSE)
|
||
|
{
|
||
|
REAL lWidth;
|
||
|
|
||
|
if(cross > 0)
|
||
|
lWidth = leftWidth; // Right Turn
|
||
|
else
|
||
|
lWidth = - leftWidth; // Left Turn
|
||
|
|
||
|
leftShift1 = (REALSQRT(miterLimit2 - n1n1 + g1n1*g1n1)
|
||
|
- g1n1)*lWidth;
|
||
|
leftShift2 = (REALSQRT(miterLimit2 - n2n2 + g2n2*g2n2)
|
||
|
+ g2n2)*lWidth;
|
||
|
}
|
||
|
|
||
|
if(*rightInside == FALSE)
|
||
|
{
|
||
|
REAL rWidth;
|
||
|
|
||
|
if(cross > 0)
|
||
|
rWidth = rightWidth; // Right Turn
|
||
|
else
|
||
|
rWidth = - rightWidth; // Left Turn
|
||
|
|
||
|
rightShift1 = (REALSQRT(miterLimit2 - n1n1 + g1n1*g1n1)
|
||
|
- g1n1)*rWidth;
|
||
|
rightShift2 = (REALSQRT(miterLimit2 - n2n2 + g2n2*g2n2)
|
||
|
+ g2n2)*rWidth;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(isLeftMiterJoin)
|
||
|
{
|
||
|
leftPoints[0].X = point.X + leftWidth*v.X;
|
||
|
leftPoints[0].Y = point.Y + leftWidth*v.Y;
|
||
|
*leftCount = 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
leftPoints[0].X = point.X + leftWidth*norm1.X + leftShift1*grad1.X;
|
||
|
leftPoints[0].Y = point.Y + leftWidth*norm1.Y + leftShift1*grad1.Y;
|
||
|
leftPoints[1].X = point.X + leftWidth*norm2.X - leftShift2*grad2.X;
|
||
|
leftPoints[1].Y = point.Y + leftWidth*norm2.Y - leftShift2*grad2.Y;
|
||
|
|
||
|
// Check if two points are degenerate.
|
||
|
|
||
|
if(REALABS(leftPoints[1].X - leftPoints[0].X) +
|
||
|
REALABS(leftPoints[1].Y - leftPoints[0].Y)
|
||
|
> POINTF_EPSILON)
|
||
|
{
|
||
|
*leftCount = 2;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Since there is no overlap, set the inside flag to FALSE.
|
||
|
|
||
|
*leftCount = 1;
|
||
|
*leftInside = FALSE;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
if(isRightMiterJoin)
|
||
|
{
|
||
|
rightPoints[0].X = point.X + rightWidth*v.X;
|
||
|
rightPoints[0].Y = point.Y + rightWidth*v.Y;
|
||
|
*rightCount = 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
rightPoints[0].X = point.X + rightWidth*norm1.X + rightShift1*grad1.X;
|
||
|
rightPoints[0].Y = point.Y + rightWidth*norm1.Y + rightShift1*grad1.Y;
|
||
|
rightPoints[1].X = point.X + rightWidth*norm2.X - rightShift2*grad2.X;
|
||
|
rightPoints[1].Y = point.Y + rightWidth*norm2.Y - rightShift2*grad2.Y;
|
||
|
|
||
|
// Check if two points are degenerate.
|
||
|
|
||
|
if(REALABS(rightPoints[1].X - rightPoints[0].X) +
|
||
|
REALABS(rightPoints[1].Y - rightPoints[0].Y)
|
||
|
> POINTF_EPSILON)
|
||
|
{
|
||
|
*rightCount = 2;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Since there is no overlap, set the inside flag to FALSE.
|
||
|
|
||
|
*rightCount = 1;
|
||
|
*rightInside = FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return direction;
|
||
|
}
|
||
|
|
||
|
enum GpRoundJoinFlag
|
||
|
{
|
||
|
NeedsNone = 0,
|
||
|
NeedsOnlyRoundJoin = 1,
|
||
|
NeedsOnlyNonRoundJoin = 2,
|
||
|
NeedsBoth = 3
|
||
|
};
|
||
|
|
||
|
/**************************************************************************\
|
||
|
*
|
||
|
* Function Description:
|
||
|
*
|
||
|
* From the given point and the two tangent vectors of the two edges,
|
||
|
* and the radius of the round join, this returns the verteces for
|
||
|
* left edges and right edges of the round join for the current point.
|
||
|
* This is used when the bending angle is less than 90 degrees
|
||
|
* and is called by GetRoundJoin.
|
||
|
*
|
||
|
* Arguments:
|
||
|
*
|
||
|
* [IN] point - The current points in the original path.
|
||
|
* [IN] grad1 - The tangent of the current edge.
|
||
|
* [IN] grad2 - The tangent of the next edge.
|
||
|
* [IN] dot - The dot product of grad1 and grad2.
|
||
|
* [IN] leftWidth - The left width of the round join.
|
||
|
* [IN] rightWidth - The right width of the round join.
|
||
|
* [OUT] leftCount - The count of the left points.
|
||
|
* [OUT] leftPoints - The left points.
|
||
|
* [OUT] rightCount - The count of the right points.
|
||
|
* [OUT] rightPoints - The right points.
|
||
|
*
|
||
|
* Both leftPoints and rightPoints must have at least dimension of 4.
|
||
|
* If leftCount is positive (negative), this means the left edges are
|
||
|
* lines with leftCount points (cubic Bezier curves with -leftCount
|
||
|
* control points).
|
||
|
* If rightCount is positive (negative), this means the right edges are
|
||
|
* lines with rightCount points (cubic Bezier curves with -rightCount
|
||
|
* control points).
|
||
|
*
|
||
|
* Return Value:
|
||
|
*
|
||
|
* None
|
||
|
*
|
||
|
* 06/16/99 ikkof
|
||
|
* Created it
|
||
|
*
|
||
|
\**************************************************************************/
|
||
|
|
||
|
VOID
|
||
|
getSmallRoundJoin(
|
||
|
const GpPointF& point,
|
||
|
const GpPointF& grad1,
|
||
|
const GpPointF& grad2,
|
||
|
const GpPointF& norm1,
|
||
|
const GpPointF& norm2,
|
||
|
REAL leftWidth,
|
||
|
REAL rightWidth,
|
||
|
INT *leftCount,
|
||
|
GpPointF *leftPoints,
|
||
|
INT *rightCount,
|
||
|
GpPointF *rightPoints,
|
||
|
REAL dot,
|
||
|
REAL cross,
|
||
|
BOOL needsToAdjustNormals,
|
||
|
REAL miterLimit2,
|
||
|
INT condition,
|
||
|
BOOL useBevelJoinInside
|
||
|
)
|
||
|
{
|
||
|
if((condition & NeedsBoth) == 0)
|
||
|
{
|
||
|
*leftCount = 0;
|
||
|
*rightCount = 0;
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
GpPointF n1, n2;
|
||
|
n1 = norm1;
|
||
|
n2 = norm2;
|
||
|
|
||
|
REAL k;
|
||
|
REAL almostStraight = 1.0f - 0.01f;
|
||
|
|
||
|
if(dot < almostStraight)
|
||
|
{
|
||
|
// Obtain the distance from the first control point
|
||
|
// or from the last control point.
|
||
|
// For its derivation, see ikkof's notes for "Round Joins".
|
||
|
|
||
|
REAL cross1 = cross;
|
||
|
if(cross < 0)
|
||
|
cross1 = - cross;
|
||
|
k = 4*(REALSQRT(2*(1 - dot)) - cross1)/(3*(1 - dot));
|
||
|
|
||
|
GpPointF *outPoints, *inPoints;
|
||
|
INT *outCount, *inCount;
|
||
|
|
||
|
REAL outWidth, inWidth;
|
||
|
|
||
|
if(cross >= 0)
|
||
|
{
|
||
|
// The left edges are round join.
|
||
|
|
||
|
outPoints = leftPoints;
|
||
|
inPoints = rightPoints;
|
||
|
outCount = leftCount;
|
||
|
inCount = rightCount;
|
||
|
outWidth = leftWidth;
|
||
|
inWidth = rightWidth;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// The right edges are round join.
|
||
|
|
||
|
outPoints = rightPoints;
|
||
|
inPoints = leftPoints;
|
||
|
outCount = rightCount;
|
||
|
inCount = leftCount;
|
||
|
outWidth = - rightWidth;
|
||
|
inWidth = - leftWidth;
|
||
|
n1.X = - n1.X;
|
||
|
n1.Y = - n1.Y;
|
||
|
n2.X = - n2.X;
|
||
|
n2.Y = - n2.Y;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Get the normal direction for the miter join.
|
||
|
|
||
|
GpPointF v;
|
||
|
REAL test;
|
||
|
|
||
|
v.X = grad1.X - grad2.X;
|
||
|
v.Y = grad1.Y - grad2.Y;
|
||
|
|
||
|
// Test the miter limit.
|
||
|
|
||
|
BOOL useMiterJoin = FALSE;;
|
||
|
|
||
|
// Reduce the miter limit
|
||
|
|
||
|
miterLimit2 = 3*3;
|
||
|
|
||
|
// Note that abs(cross) == abs(cross1) from the definition.
|
||
|
|
||
|
if(REALABS(cross1) >= REAL_EPSILON)
|
||
|
{
|
||
|
REAL test = v.X*v.X + v.Y*v.Y - cross*cross*miterLimit2;
|
||
|
if(test <= 0)
|
||
|
{
|
||
|
useMiterJoin = TRUE;
|
||
|
v.X /= cross1;
|
||
|
v.Y /= cross1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
useMiterJoin = useMiterJoin && !useBevelJoinInside;
|
||
|
|
||
|
REAL k1;
|
||
|
if(outWidth > 0)
|
||
|
{
|
||
|
if(condition & NeedsOnlyRoundJoin)
|
||
|
{
|
||
|
k1 = outWidth*k;
|
||
|
outPoints[0].X = point.X + outWidth*n1.X;
|
||
|
outPoints[0].Y = point.Y + outWidth*n1.Y;
|
||
|
outPoints[1].X = outPoints[0].X + k1*grad1.X;
|
||
|
outPoints[1].Y = outPoints[0].Y + k1*grad1.Y;
|
||
|
outPoints[3].X = point.X + outWidth*n2.X;
|
||
|
outPoints[3].Y = point.Y + outWidth*n2.Y;
|
||
|
outPoints[2].X = outPoints[3].X - k1*grad2.X;
|
||
|
outPoints[2].Y = outPoints[3].Y - k1*grad2.Y;
|
||
|
*outCount = -4; // Indicate "-" for Bezier
|
||
|
}
|
||
|
else
|
||
|
*outCount = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(condition & NeedsOnlyNonRoundJoin)
|
||
|
{
|
||
|
if(outWidth == 0)
|
||
|
{
|
||
|
outPoints[0] = point;
|
||
|
*outCount = 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(useMiterJoin)
|
||
|
{
|
||
|
outPoints[0].X = point.X + outWidth*v.X;
|
||
|
outPoints[0].Y = point.Y + outWidth*v.Y;
|
||
|
*outCount = 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
outPoints[0].X = point.X + outWidth*n1.X;
|
||
|
outPoints[0].Y = point.Y + outWidth*n1.Y;
|
||
|
outPoints[1].X = point.X + outWidth*n2.X;
|
||
|
outPoints[1].Y = point.Y + outWidth*n2.Y;
|
||
|
*outCount = 2;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
*outCount = 0;
|
||
|
}
|
||
|
|
||
|
if(inWidth > 0)
|
||
|
{
|
||
|
if(condition & NeedsOnlyRoundJoin)
|
||
|
{
|
||
|
k1 = inWidth*k;
|
||
|
inPoints[0].X = point.X + inWidth*n1.X;
|
||
|
inPoints[0].Y = point.Y + inWidth*n1.Y;
|
||
|
inPoints[1].X = inPoints[0].X + k1*grad1.X;
|
||
|
inPoints[1].Y = inPoints[0].Y + k1*grad1.Y;
|
||
|
inPoints[3].X = point.X + inWidth*n2.X;
|
||
|
inPoints[3].Y = point.Y + inWidth*n2.Y;
|
||
|
inPoints[2].X = inPoints[3].X - k1*grad2.X;
|
||
|
inPoints[2].Y = inPoints[3].Y - k1*grad2.Y;
|
||
|
*inCount = -4; // Indicate "-" for Bezier
|
||
|
}
|
||
|
else
|
||
|
*inCount = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(condition & NeedsOnlyNonRoundJoin)
|
||
|
{
|
||
|
if(inWidth == 0)
|
||
|
{
|
||
|
inPoints[0] = point;
|
||
|
*inCount = 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(useMiterJoin)
|
||
|
{
|
||
|
inPoints[0].X = point.X + inWidth*v.X;
|
||
|
inPoints[0].Y = point.Y + inWidth*v.Y;
|
||
|
*inCount = 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
inPoints[0].X = point.X + inWidth*n1.X;
|
||
|
inPoints[0].Y = point.Y + inWidth*n1.Y;
|
||
|
inPoints[1].X = point.X + inWidth*n2.X;
|
||
|
inPoints[1].Y = point.Y + inWidth*n2.Y;
|
||
|
*inCount = 2;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
*inCount = 0;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(condition & NeedsOnlyNonRoundJoin)
|
||
|
{
|
||
|
// This is a straight line.
|
||
|
|
||
|
leftPoints[0].X = point.X + leftWidth*n1.X;
|
||
|
leftPoints[0].Y = point.Y + leftWidth*n1.Y;
|
||
|
*leftCount = 1;
|
||
|
|
||
|
rightPoints[0].X = point.X + rightWidth*n1.X;
|
||
|
rightPoints[0].Y = point.Y + rightWidth*n1.Y;
|
||
|
*rightCount = 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*leftCount = 0;
|
||
|
*rightCount = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**************************************************************************\
|
||
|
*
|
||
|
* Function Description:
|
||
|
*
|
||
|
* From the given previous, current, and next points and the radius
|
||
|
* of the round join, this returns the verteces for left edges and
|
||
|
* right edges of the round join for the current point.
|
||
|
*
|
||
|
* Arguments:
|
||
|
*
|
||
|
* [IN] points - The previous, current, and next points
|
||
|
* in the original path.
|
||
|
* [IN] leftWidth - The left width of the round join.
|
||
|
* [IN] rightWidth - The right width of the round join.
|
||
|
* [OUT] leftCount - The count of the left points.
|
||
|
* [OUT] leftPoints - The left points.
|
||
|
* [OUT] rightCount - The count of the right points.
|
||
|
* [OUT] rightPoints - The right points.
|
||
|
*
|
||
|
* Both leftPoints and rightPoints must have at least dimension of 7.
|
||
|
* If leftCount is positive (negative), this means the left edges are
|
||
|
* lines with leftCount points (cubic Bezier curves with -leftCount
|
||
|
* control points).
|
||
|
* If rightCount is positive (negative), this means the right edges are
|
||
|
* lines with rightCount points (cubic Bezier curves with -rightCount
|
||
|
* control points).
|
||
|
*
|
||
|
* Return Value:
|
||
|
*
|
||
|
* FALSE if the current point coindes with the previous point or
|
||
|
* the next point. Otherwise, this returns TRUE.
|
||
|
*
|
||
|
* 06/16/99 ikkof
|
||
|
* Created it
|
||
|
*
|
||
|
\**************************************************************************/
|
||
|
|
||
|
GpTurningDirection
|
||
|
getRoundJoin(
|
||
|
const GpPointF& point,
|
||
|
const GpPointF& grad1,
|
||
|
const GpPointF& grad2,
|
||
|
const GpPointF& norm1,
|
||
|
const GpPointF& norm2,
|
||
|
REAL leftWidth,
|
||
|
REAL rightWidth,
|
||
|
INT* leftCount,
|
||
|
GpPointF* leftPoints,
|
||
|
BOOL* leftInside,
|
||
|
INT* rightCount,
|
||
|
GpPointF* rightPoints,
|
||
|
BOOL* rightInside,
|
||
|
BOOL needsToAdjustNormals,
|
||
|
REAL miterLimit2,
|
||
|
BOOL useBevelJoinInside
|
||
|
)
|
||
|
{
|
||
|
//!!! We need to update inside flags for Round joins later.
|
||
|
|
||
|
*leftInside = FALSE;
|
||
|
*rightInside = FALSE;
|
||
|
|
||
|
ASSERT(leftPoints && rightPoints);
|
||
|
ASSERT(leftCount && rightCount);
|
||
|
|
||
|
REAL radius = leftWidth;
|
||
|
|
||
|
// When it comes here, the three points are not degenerate.
|
||
|
|
||
|
REAL dot = grad1.X*grad2.X + grad1.Y*grad2.Y; // dot product.
|
||
|
|
||
|
REAL cross;
|
||
|
GpTurningDirection direction = getTurningDirection(
|
||
|
&cross, grad1, grad2);
|
||
|
// &cross, grad1, grad2, norm1, norm2);
|
||
|
|
||
|
// If dot >= 0 (the bending angle is less than or equal to 90 degrees,
|
||
|
// we can approximate this arc with one cubic Beizer curve.
|
||
|
|
||
|
INT condition;
|
||
|
REAL smallErr = - 0.001f;
|
||
|
if(dot > smallErr)
|
||
|
{
|
||
|
condition = NeedsBoth;
|
||
|
getSmallRoundJoin(point, grad1, grad2, norm1, norm2,
|
||
|
leftWidth, rightWidth,
|
||
|
leftCount, leftPoints, rightCount, rightPoints,
|
||
|
dot, cross, needsToAdjustNormals, miterLimit2,
|
||
|
condition, useBevelJoinInside);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// The bending angle is larger than 90 and less than or
|
||
|
// equal to 180 degrees.
|
||
|
// We can approximate this arc with two cubic Beizer curves.
|
||
|
|
||
|
GpPointF *pts1, *pts2;
|
||
|
INT count1, count2;
|
||
|
|
||
|
pts1 = leftPoints;
|
||
|
pts2 = rightPoints;
|
||
|
|
||
|
// First obtain the non-round join parts.
|
||
|
|
||
|
condition = NeedsOnlyNonRoundJoin;
|
||
|
|
||
|
getSmallRoundJoin(point, grad1, grad2, norm1, norm2,
|
||
|
leftWidth, rightWidth,
|
||
|
&count1, pts1, &count2, pts2,
|
||
|
dot, cross, needsToAdjustNormals, miterLimit2,
|
||
|
condition, useBevelJoinInside);
|
||
|
|
||
|
INT cnt1, cnt2;
|
||
|
|
||
|
if(count1 > 0)
|
||
|
cnt1 = count1;
|
||
|
else
|
||
|
cnt1 = 0;
|
||
|
|
||
|
if(count2 > 0)
|
||
|
cnt2 = count2;
|
||
|
else
|
||
|
cnt2 = 0;
|
||
|
|
||
|
pts1 += cnt1;
|
||
|
pts2 += cnt2;
|
||
|
*leftCount = cnt1;
|
||
|
*rightCount = cnt2;
|
||
|
|
||
|
// Obtain the middle unit gradient vector.
|
||
|
|
||
|
GpPointF midNorm;
|
||
|
midNorm.X = norm1.X + norm2.X;
|
||
|
midNorm.Y = norm1.Y + norm2.Y;
|
||
|
|
||
|
if(midNorm.X != 0 || midNorm.Y != 0)
|
||
|
{
|
||
|
REAL dm = midNorm.X*midNorm.X + midNorm.Y*midNorm.Y;
|
||
|
dm = REALSQRT(dm);
|
||
|
midNorm.X /= dm;
|
||
|
midNorm.Y /= dm;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
midNorm.X = - norm1.Y;
|
||
|
midNorm.Y = norm1.X;
|
||
|
}
|
||
|
|
||
|
GpPointF lm;
|
||
|
|
||
|
// Rotate the mid normal +90 degrees.
|
||
|
|
||
|
lm.X = - midNorm.Y;
|
||
|
lm.Y = midNorm.X;
|
||
|
|
||
|
// Obtain the first half of the round join.
|
||
|
|
||
|
condition = NeedsOnlyRoundJoin;
|
||
|
|
||
|
dot = grad1.X*lm.X + grad1.Y*lm.Y;
|
||
|
cross = grad1.X*lm.Y - grad1.Y*lm.X;
|
||
|
getSmallRoundJoin(point, grad1, lm, norm1, midNorm,
|
||
|
leftWidth, rightWidth,
|
||
|
&count1, pts1, &count2, pts2,
|
||
|
dot, cross, needsToAdjustNormals, miterLimit2,
|
||
|
condition, useBevelJoinInside);
|
||
|
|
||
|
// Note that since the end point of the first half of
|
||
|
// the round join and the start point of the second
|
||
|
// of the round join are the same, don't copy
|
||
|
// the end point of the first half of the round join.
|
||
|
|
||
|
if(count1 < 0)
|
||
|
cnt1 = - count1 - 1;
|
||
|
else
|
||
|
cnt1 = 0;
|
||
|
|
||
|
if(count2 < 0)
|
||
|
cnt2 = - count2 - 1;
|
||
|
else
|
||
|
cnt2 = 0;
|
||
|
|
||
|
pts1 += cnt1;
|
||
|
pts2 += cnt2;
|
||
|
*leftCount += cnt1;
|
||
|
*rightCount += cnt2;
|
||
|
|
||
|
// Obtain the second half of the round join.
|
||
|
|
||
|
dot = lm.X*grad2.X + lm.Y*grad2.Y;
|
||
|
cross = lm.X*grad2.Y - lm.Y*grad2.X;
|
||
|
getSmallRoundJoin(point, lm, grad2, midNorm, norm2,
|
||
|
leftWidth, rightWidth,
|
||
|
&count1, pts1, &count2, pts2,
|
||
|
dot, cross, needsToAdjustNormals, miterLimit2,
|
||
|
condition, useBevelJoinInside);
|
||
|
|
||
|
// Combines the two curves or lines.
|
||
|
|
||
|
if(count1 < 0)
|
||
|
cnt1 += - count1;
|
||
|
else
|
||
|
cnt1 = 0;
|
||
|
|
||
|
if(count2 < 0)
|
||
|
cnt2 += - count2;
|
||
|
else
|
||
|
cnt2 = 0;
|
||
|
|
||
|
if(cnt1 > 0)
|
||
|
*leftCount = - cnt1;
|
||
|
if(cnt2 > 0)
|
||
|
*rightCount = - cnt2;
|
||
|
}
|
||
|
|
||
|
return direction;
|
||
|
}
|
||
|
|
||
|
/**************************************************************************\
|
||
|
*
|
||
|
* Function Description:
|
||
|
*
|
||
|
* Calculates the vector for Miter or Bevel join. This vector represents
|
||
|
* the shift to the left along the moving direction.
|
||
|
* In case of Miter join, when the Miter join exceeds the miter limit,
|
||
|
* this returns the Bevel join.
|
||
|
*
|
||
|
* Arguments:
|
||
|
*
|
||
|
* [OUT] vector - the left shift for the Miter join. This must be
|
||
|
* allocated at least for the dimension of 2.
|
||
|
* [OUT] count - the number of join points.
|
||
|
* [IN] miterLimit2 - the square of the Miter limit.
|
||
|
* [IN] grad1 - the unit tangent vector of the last edge.
|
||
|
* [IN] grad2 - the unit tangent vector of the current edge.
|
||
|
*
|
||
|
* Return Value:
|
||
|
*
|
||
|
* Turning direction from the last edge to the current edge.
|
||
|
*
|
||
|
\**************************************************************************/
|
||
|
|
||
|
GpTurningDirection
|
||
|
getHobbyJoin(
|
||
|
const GpPointF& point,
|
||
|
const GpPointF& grad1,
|
||
|
const GpPointF& grad2,
|
||
|
INT polyCount,
|
||
|
const GpPointF* polyPoints,
|
||
|
const REAL* polyAngles,
|
||
|
// const GpPointF& norm1,
|
||
|
// const GpPointF& norm2,
|
||
|
REAL leftWidth,
|
||
|
REAL rightWidth,
|
||
|
INT *leftCount,
|
||
|
GpPointF *leftPoints,
|
||
|
INT *rightCount,
|
||
|
GpPointF *rightPoints,
|
||
|
BOOL needsToAdjustNormals,
|
||
|
REAL miterLimit2,
|
||
|
BOOL isMiter,
|
||
|
BOOL useBevelJoinInside
|
||
|
)
|
||
|
{
|
||
|
if(miterLimit2 <= 1)
|
||
|
isMiter = FALSE;
|
||
|
|
||
|
GpTurningDirection direction = NotTurning;
|
||
|
|
||
|
// Handle the degenerate cases.
|
||
|
|
||
|
GpPointF v;
|
||
|
REAL cross;
|
||
|
|
||
|
direction = getTurningDirection(&cross, grad1, grad2);
|
||
|
|
||
|
if(direction == NotMoving)
|
||
|
{
|
||
|
*leftCount = 0;
|
||
|
*rightCount = 0;
|
||
|
return direction;
|
||
|
}
|
||
|
|
||
|
// Find the left vertex ids.
|
||
|
|
||
|
INT leftIndex1, leftIndex2;
|
||
|
leftIndex1 = getVertexID(grad1, TRUE, polyCount, polyAngles);
|
||
|
leftIndex2 = getVertexID(grad2, TRUE, polyCount, polyAngles);
|
||
|
|
||
|
INT i;
|
||
|
|
||
|
if(direction == TurningLeft)
|
||
|
{
|
||
|
*leftCount = 2;
|
||
|
leftPoints[0] = point + polyPoints[leftIndex1];
|
||
|
leftPoints[1] = point + polyPoints[leftIndex2];
|
||
|
}
|
||
|
else if(direction == TurningRight)
|
||
|
{
|
||
|
if(leftIndex2 > leftIndex1)
|
||
|
{
|
||
|
*leftCount = leftIndex2 - leftIndex1 + 1;
|
||
|
|
||
|
for(i = 0; i <= leftIndex2 - leftIndex1; i++)
|
||
|
leftPoints[i] = point + polyPoints[i + leftIndex1];
|
||
|
}
|
||
|
else if(leftIndex2 < leftIndex1)
|
||
|
{
|
||
|
*leftCount = polyCount - leftIndex1 + leftIndex2 + 1;
|
||
|
|
||
|
for(i = 0; i < polyCount - leftIndex1; i++)
|
||
|
leftPoints[i] = point + polyPoints[i + leftIndex1];
|
||
|
|
||
|
for(i = 0; i <= leftIndex2; i++)
|
||
|
leftPoints[polyCount - leftIndex1 + i]
|
||
|
= point + polyPoints[i];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*leftCount = 1;
|
||
|
leftPoints[0] = point + polyPoints[leftIndex1];
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*leftCount = 1;
|
||
|
leftPoints[0] = point + polyPoints[leftIndex1];
|
||
|
}
|
||
|
|
||
|
INT rightIndex1, rightIndex2;
|
||
|
rightIndex1 = getVertexID(grad1, FALSE, polyCount, polyAngles);
|
||
|
rightIndex2 = getVertexID(grad2, FALSE, polyCount, polyAngles);
|
||
|
|
||
|
if(direction == TurningRight)
|
||
|
{
|
||
|
*rightCount = 2;
|
||
|
rightPoints[0] = point + polyPoints[rightIndex1];
|
||
|
rightPoints[1] = point + polyPoints[rightIndex2];
|
||
|
}
|
||
|
else if(direction == TurningLeft)
|
||
|
{
|
||
|
if(rightIndex1 > rightIndex2)
|
||
|
{
|
||
|
*rightCount = rightIndex1 - rightIndex2 + 1;
|
||
|
|
||
|
for(i = 0; i <= rightIndex1 - rightIndex2; i++)
|
||
|
rightPoints[i] = point + polyPoints[rightIndex1 - i];
|
||
|
}
|
||
|
else if(rightIndex1 < rightIndex2)
|
||
|
{
|
||
|
*rightCount = polyCount - rightIndex2 + rightIndex1 + 1;
|
||
|
|
||
|
for(i = 0; i <= rightIndex1; i++)
|
||
|
rightPoints[i] = point + polyPoints[rightIndex1 - i];
|
||
|
|
||
|
for(i = 0; i < polyCount - rightIndex2; i++)
|
||
|
rightPoints[rightIndex1 + 1 + i]
|
||
|
= point + polyPoints[polyCount - i - 1];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*rightCount = 1;
|
||
|
rightPoints[0] = point + polyPoints[rightIndex1];
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*rightCount = 1;
|
||
|
rightPoints[0] = point + polyPoints[rightIndex1];
|
||
|
}
|
||
|
|
||
|
return direction;
|
||
|
}
|
||
|
|
||
|
GpTurningDirection
|
||
|
getJoin(
|
||
|
GpLineJoin lineJoin,
|
||
|
const GpPointF& point,
|
||
|
const GpPointF& grad1,
|
||
|
const GpPointF& grad2,
|
||
|
const GpPointF& norm1,
|
||
|
const GpPointF& norm2,
|
||
|
REAL leftWidth,
|
||
|
REAL rightWidth,
|
||
|
INT *leftCount,
|
||
|
GpPointF *leftPoints,
|
||
|
BOOL *leftInside,
|
||
|
INT *rightCount,
|
||
|
GpPointF *rightPoints,
|
||
|
BOOL *rightInside,
|
||
|
BOOL needsToAdjustNormals,
|
||
|
REAL miterLimit2,
|
||
|
BOOL useBevelJoinInside
|
||
|
)
|
||
|
{
|
||
|
BOOL isMiter = TRUE;
|
||
|
|
||
|
GpTurningDirection direction;
|
||
|
|
||
|
switch(lineJoin)
|
||
|
{
|
||
|
case LineJoinBevel:
|
||
|
isMiter = FALSE; // Fall through to Miter case.
|
||
|
|
||
|
case LineJoinMiterClipped:
|
||
|
// Treat Miter clipped joints that exceed the miter limit as
|
||
|
// beveled joints. Fall through to Miter case.
|
||
|
if (lineJoin == LineJoinMiterClipped &&
|
||
|
getMiterExceeded(grad1, grad2, miterLimit2))
|
||
|
{
|
||
|
isMiter = FALSE;
|
||
|
}
|
||
|
|
||
|
case LineJoinMiter:
|
||
|
direction = getMiterBevelJoin(point, grad1, grad2, norm1, norm2,
|
||
|
leftWidth, rightWidth,
|
||
|
leftCount, leftPoints, leftInside,
|
||
|
rightCount, rightPoints, rightInside,
|
||
|
needsToAdjustNormals, miterLimit2, isMiter, useBevelJoinInside);
|
||
|
break;
|
||
|
|
||
|
case LineJoinRound:
|
||
|
direction = getRoundJoin(point, grad1, grad2, norm1, norm2,
|
||
|
leftWidth, rightWidth,
|
||
|
leftCount, leftPoints, leftInside,
|
||
|
rightCount, rightPoints, rightInside,
|
||
|
needsToAdjustNormals, miterLimit2, useBevelJoinInside);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return direction;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**************************************************************************\
|
||
|
*
|
||
|
* Function Description:
|
||
|
*
|
||
|
* From the given the reference point, gradient, and widths,
|
||
|
* this returns the verteces for the round cap.
|
||
|
* The direction of the round cap is always clockwise.
|
||
|
*
|
||
|
* Arguments:
|
||
|
*
|
||
|
* [IN] point - The reference point.
|
||
|
* [IN] grad - The gradient.
|
||
|
* [IN] isStartCap - TRUE if this is the start cap.
|
||
|
* [IN] leftWidth - The left width from the reference.
|
||
|
* [IN] rightWidth - The right width from the reference point.
|
||
|
*
|
||
|
*
|
||
|
* Return Value:
|
||
|
*
|
||
|
* Ok if successfull.
|
||
|
*
|
||
|
* 06/16/99 ikkof
|
||
|
* Created it
|
||
|
*
|
||
|
\**************************************************************************/
|
||
|
|
||
|
GpStatus
|
||
|
GpPathWidener::SetRoundCap(
|
||
|
const GpPointF& point,
|
||
|
const GpPointF& grad,
|
||
|
BOOL isStartCap,
|
||
|
REAL leftWidth,
|
||
|
REAL rightWidth
|
||
|
)
|
||
|
{
|
||
|
if( (REALABS(grad.X) < REAL_EPSILON) &&
|
||
|
(REALABS(grad.Y) < REAL_EPSILON) )
|
||
|
{
|
||
|
return InvalidParameter;
|
||
|
}
|
||
|
|
||
|
GpPointF* capPoints = NULL;
|
||
|
BYTE* capTypes = NULL;
|
||
|
|
||
|
if(isStartCap)
|
||
|
{
|
||
|
CapPoints1.Reset(FALSE);
|
||
|
CapTypes1.Reset(FALSE);
|
||
|
capPoints = CapPoints1.AddMultiple(7);
|
||
|
if(capPoints)
|
||
|
capTypes = CapTypes1.AddMultiple(7);
|
||
|
|
||
|
if(!capPoints || !capTypes)
|
||
|
return OutOfMemory;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CapPoints2.Reset(FALSE);
|
||
|
CapTypes2.Reset(FALSE);
|
||
|
capPoints = CapPoints2.AddMultiple(7);
|
||
|
if(capPoints)
|
||
|
capTypes = CapTypes2.AddMultiple(7);
|
||
|
|
||
|
if(!capPoints || !capTypes)
|
||
|
return OutOfMemory;
|
||
|
}
|
||
|
|
||
|
GpMemset(capTypes, PathPointTypeBezier, 7);
|
||
|
capTypes[0] = PathPointTypeLine;
|
||
|
|
||
|
GpPointF tangent;
|
||
|
|
||
|
if(isStartCap)
|
||
|
{
|
||
|
tangent.X = - grad.X;
|
||
|
tangent.Y = - grad.Y;
|
||
|
}
|
||
|
else
|
||
|
tangent = grad;
|
||
|
|
||
|
REAL radius = (leftWidth - rightWidth)/2;
|
||
|
GpPointF center;
|
||
|
|
||
|
center.X = point.X + (leftWidth + rightWidth)*grad.Y/2;
|
||
|
center.Y = point.Y - (leftWidth + rightWidth)*grad.X/2;
|
||
|
|
||
|
if(isStartCap)
|
||
|
{
|
||
|
center.X -= Inset1*tangent.X;
|
||
|
center.Y -= Inset1*tangent.Y;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
center.X -= Inset2*tangent.X;
|
||
|
center.Y -= Inset2*tangent.Y;
|
||
|
}
|
||
|
|
||
|
REAL s1, c1;
|
||
|
|
||
|
// Direction of the left normal multipled by radius.
|
||
|
|
||
|
c1 = radius*tangent.Y;
|
||
|
s1 = - radius*tangent.X;
|
||
|
|
||
|
// 2 Bezier segments for a half circle with radius 1.
|
||
|
|
||
|
REAL u_cir = U_CIR;
|
||
|
capPoints[ 0].X = 1; capPoints[ 0].Y = 0;
|
||
|
capPoints[ 1].X = 1; capPoints[ 1].Y = u_cir;
|
||
|
capPoints[ 2].X = u_cir; capPoints[ 2].Y = 1;
|
||
|
capPoints[ 3].X = 0; capPoints[ 3].Y = 1;
|
||
|
capPoints[ 4].X = -u_cir; capPoints[ 4].Y = 1;
|
||
|
capPoints[ 5].X = -1; capPoints[ 5].Y = u_cir;
|
||
|
capPoints[ 6].X = -1; capPoints[ 6].Y = 0;
|
||
|
|
||
|
// Rotate, scale, and translate the original half circle.
|
||
|
|
||
|
for(INT i = 0; i < 7; i++)
|
||
|
{
|
||
|
REAL x, y;
|
||
|
|
||
|
x = capPoints[i].X;
|
||
|
y = capPoints[i].Y;
|
||
|
capPoints[i].X = (c1*x - s1*y) + center.X;
|
||
|
capPoints[i].Y = (s1*x + c1*y) + center.Y;
|
||
|
}
|
||
|
|
||
|
return Ok;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**************************************************************************\
|
||
|
*
|
||
|
* Function Description:
|
||
|
*
|
||
|
* Creates a double round cap for inset pen ('B' shaped)
|
||
|
*
|
||
|
* Arguments:
|
||
|
*
|
||
|
* [IN] point - The reference point.
|
||
|
* [IN] grad - The gradient.
|
||
|
* [IN] isStartCap - TRUE if this is the start cap.
|
||
|
* [IN] leftWidth - The left width from the reference.
|
||
|
* [IN] rightWidth - The right width from the reference point.
|
||
|
*
|
||
|
*
|
||
|
* Return Value:
|
||
|
*
|
||
|
* Ok if successfull.
|
||
|
*
|
||
|
* 10/01/2000 asecchia
|
||
|
* Created it
|
||
|
*
|
||
|
\**************************************************************************/
|
||
|
|
||
|
GpStatus
|
||
|
GpPathWidener::SetDoubleRoundCap(
|
||
|
const GpPointF& point,
|
||
|
const GpPointF& grad,
|
||
|
BOOL isStartCap,
|
||
|
REAL leftWidth,
|
||
|
REAL rightWidth
|
||
|
)
|
||
|
{
|
||
|
if( (REALABS(grad.X) < REAL_EPSILON) &&
|
||
|
(REALABS(grad.Y) < REAL_EPSILON) )
|
||
|
{
|
||
|
return InvalidParameter;
|
||
|
}
|
||
|
|
||
|
GpPointF* capPoints = NULL;
|
||
|
BYTE* capTypes = NULL;
|
||
|
|
||
|
if(isStartCap)
|
||
|
{
|
||
|
CapPoints1.Reset(FALSE);
|
||
|
CapTypes1.Reset(FALSE);
|
||
|
capPoints = CapPoints1.AddMultiple(14);
|
||
|
if(capPoints)
|
||
|
capTypes = CapTypes1.AddMultiple(14);
|
||
|
|
||
|
if(!capPoints || !capTypes)
|
||
|
return OutOfMemory;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CapPoints2.Reset(FALSE);
|
||
|
CapTypes2.Reset(FALSE);
|
||
|
capPoints = CapPoints2.AddMultiple(14);
|
||
|
if(capPoints)
|
||
|
capTypes = CapTypes2.AddMultiple(14);
|
||
|
|
||
|
if(!capPoints || !capTypes)
|
||
|
return OutOfMemory;
|
||
|
}
|
||
|
|
||
|
GpMemset(capTypes, PathPointTypeBezier, 14);
|
||
|
capTypes[0] = PathPointTypeLine;
|
||
|
capTypes[7] = PathPointTypeLine;
|
||
|
|
||
|
GpPointF tangent;
|
||
|
|
||
|
if(isStartCap)
|
||
|
{
|
||
|
tangent.X = - grad.X;
|
||
|
tangent.Y = - grad.Y;
|
||
|
}
|
||
|
else
|
||
|
tangent = grad;
|
||
|
|
||
|
REAL radius = (leftWidth - rightWidth)/2;
|
||
|
GpPointF center;
|
||
|
|
||
|
center.X = point.X + (leftWidth + rightWidth)*grad.Y/2;
|
||
|
center.Y = point.Y - (leftWidth + rightWidth)*grad.X/2;
|
||
|
|
||
|
if(isStartCap)
|
||
|
{
|
||
|
center.X -= Inset1*tangent.X;
|
||
|
center.Y -= Inset1*tangent.Y;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
center.X -= Inset2*tangent.X;
|
||
|
center.Y -= Inset2*tangent.Y;
|
||
|
}
|
||
|
|
||
|
REAL s1, c1;
|
||
|
|
||
|
// Direction of the left normal multipled by radius.
|
||
|
|
||
|
c1 = radius*tangent.Y;
|
||
|
s1 = - radius*tangent.X;
|
||
|
|
||
|
// 2 Bezier segments for a half circle with radius 1.
|
||
|
|
||
|
REAL u_cir = U_CIR;
|
||
|
capPoints[ 0].X = 1; capPoints[ 0].Y = 0;
|
||
|
capPoints[ 1].X = 1; capPoints[ 1].Y = u_cir;
|
||
|
capPoints[ 2].X = u_cir; capPoints[ 2].Y = 1;
|
||
|
capPoints[ 3].X = 0; capPoints[ 3].Y = 1;
|
||
|
capPoints[ 4].X = -u_cir; capPoints[ 4].Y = 1;
|
||
|
capPoints[ 5].X = -1; capPoints[ 5].Y = u_cir;
|
||
|
capPoints[ 6].X = -1; capPoints[ 6].Y = 0;
|
||
|
|
||
|
// Create the second bump and scale the first one.
|
||
|
|
||
|
for(int i=0; i<7; i++)
|
||
|
{
|
||
|
capPoints[i+7].X = capPoints[i].X * 0.5f-0.5f;
|
||
|
capPoints[i+7].Y = capPoints[i].Y * 0.5f;
|
||
|
capPoints[i].X = 0.5f + capPoints[i].X * 0.5f;
|
||
|
capPoints[i].Y = capPoints[i].Y * 0.5f;
|
||
|
}
|
||
|
|
||
|
// Rotate, scale, and translate the original half circle.
|
||
|
|
||
|
for(INT i = 0; i < 14; i++)
|
||
|
{
|
||
|
REAL x, y;
|
||
|
|
||
|
x = capPoints[i].X;
|
||
|
y = capPoints[i].Y;
|
||
|
capPoints[i].X = (c1*x - s1*y) + center.X;
|
||
|
capPoints[i].Y = (s1*x + c1*y) + center.Y;
|
||
|
}
|
||
|
|
||
|
return Ok;
|
||
|
}
|
||
|
|
||
|
GpStatus
|
||
|
GpPathWidener::SetTriangleCap(
|
||
|
const GpPointF& point,
|
||
|
const GpPointF& grad,
|
||
|
BOOL isStartCap,
|
||
|
REAL leftWidth,
|
||
|
REAL rightWidth,
|
||
|
const GpPointF *points,
|
||
|
INT pointCount
|
||
|
)
|
||
|
{
|
||
|
if( (REALABS(grad.X) < REAL_EPSILON) &&
|
||
|
(REALABS(grad.Y) < REAL_EPSILON) )
|
||
|
{
|
||
|
return InvalidParameter;
|
||
|
}
|
||
|
|
||
|
GpPointF* capPoints = NULL;
|
||
|
BYTE* capTypes = NULL;
|
||
|
|
||
|
DynByteArray *capTypesArray;
|
||
|
DynPointFArray *capPointsArray;
|
||
|
|
||
|
|
||
|
if(isStartCap)
|
||
|
{
|
||
|
CapPoints1.Reset(FALSE);
|
||
|
CapTypes1.Reset(FALSE);
|
||
|
capPoints = CapPoints1.AddMultiple(3);
|
||
|
if(capPoints)
|
||
|
capTypes = CapTypes1.AddMultiple(3);
|
||
|
|
||
|
if(!capPoints || !capTypes)
|
||
|
return OutOfMemory;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CapPoints2.Reset(FALSE);
|
||
|
CapTypes2.Reset(FALSE);
|
||
|
capPoints = CapPoints2.AddMultiple(3);
|
||
|
if(capPoints)
|
||
|
capTypes = CapTypes2.AddMultiple(3);
|
||
|
|
||
|
if(!capPoints || !capTypes)
|
||
|
return OutOfMemory;
|
||
|
}
|
||
|
|
||
|
GpMemset(&capTypes[0], PathPointTypeLine, 3);
|
||
|
|
||
|
|
||
|
GpPointF norm, tangent;
|
||
|
|
||
|
norm.X = grad.Y;
|
||
|
norm.Y = - grad.X;
|
||
|
|
||
|
if(isStartCap)
|
||
|
{
|
||
|
tangent.X = - grad.X;
|
||
|
tangent.Y = - grad.Y;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
tangent = grad;
|
||
|
}
|
||
|
|
||
|
GpPointF leftPt, rightPt;
|
||
|
|
||
|
leftPt.X = point.X + leftWidth*norm.X;
|
||
|
leftPt.Y = point.Y + leftWidth*norm.Y;
|
||
|
rightPt.X = point.X + rightWidth*norm.X;
|
||
|
rightPt.Y = point.Y + rightWidth*norm.Y;
|
||
|
|
||
|
GpPointF center;
|
||
|
|
||
|
REAL width = REALABS(leftWidth-rightWidth);
|
||
|
|
||
|
center.X = 0.5f*(leftPt.X + rightPt.X + width*tangent.X);
|
||
|
center.Y = 0.5f*(leftPt.Y + rightPt.Y + width*tangent.Y);
|
||
|
|
||
|
capPoints[1] = center;
|
||
|
|
||
|
if(isStartCap)
|
||
|
{
|
||
|
capPoints[0] = rightPt;
|
||
|
capPoints[2] = leftPt;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
capPoints[0] = leftPt;
|
||
|
capPoints[2] = rightPt;
|
||
|
}
|
||
|
|
||
|
return Ok;
|
||
|
}
|
||
|
|
||
|
/**************************************************************************\
|
||
|
*
|
||
|
* Function Description:
|
||
|
*
|
||
|
* Add the first widened point of the current path type.
|
||
|
*
|
||
|
* Arguments:
|
||
|
*
|
||
|
* [IN] leftWidth - The left width for widened line.
|
||
|
* [IN] rightWidth - The right width for the widened line.
|
||
|
* [IN] lineJoin - The type of the line join.
|
||
|
* [OUT] leftPoints1 - The buffer for the left points.
|
||
|
* [OUT] leftTypes1 - The buffer for the left types.
|
||
|
* [OUT] addedLeftCount - The number of the added left points and types.
|
||
|
* [OUT] rightPoints1 - The buffer for the right points.
|
||
|
* [OUT] rightTypes1 - The buffer for the right types.
|
||
|
* [OUT] addedRightCount - The number of the added right points and types.
|
||
|
* [OUT] leftEndPt - The end point of the left line for the current
|
||
|
* subpath. This is calculated only for the first
|
||
|
* subpath point.
|
||
|
* [OUT] rightEndPt - The end point of the right line for tha current
|
||
|
* subpath. This is calculated only for the first
|
||
|
* subpath point.
|
||
|
* [IN] grad - The gradients of the center points for the
|
||
|
* current path type.
|
||
|
* [IN] dataPoints - The center points data for the current path type
|
||
|
* [IN] dataCount - The number of data points in the current path type.
|
||
|
* [IN/OUT] lastPt - The last point used in calculations.
|
||
|
* [IN] flag - The flag to indicates the various properties
|
||
|
* of the current subpath and type.
|
||
|
*
|
||
|
*
|
||
|
* Return Value:
|
||
|
*
|
||
|
* NONE
|
||
|
*
|
||
|
* 01/24/2000 ikkof
|
||
|
* Created it
|
||
|
*
|
||
|
\**************************************************************************/
|
||
|
|
||
|
VOID
|
||
|
GpPathWidener::WidenFirstPoint(
|
||
|
REAL leftWidth,
|
||
|
REAL rightWidth,
|
||
|
GpLineJoin lineJoin,
|
||
|
REAL miterLimit2,
|
||
|
GpPointF* leftPoints,
|
||
|
BYTE* leftTypes,
|
||
|
INT* addedLeftCount,
|
||
|
GpPointF* rightPoints,
|
||
|
BYTE* rightTypes,
|
||
|
INT* addedRightCount,
|
||
|
GpPointF* leftEndPt,
|
||
|
GpPointF* rightEndPt,
|
||
|
const GpPointF* grad,
|
||
|
const GpPointF* norm,
|
||
|
const GpPointF* dataPoints,
|
||
|
INT dataCount,
|
||
|
GpPointF* lastPt,
|
||
|
const REAL* firstInsets,
|
||
|
INT flag
|
||
|
)
|
||
|
{
|
||
|
GpPointF nextPt = dataPoints[0];
|
||
|
GpPointF grad1, grad2;
|
||
|
GpPointF norm1, norm2;
|
||
|
|
||
|
|
||
|
INT leftCount = 0;
|
||
|
INT rightCount = 0;
|
||
|
grad1 = *grad++;
|
||
|
grad2 = *grad;
|
||
|
norm1 = *norm++;
|
||
|
norm2 = *norm;
|
||
|
|
||
|
INT numOfAddedFirstPts = 0;
|
||
|
|
||
|
if(flag & WideningFirstType)
|
||
|
{
|
||
|
BOOL needsToAdjustNormals = FALSE;
|
||
|
|
||
|
GpLineJoin lineJoin1 = lineJoin;
|
||
|
if(flag & WideningNeedsToAdjustNormals)
|
||
|
{
|
||
|
needsToAdjustNormals = TRUE;
|
||
|
lineJoin1 = LineJoinMiter; // Don't use RoundJoin.
|
||
|
}
|
||
|
|
||
|
if(!(flag & WideningClosed))
|
||
|
{
|
||
|
lineJoin1 = LineJoinBevel;
|
||
|
}
|
||
|
|
||
|
const INT bufferCount = 32;
|
||
|
GpPointF lPts[bufferCount], rPts[bufferCount];
|
||
|
INT lCnt, rCnt;
|
||
|
GpTurningDirection direction;
|
||
|
BOOL useBevelJoinInside = (flag & WideningUseBevelJoinInside) != 0;
|
||
|
|
||
|
INT polyCount = JoinPolygonPoints.GetCount();
|
||
|
const GpPointF* polyPoints = JoinPolygonPoints.GetDataBuffer();
|
||
|
const REAL* polyAngles = JoinPolygonAngles.GetDataBuffer();
|
||
|
|
||
|
BOOL leftInside = FALSE, rightInside = FALSE;
|
||
|
|
||
|
if(polyCount > 0)
|
||
|
direction = getHobbyJoin(
|
||
|
// lineJoin1,
|
||
|
nextPt,
|
||
|
grad1,
|
||
|
grad2,
|
||
|
polyCount,
|
||
|
polyPoints,
|
||
|
polyAngles,
|
||
|
leftWidth,
|
||
|
rightWidth,
|
||
|
&lCnt,
|
||
|
&lPts[0],
|
||
|
&rCnt,
|
||
|
&rPts[0],
|
||
|
needsToAdjustNormals,
|
||
|
miterLimit2,
|
||
|
FALSE, // IsMiter
|
||
|
useBevelJoinInside
|
||
|
);
|
||
|
else
|
||
|
direction = getJoin(
|
||
|
lineJoin1,
|
||
|
nextPt,
|
||
|
grad1,
|
||
|
grad2,
|
||
|
norm1,
|
||
|
norm2,
|
||
|
leftWidth,
|
||
|
rightWidth,
|
||
|
&lCnt,
|
||
|
&lPts[0],
|
||
|
&leftInside,
|
||
|
&rCnt,
|
||
|
&rPts[0],
|
||
|
&rightInside,
|
||
|
needsToAdjustNormals,
|
||
|
miterLimit2,
|
||
|
useBevelJoinInside
|
||
|
);
|
||
|
|
||
|
//!!! Inside flag check
|
||
|
if(leftInside)
|
||
|
{
|
||
|
ASSERT((lCnt & 0x01) == 0);
|
||
|
}
|
||
|
|
||
|
|
||
|
//!!! Inside flag check
|
||
|
if(rightInside)
|
||
|
{
|
||
|
ASSERT((rCnt & 0x01) == 0);
|
||
|
}
|
||
|
|
||
|
*leftEndPt = lPts[0];
|
||
|
*rightEndPt = rPts[0];
|
||
|
|
||
|
BYTE pathType;
|
||
|
|
||
|
if(flag & WideningClosed)
|
||
|
{
|
||
|
if(lCnt > 0)
|
||
|
{
|
||
|
pathType = PathPointTypeLine;
|
||
|
}
|
||
|
else if(lCnt < 0)
|
||
|
{
|
||
|
lCnt = - lCnt;
|
||
|
pathType = PathPointTypeBezier;
|
||
|
}
|
||
|
|
||
|
if(lCnt > 0)
|
||
|
{
|
||
|
//!!! Inside flag check
|
||
|
if(leftInside)
|
||
|
{
|
||
|
ASSERT((lCnt & 0x01) == 0);
|
||
|
}
|
||
|
|
||
|
if(leftInside)
|
||
|
pathType |= PathPointTypeInternalUse;
|
||
|
GpMemset(leftTypes, pathType, lCnt);
|
||
|
leftTypes[0] = PathPointTypeStart;
|
||
|
if(leftInside)
|
||
|
leftTypes[0] |= PathPointTypeInternalUse;
|
||
|
|
||
|
GpMemcpy(leftPoints, &lPts[0], lCnt*sizeof(GpPointF));
|
||
|
leftTypes += lCnt;
|
||
|
leftPoints += lCnt;
|
||
|
leftCount += lCnt;
|
||
|
}
|
||
|
|
||
|
if(rCnt > 0)
|
||
|
{
|
||
|
pathType = PathPointTypeLine;
|
||
|
}
|
||
|
else if(rCnt < 0)
|
||
|
{
|
||
|
rCnt = - rCnt;
|
||
|
pathType = PathPointTypeBezier;
|
||
|
}
|
||
|
|
||
|
if(rCnt > 0)
|
||
|
{
|
||
|
//!!! Inside flag check
|
||
|
if(rightInside)
|
||
|
{
|
||
|
ASSERT((rCnt & 0x01) == 0);
|
||
|
}
|
||
|
|
||
|
if(rightInside)
|
||
|
pathType |= PathPointTypeInternalUse;
|
||
|
GpMemset(rightTypes, pathType, rCnt);
|
||
|
rightTypes[0] = PathPointTypeStart;
|
||
|
if(rightInside)
|
||
|
rightTypes[0] |= PathPointTypeInternalUse;
|
||
|
|
||
|
GpMemcpy(rightPoints, &rPts[0], rCnt*sizeof(GpPointF));
|
||
|
rightTypes += rCnt;
|
||
|
rightPoints += rCnt;
|
||
|
rightCount += rCnt;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// The path is not closed. Bevel join is used.
|
||
|
|
||
|
GpPointF leftStartPt;
|
||
|
GpPointF rightStartPt;
|
||
|
INT index;
|
||
|
|
||
|
if(lCnt == 1)
|
||
|
index = 0;
|
||
|
else
|
||
|
index = 1;
|
||
|
leftStartPt = lPts[index];
|
||
|
|
||
|
if(rCnt == 1)
|
||
|
index = 0;
|
||
|
else
|
||
|
index = 1;
|
||
|
rightStartPt = rPts[index];
|
||
|
|
||
|
if(!(flag & WideningClosed) && firstInsets[0] != 0)
|
||
|
{
|
||
|
leftStartPt.X += firstInsets[0]*grad2.X;
|
||
|
leftStartPt.Y += firstInsets[0]*grad2.Y;
|
||
|
}
|
||
|
|
||
|
if(!(flag & WideningClosed) && firstInsets[1] != 0)
|
||
|
{
|
||
|
rightStartPt.X += firstInsets[1]*grad2.X;
|
||
|
rightStartPt.Y += firstInsets[1]*grad2.Y;
|
||
|
}
|
||
|
|
||
|
*leftTypes++ = PathPointTypeStart;
|
||
|
*rightTypes++ = PathPointTypeStart;
|
||
|
*leftPoints = leftStartPt;
|
||
|
*rightPoints = rightStartPt;
|
||
|
|
||
|
leftPoints++;
|
||
|
rightPoints++;
|
||
|
leftCount++;
|
||
|
rightCount++;
|
||
|
}
|
||
|
|
||
|
*lastPt = nextPt;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
leftCount = rightCount = 0;
|
||
|
}
|
||
|
|
||
|
*addedLeftCount = leftCount;
|
||
|
*addedRightCount = rightCount;
|
||
|
}
|
||
|
|
||
|
/**************************************************************************\
|
||
|
*
|
||
|
* Function Description:
|
||
|
*
|
||
|
* Add the widened points for Lines
|
||
|
*
|
||
|
* For the arguments, See comments for widenFirstPoints
|
||
|
*
|
||
|
\**************************************************************************/
|
||
|
|
||
|
GpStatus
|
||
|
GpPathWidener::WidenLinePoints(
|
||
|
REAL leftWidth,
|
||
|
REAL rightWidth,
|
||
|
GpLineJoin lineJoin,
|
||
|
REAL miterLimit2,
|
||
|
GpPointF* leftPoints,
|
||
|
BYTE* leftTypes,
|
||
|
INT* addedLeftCount,
|
||
|
GpPointF* rightPoints,
|
||
|
BYTE* rightTypes,
|
||
|
INT* addedRightCount,
|
||
|
const GpPointF* grad,
|
||
|
const GpPointF* norm,
|
||
|
const GpPointF* dataPoints,
|
||
|
INT dataCount,
|
||
|
GpPointF* lastPt,
|
||
|
const REAL* lastInsets,
|
||
|
INT flag
|
||
|
)
|
||
|
{
|
||
|
GpPointF grad1, grad2;
|
||
|
GpPointF norm1, norm2;
|
||
|
|
||
|
// Skip the first point since it is already added either by
|
||
|
// widenFirstPoint() or by the widen call of the previous type.
|
||
|
|
||
|
dataPoints++;
|
||
|
dataCount--; // The number of the remaining points.
|
||
|
|
||
|
// Also skip the first gradient.
|
||
|
|
||
|
grad++;
|
||
|
grad1 = *grad++;
|
||
|
norm++;
|
||
|
norm1 = *norm++;
|
||
|
|
||
|
BOOL isLastType = FALSE;
|
||
|
|
||
|
if(flag & WideningLastType)
|
||
|
isLastType = TRUE;
|
||
|
|
||
|
BOOL needsToAdjustNormals = FALSE;
|
||
|
|
||
|
GpLineJoin lineJoin1 = lineJoin;
|
||
|
if(flag & WideningNeedsToAdjustNormals)
|
||
|
{
|
||
|
needsToAdjustNormals = TRUE;
|
||
|
lineJoin1 = LineJoinMiter; // Don't use RoundJoin.
|
||
|
}
|
||
|
|
||
|
INT leftCount = 0, rightCount = 0;
|
||
|
BOOL isLastPoint = FALSE;
|
||
|
BYTE pathType = PathPointTypeLine;
|
||
|
|
||
|
INT jmax = dataCount;
|
||
|
if(isLastType)
|
||
|
{
|
||
|
if(flag & WideningClosed)
|
||
|
{
|
||
|
if(!(flag & WideningLastPointSame))
|
||
|
{
|
||
|
// When the subpath is closed, and the last point is not
|
||
|
// the same as the start point, don't regard this as
|
||
|
// the last type. Add points as usual.
|
||
|
|
||
|
isLastType = FALSE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// No need to add the last point since this is already
|
||
|
// added by the first point.
|
||
|
|
||
|
jmax--;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
BOOL useBevelJoinInside = (flag & WideningUseBevelJoinInside) != 0;
|
||
|
|
||
|
INT polyCount = JoinPolygonPoints.GetCount();
|
||
|
const GpPointF* polyPoints = JoinPolygonPoints.GetDataBuffer();
|
||
|
const REAL* polyAngles = JoinPolygonAngles.GetDataBuffer();
|
||
|
|
||
|
INT i, j;
|
||
|
for(j = 0; j < jmax; j++)
|
||
|
{
|
||
|
GpPointF nextPt = *dataPoints;
|
||
|
|
||
|
if(isLastType && (j == dataCount - 1))
|
||
|
{
|
||
|
isLastPoint = TRUE;
|
||
|
lineJoin1 = LineJoinBevel;
|
||
|
}
|
||
|
|
||
|
if(lastPt->X != nextPt.X || lastPt->Y != nextPt.Y)
|
||
|
{
|
||
|
grad2 = *grad;
|
||
|
norm2 = *norm;
|
||
|
|
||
|
const INT bufferCount = 32;
|
||
|
GpPointF lPts[bufferCount], rPts[bufferCount];
|
||
|
INT lCnt, rCnt;
|
||
|
GpTurningDirection direction;
|
||
|
BOOL leftInside = FALSE, rightInside = FALSE;
|
||
|
|
||
|
if(polyCount > 0)
|
||
|
direction = getHobbyJoin(
|
||
|
// lineJoin1,
|
||
|
nextPt,
|
||
|
grad1,
|
||
|
grad2,
|
||
|
polyCount,
|
||
|
polyPoints,
|
||
|
polyAngles,
|
||
|
leftWidth,
|
||
|
rightWidth,
|
||
|
&lCnt,
|
||
|
&lPts[0],
|
||
|
&rCnt,
|
||
|
&rPts[0],
|
||
|
needsToAdjustNormals,
|
||
|
miterLimit2,
|
||
|
FALSE, // IsMiter
|
||
|
useBevelJoinInside
|
||
|
);
|
||
|
else
|
||
|
direction = getJoin(
|
||
|
lineJoin1,
|
||
|
nextPt,
|
||
|
grad1,
|
||
|
grad2,
|
||
|
norm1,
|
||
|
norm2,
|
||
|
leftWidth,
|
||
|
rightWidth,
|
||
|
&lCnt,
|
||
|
&lPts[0],
|
||
|
&leftInside,
|
||
|
&rCnt,
|
||
|
&rPts[0],
|
||
|
&rightInside,
|
||
|
needsToAdjustNormals,
|
||
|
miterLimit2,
|
||
|
useBevelJoinInside
|
||
|
);
|
||
|
|
||
|
//!!! Inside flag check
|
||
|
if(leftInside)
|
||
|
{
|
||
|
ASSERT((lCnt & 0x01) == 0);
|
||
|
}
|
||
|
|
||
|
|
||
|
//!!! Inside flag check
|
||
|
if(rightInside)
|
||
|
{
|
||
|
ASSERT((rCnt & 0x01) == 0);
|
||
|
}
|
||
|
|
||
|
if(isLastPoint)
|
||
|
{
|
||
|
lCnt = 1;
|
||
|
rCnt = 1;
|
||
|
leftInside = FALSE;
|
||
|
rightInside = FALSE;
|
||
|
|
||
|
if(lastInsets[0] != 0)
|
||
|
{
|
||
|
lPts[0].X -= lastInsets[0]*grad1.X;
|
||
|
lPts[0].Y -= lastInsets[0]*grad1.Y;
|
||
|
}
|
||
|
|
||
|
if(lastInsets[1] != 0)
|
||
|
{
|
||
|
rPts[0].X -= lastInsets[1]*grad1.X;
|
||
|
rPts[0].Y -= lastInsets[1]*grad1.Y;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(lCnt > 0)
|
||
|
{
|
||
|
pathType = PathPointTypeLine;
|
||
|
}
|
||
|
else if(lCnt < 0)
|
||
|
{
|
||
|
lCnt = - lCnt;
|
||
|
pathType = PathPointTypeBezier;
|
||
|
}
|
||
|
|
||
|
if(lCnt > 0)
|
||
|
{
|
||
|
//!!! Inside flag check
|
||
|
if(leftInside)
|
||
|
{
|
||
|
ASSERT((lCnt & 0x01) == 0);
|
||
|
}
|
||
|
|
||
|
if(leftInside)
|
||
|
pathType |= PathPointTypeInternalUse;
|
||
|
GpMemset(leftTypes, pathType, lCnt);
|
||
|
leftTypes[0] = PathPointTypeLine;
|
||
|
if(leftInside)
|
||
|
leftTypes[0] |= PathPointTypeInternalUse;
|
||
|
|
||
|
GpMemcpy(leftPoints, &lPts[0], lCnt*sizeof(GpPointF));
|
||
|
leftTypes += lCnt;
|
||
|
leftPoints += lCnt;
|
||
|
leftCount += lCnt;
|
||
|
}
|
||
|
|
||
|
if(rCnt > 0)
|
||
|
{
|
||
|
pathType = PathPointTypeLine;
|
||
|
}
|
||
|
else if(rCnt < 0)
|
||
|
{
|
||
|
rCnt = - rCnt;
|
||
|
pathType = PathPointTypeBezier;
|
||
|
}
|
||
|
|
||
|
if(rCnt > 0)
|
||
|
{
|
||
|
//!!! Inside flag check
|
||
|
if(rightInside)
|
||
|
{
|
||
|
ASSERT((rCnt & 0x01) == 0);
|
||
|
}
|
||
|
|
||
|
if(rightInside)
|
||
|
pathType |= PathPointTypeInternalUse;
|
||
|
GpMemset(rightTypes, pathType, rCnt);
|
||
|
rightTypes[0] = PathPointTypeLine;
|
||
|
if(rightInside)
|
||
|
rightTypes[0] |= PathPointTypeInternalUse;
|
||
|
|
||
|
GpMemcpy(rightPoints, &rPts[0], rCnt*sizeof(GpPointF));
|
||
|
rightTypes += rCnt;
|
||
|
rightPoints += rCnt;
|
||
|
rightCount += rCnt;
|
||
|
}
|
||
|
|
||
|
grad1 = grad2;
|
||
|
norm1 = norm2;
|
||
|
*lastPt = nextPt;
|
||
|
}
|
||
|
|
||
|
grad++;
|
||
|
norm++;
|
||
|
dataPoints++;
|
||
|
}
|
||
|
|
||
|
*addedLeftCount = leftCount;
|
||
|
*addedRightCount = rightCount;
|
||
|
|
||
|
return Ok;
|
||
|
}
|
||
|
|
||
|
/**************************************************************************\
|
||
|
*
|
||
|
* Function Description:
|
||
|
*
|
||
|
* Add the widened points for Beziers
|
||
|
*
|
||
|
* For the arguments, See comments for widenFirstPoints
|
||
|
*
|
||
|
\**************************************************************************/
|
||
|
|
||
|
GpStatus
|
||
|
GpPathWidener::WidenBezierPoints(
|
||
|
REAL leftWidth,
|
||
|
REAL rightWidth,
|
||
|
GpLineJoin lineJoin,
|
||
|
REAL miterLimit2,
|
||
|
GpPointF* leftPoints,
|
||
|
BYTE* leftTypes,
|
||
|
INT* addedLeftCount,
|
||
|
GpPointF* rightPoints,
|
||
|
BYTE* rightTypes,
|
||
|
INT* addedRightCount,
|
||
|
const GpPointF* grad,
|
||
|
const GpPointF* norm,
|
||
|
const GpPointF* dataPoints,
|
||
|
INT dataCount,
|
||
|
GpPointF* lastPt,
|
||
|
const REAL* lastInsets,
|
||
|
INT flag
|
||
|
)
|
||
|
{
|
||
|
//!!! Kink removal has not been considered here yet.
|
||
|
|
||
|
GpPointF grad1, grad2;
|
||
|
GpPointF norm1, norm2;
|
||
|
|
||
|
// Skip the first point since it is already added either by
|
||
|
// widenFirstPoint() or by the widen call of the previous type.
|
||
|
|
||
|
dataPoints++;
|
||
|
dataCount--; // The number of the remaining points.
|
||
|
|
||
|
// Also skip the first gradient.
|
||
|
|
||
|
grad++;
|
||
|
grad1 = *grad++;
|
||
|
|
||
|
norm++;
|
||
|
norm1 = *norm++;
|
||
|
|
||
|
BOOL isLastType = FALSE;
|
||
|
|
||
|
if(flag & WideningLastType)
|
||
|
isLastType = TRUE;
|
||
|
|
||
|
BOOL needsToAdjustNormals = FALSE;
|
||
|
|
||
|
GpLineJoin lineJoin1 = lineJoin;
|
||
|
if(flag & WideningNeedsToAdjustNormals)
|
||
|
{
|
||
|
needsToAdjustNormals = TRUE;
|
||
|
lineJoin1 = LineJoinMiter; // Don't use RoundJoin.
|
||
|
}
|
||
|
|
||
|
INT remainder = dataCount % 3;
|
||
|
INT bezierCount = dataCount/3;
|
||
|
|
||
|
ASSERT(remainder == 0); // dataCount must be multiple of 3.
|
||
|
|
||
|
INT leftCount = 0, rightCount = 0;
|
||
|
BOOL isLastPoint = FALSE;
|
||
|
BYTE pathType = PathPointTypeBezier;
|
||
|
|
||
|
if(isLastType)
|
||
|
{
|
||
|
if((flag & WideningClosed) && !(flag & WideningLastPointSame))
|
||
|
{
|
||
|
// When the subpath is closed, and the last point is not
|
||
|
// the same as the start point, don't regard this as
|
||
|
// the last type. Add points as usual.
|
||
|
|
||
|
isLastType = FALSE;
|
||
|
}
|
||
|
|
||
|
// When the path is closed and the last point is the same,
|
||
|
// we must do the special treatment since the last join points
|
||
|
// were already added as the first join points.
|
||
|
// So keep isLastType to TRUE for this case.
|
||
|
}
|
||
|
|
||
|
BOOL useBevelJoinInside = flag & WideningUseBevelJoinInside;
|
||
|
|
||
|
INT i, j;
|
||
|
for(j = 0; j < bezierCount; j++)
|
||
|
{
|
||
|
for(INT k = 0; k < 3; k++)
|
||
|
{
|
||
|
GpPointF nextPt = *dataPoints;
|
||
|
|
||
|
if(k < 2)
|
||
|
{
|
||
|
// Second and third control point.
|
||
|
|
||
|
lineJoin1 = LineJoinMiter;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// The last control point.
|
||
|
|
||
|
// lineJoin1 = lineJoin;
|
||
|
lineJoin1 = LineJoinRound;
|
||
|
}
|
||
|
|
||
|
if(isLastType
|
||
|
&& (j == bezierCount - 1) && (k == 2))
|
||
|
{
|
||
|
isLastPoint = TRUE;
|
||
|
|
||
|
if(!(flag & WideningClosed))
|
||
|
{
|
||
|
// When the subpath is not closed, make the
|
||
|
// last join as Bevel join.
|
||
|
|
||
|
lineJoin1 = LineJoinBevel;
|
||
|
|
||
|
// When the subpath is closed, use the current
|
||
|
// join.
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
lineJoin1 = LineJoinRound;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
grad2 = *grad;
|
||
|
norm2 = *norm;
|
||
|
GpPointF lPts[7], rPts[7];
|
||
|
INT lCnt, rCnt;
|
||
|
GpTurningDirection direction;
|
||
|
BOOL leftInside = FALSE, rightInside = FALSE;
|
||
|
|
||
|
direction = getJoin(
|
||
|
lineJoin1,
|
||
|
nextPt,
|
||
|
grad1,
|
||
|
grad2,
|
||
|
norm1,
|
||
|
norm2,
|
||
|
leftWidth,
|
||
|
rightWidth,
|
||
|
&lCnt,
|
||
|
&lPts[0],
|
||
|
&leftInside,
|
||
|
&rCnt,
|
||
|
&rPts[0],
|
||
|
&rightInside,
|
||
|
needsToAdjustNormals,
|
||
|
miterLimit2,
|
||
|
useBevelJoinInside
|
||
|
);
|
||
|
|
||
|
if(k < 2)
|
||
|
{
|
||
|
// In case that the miter join was not availabe
|
||
|
// for k < 2, take the average of two vectors.
|
||
|
|
||
|
if(lCnt == 2)
|
||
|
{
|
||
|
lPts[0].X = (lPts[0].X + lPts[1].X)/2;
|
||
|
lPts[0].Y = (lPts[0].Y + lPts[1].Y)/2;
|
||
|
}
|
||
|
lCnt = 1;
|
||
|
|
||
|
if(rCnt == 2)
|
||
|
{
|
||
|
rPts[0].X = (rPts[0].X + rPts[1].X)/2;
|
||
|
rPts[0].Y = (rPts[0].Y + rPts[1].Y)/2;
|
||
|
}
|
||
|
rCnt = 1;
|
||
|
}
|
||
|
|
||
|
if(isLastPoint)
|
||
|
{
|
||
|
// In order to keep the 3n point format for the Bezier
|
||
|
// curves, we must add the first point of the join
|
||
|
// points as the last point of the last Bezier segment.
|
||
|
|
||
|
if(!(flag & WideningClosed))
|
||
|
{
|
||
|
lCnt = 1;
|
||
|
rCnt = 1;
|
||
|
|
||
|
if(lastInsets[0] != 0)
|
||
|
{
|
||
|
lPts[0].X -= lastInsets[0]*grad1.X;
|
||
|
lPts[0].Y -= lastInsets[0]*grad1.Y;
|
||
|
}
|
||
|
|
||
|
if(lastInsets[1] != 0)
|
||
|
{
|
||
|
rPts[0].X -= lastInsets[1]*grad1.X;
|
||
|
rPts[0].Y -= lastInsets[1]*grad1.Y;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
*leftPoints++ = lPts[0];
|
||
|
*leftTypes++ = pathType;
|
||
|
leftCount++;
|
||
|
|
||
|
*rightPoints++ = rPts[0];
|
||
|
*rightTypes++ = pathType;
|
||
|
rightCount++;
|
||
|
|
||
|
if(k == 2)
|
||
|
{
|
||
|
if(lCnt > 1)
|
||
|
{
|
||
|
*leftPoints++ = lPts[1];
|
||
|
*leftTypes++ = PathPointTypeLine;
|
||
|
leftCount++;
|
||
|
}
|
||
|
else if(lCnt < 0)
|
||
|
{
|
||
|
lCnt = - lCnt;
|
||
|
ASSERT(lCnt % 3 == 1);
|
||
|
GpMemcpy(leftPoints, &lPts[1], (lCnt - 1)*sizeof(GpPointF));
|
||
|
GpMemset(leftTypes, pathType, lCnt - 1);
|
||
|
leftPoints += lCnt - 1;
|
||
|
leftTypes += lCnt - 1;
|
||
|
leftCount += lCnt - 1;
|
||
|
}
|
||
|
|
||
|
if(rCnt > 1)
|
||
|
{
|
||
|
*rightPoints++ = rPts[1];
|
||
|
*rightTypes++ = PathPointTypeLine;
|
||
|
rightCount++;
|
||
|
}
|
||
|
else if(rCnt < 0)
|
||
|
{
|
||
|
rCnt = - rCnt;
|
||
|
ASSERT(rCnt % 3 == 1);
|
||
|
GpMemcpy(rightPoints, &rPts[1], (rCnt - 1)*sizeof(GpPointF));
|
||
|
GpMemset(rightTypes, pathType, rCnt - 1);
|
||
|
rightPoints += rCnt - 1;
|
||
|
rightTypes += rCnt - 1;
|
||
|
rightCount += rCnt - 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
grad1 = grad2;
|
||
|
norm1 = norm2;
|
||
|
*lastPt = nextPt;
|
||
|
|
||
|
grad++;
|
||
|
norm++;
|
||
|
dataPoints++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
*addedLeftCount = leftCount;
|
||
|
*addedRightCount = rightCount;
|
||
|
|
||
|
return Ok;
|
||
|
}
|
||
|
|
||
|
/**************************************************************************\
|
||
|
*
|
||
|
* Function Description:
|
||
|
*
|
||
|
* Add the widened points for each path type.
|
||
|
*
|
||
|
* For the arguments, See comments for widenFirstPoints
|
||
|
*
|
||
|
\**************************************************************************/
|
||
|
|
||
|
GpStatus
|
||
|
GpPathWidener::WidenEachPathType(
|
||
|
BYTE pathType,
|
||
|
REAL leftWidth,
|
||
|
REAL rightWidth,
|
||
|
GpLineJoin lineJoin,
|
||
|
REAL miterLimit2,
|
||
|
GpPointF* leftPoints,
|
||
|
BYTE* leftTypes,
|
||
|
INT* addedLeftCount,
|
||
|
GpPointF* rightPoints,
|
||
|
BYTE* rightTypes,
|
||
|
INT* addedRightCount,
|
||
|
const GpPointF* grad,
|
||
|
const GpPointF* norm,
|
||
|
const GpPointF* dataPoints,
|
||
|
INT dataCount,
|
||
|
GpPointF* lastPt,
|
||
|
const REAL* lastInsets,
|
||
|
INT flag
|
||
|
)
|
||
|
{
|
||
|
GpStatus status = GenericError;
|
||
|
|
||
|
switch(pathType)
|
||
|
{
|
||
|
case PathPointTypeLine:
|
||
|
status = WidenLinePoints(
|
||
|
leftWidth,
|
||
|
rightWidth,
|
||
|
lineJoin,
|
||
|
miterLimit2,
|
||
|
leftPoints,
|
||
|
leftTypes,
|
||
|
addedLeftCount,
|
||
|
rightPoints,
|
||
|
rightTypes,
|
||
|
addedRightCount,
|
||
|
grad,
|
||
|
norm,
|
||
|
dataPoints,
|
||
|
dataCount,
|
||
|
lastPt,
|
||
|
lastInsets,
|
||
|
flag);
|
||
|
break;
|
||
|
|
||
|
case PathPointTypeBezier:
|
||
|
status = WidenBezierPoints(
|
||
|
leftWidth,
|
||
|
rightWidth,
|
||
|
lineJoin,
|
||
|
miterLimit2,
|
||
|
leftPoints,
|
||
|
leftTypes,
|
||
|
addedLeftCount,
|
||
|
rightPoints,
|
||
|
rightTypes,
|
||
|
addedRightCount,
|
||
|
grad,
|
||
|
norm,
|
||
|
dataPoints,
|
||
|
dataCount,
|
||
|
lastPt,
|
||
|
lastInsets,
|
||
|
flag);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
WARNING(("Trying to widen undefined types."));
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
REAL
|
||
|
getCapDelta(
|
||
|
const DpPen* pen
|
||
|
)
|
||
|
{
|
||
|
GpLineCap startCap = pen->StartCap;
|
||
|
GpLineCap endCap = pen->EndCap;
|
||
|
GpLineCap dashCap = pen->DashCap;
|
||
|
|
||
|
REAL delta = 0, delta1;
|
||
|
|
||
|
if(!(startCap & LineCapAnchorMask))
|
||
|
delta1 = 0.5f;
|
||
|
else
|
||
|
delta1 = 3.0f; // We must adjust later.
|
||
|
|
||
|
if(delta < delta1)
|
||
|
delta = delta1;
|
||
|
|
||
|
if(!(endCap & LineCapAnchorMask))
|
||
|
delta1 = 0.5f;
|
||
|
else
|
||
|
delta1 = 3.0f; // We must adjust later.
|
||
|
|
||
|
if(delta < delta1)
|
||
|
delta = delta1;
|
||
|
|
||
|
if(!(dashCap & LineCapAnchorMask))
|
||
|
delta1 = 0.5f;
|
||
|
else
|
||
|
delta1 = 3.0f; // We must adjust later.
|
||
|
|
||
|
if(delta < delta1)
|
||
|
delta = delta1;
|
||
|
|
||
|
//!!! Add cutom line case.
|
||
|
|
||
|
return 1.0f;
|
||
|
}
|
||
|
|
||
|
/**************************************************************************\
|
||
|
*
|
||
|
* Function Description:
|
||
|
*
|
||
|
* This calculates the extra width due to pen.
|
||
|
*
|
||
|
* Arguments:
|
||
|
*
|
||
|
* None
|
||
|
*
|
||
|
* Return Value:
|
||
|
*
|
||
|
* The extra width.
|
||
|
*
|
||
|
* 02/29/00 ikkof
|
||
|
* Created it
|
||
|
*
|
||
|
\**************************************************************************/
|
||
|
|
||
|
REAL
|
||
|
GpPathWidener::GetPenDelta()
|
||
|
{
|
||
|
const GpPointF* centerPoints = CenterPoints.GetDataBuffer();
|
||
|
const BYTE* centerTypes = CenterTypes.GetDataBuffer();
|
||
|
INT centerCount = CenterPoints.GetCount();
|
||
|
|
||
|
INT startIndex, endIndex;
|
||
|
BOOL isClosed;
|
||
|
GpStatus status = Ok;
|
||
|
|
||
|
REAL scale;
|
||
|
|
||
|
switch(Pen->PenAlignment)
|
||
|
{
|
||
|
case PenAlignmentCenter:
|
||
|
default:
|
||
|
scale = 0.5f;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
REAL capDelta = getCapDelta(Pen);
|
||
|
|
||
|
REAL joinDelta = 1.0f;
|
||
|
|
||
|
if(Pen->Join == LineJoinMiter ||
|
||
|
Pen->Join == LineJoinMiterClipped)
|
||
|
{
|
||
|
while(Iterator.NextSubpath(&startIndex, &endIndex, &isClosed)
|
||
|
&& status == Ok)
|
||
|
{
|
||
|
status = CalculateGradients(startIndex, endIndex);
|
||
|
|
||
|
if(status == Ok)
|
||
|
{
|
||
|
REAL delta = GetSubpathPenMiterDelta(isClosed);
|
||
|
if(delta > joinDelta)
|
||
|
joinDelta = delta;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(status != Ok)
|
||
|
{
|
||
|
// We have to use the possible maximum for miter join.
|
||
|
// Usually this is an over-estimate since the most path
|
||
|
// don't have very sharp edges which correspond to miter limit.
|
||
|
|
||
|
joinDelta = Pen->MiterLimit;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
REAL penDelta = max(joinDelta, capDelta)*scale;
|
||
|
|
||
|
if(NeedsToTransform)
|
||
|
{
|
||
|
// This is already in device unit.
|
||
|
|
||
|
penDelta *= StrokeWidth;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Convert the width to the device unit.
|
||
|
|
||
|
penDelta *= MaximumWidth;
|
||
|
}
|
||
|
if(penDelta < 1)
|
||
|
penDelta = 1;
|
||
|
|
||
|
return penDelta;
|
||
|
}
|
||
|
|
||
|
/**************************************************************************\
|
||
|
*
|
||
|
* Function Description:
|
||
|
*
|
||
|
* This calculates the extra within a subpath due to a pen.
|
||
|
* This is called by GetPenDelta().
|
||
|
*
|
||
|
* Arguments:
|
||
|
*
|
||
|
* None
|
||
|
*
|
||
|
* Return Value:
|
||
|
*
|
||
|
* The extra width.
|
||
|
*
|
||
|
* 02/29/00 ikkof
|
||
|
* Created it
|
||
|
*
|
||
|
\**************************************************************************/
|
||
|
|
||
|
REAL
|
||
|
GpPathWidener::GetSubpathPenMiterDelta(
|
||
|
BOOL isClosed
|
||
|
)
|
||
|
{
|
||
|
INT count = Gradients.GetCount();
|
||
|
|
||
|
GpPointF* grad0 = Gradients.GetDataBuffer();
|
||
|
|
||
|
INT imin, imax;
|
||
|
if(isClosed)
|
||
|
{
|
||
|
imin = 0;
|
||
|
imax = count - 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
imin = 1;
|
||
|
imax = count - 2;
|
||
|
}
|
||
|
|
||
|
GpPointF* grad = grad0 + imin;
|
||
|
GpPointF prevGrad = *grad++;
|
||
|
GpPointF nextGrad;
|
||
|
|
||
|
REAL dot = 0;
|
||
|
|
||
|
for(INT i = imin; i < imax; i++)
|
||
|
{
|
||
|
nextGrad = *grad++;
|
||
|
REAL dot1 = prevGrad.X*nextGrad.X + prevGrad.Y*nextGrad.Y;
|
||
|
prevGrad = nextGrad;
|
||
|
|
||
|
if(dot1 < dot)
|
||
|
dot = dot1;
|
||
|
}
|
||
|
|
||
|
REAL cosHalfTheta = (dot + 1.0f)*0.5f;
|
||
|
REAL miterDelta = Pen->MiterLimit;
|
||
|
|
||
|
// If the miterDelta is smaller than the miter limit, calculate it.
|
||
|
|
||
|
if(cosHalfTheta > 0 && cosHalfTheta*miterDelta*miterDelta > 1)
|
||
|
{
|
||
|
cosHalfTheta = REALSQRT(cosHalfTheta);
|
||
|
miterDelta = 1.0f/cosHalfTheta;
|
||
|
}
|
||
|
|
||
|
return miterDelta;
|
||
|
}
|