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