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

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;
}