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