|
|
/**************************************************************************\
* * 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 n = CenterTypes.GetCount(); ctrTypes += n; ctrPoints += n;
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;
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; }
|