diff --git a/Frameworks/CoreGraphics/CGPath.mm b/Frameworks/CoreGraphics/CGPath.mm index 974158ae19..e1f2a7dd91 100644 --- a/Frameworks/CoreGraphics/CGPath.mm +++ b/Frameworks/CoreGraphics/CGPath.mm @@ -14,7 +14,6 @@ // //****************************************************************************** -#import "CGContextInternal.h" #import #import #import @@ -34,8 +33,7 @@ static const wchar_t* TAG = L"CGPath"; -// -static inline CGPoint __CreateCGPointWithTransform(CGFloat x, CGFloat y, const CGAffineTransform* transform) { +inline CGPoint __CreateCGPointWithTransform(CGFloat x, CGFloat y, const CGAffineTransform* transform) { CGPoint pt{ x, y }; if (transform) { pt = CGPointApplyAffineTransform(pt, *transform); @@ -51,22 +49,46 @@ static inline CGPoint __CreateCGPointWithTransform(CGFloat x, CGFloat y, const C ComPtr pathGeometry; ComPtr geometrySink; - bool isFigureClosed; + bool figureClosed; CGPoint currentPoint{ 0, 0 }; CGPoint startingPoint{ 0, 0 }; - __CGPathImpl() : isFigureClosed(true) { + __CGPathImpl() : figureClosed(true) { } }; struct __CGPath : CoreFoundation::CppBase<__CGPath, __CGPathImpl> { + ComPtr GetPathGeometry() { + return _impl.pathGeometry; + } + + ComPtr GetGeometrySink() { + return _impl.geometrySink; + } + + CGPoint GetCurrentPoint() { + return _impl.currentPoint; + } + + CGPoint GetStartingPoint() { + return _impl.startingPoint; + } + + void SetCurrentPoint(CGPoint newPoint) { + _impl.currentPoint = newPoint; + } + + void SetStartingPoint(CGPoint newPoint) { + _impl.startingPoint = newPoint; + } + // A private helper function for re-opening a path geometry. CGPath does not // have a concept of an open and a closed path but D2D relies on it. A // path/sink cannot be read from while the path is open thus it must be // closed. However, a CGPath can be edited again after being read from so // we must open the path again. This cannot be done normally, so we must // create a new path with the old path information to edit. - void preparePathForEditing() { + HRESULT PreparePathForEditing() { if (!_impl.geometrySink) { // Re-open this geometry. ComPtr factory = _GetD2DFactoryInstance(); @@ -78,45 +100,48 @@ void preparePathForEditing() { // Open a new path that the contents of the old path will be streamed into. We cannot re-use the same path as it is now closed // and cannot be opened again. We use the newPath variable because the factory was returning the same pointer for some strange // reason so this will force it to do otherwise. - FAIL_FAST_IF_FAILED(factory->CreatePathGeometry(&newPath)); - FAIL_FAST_IF_FAILED(newPath->Open(&newSink)); - FAIL_FAST_IF_FAILED(_impl.pathGeometry->Stream(newSink.Get())); + RETURN_IF_FAILED(factory->CreatePathGeometry(&newPath)); + RETURN_IF_FAILED(newPath->Open(&newSink)); + RETURN_IF_FAILED(_impl.pathGeometry->Stream(newSink.Get())); _impl.pathGeometry = newPath; _impl.geometrySink = newSink; // Without a new figure being created, it's by default closed - _impl.isFigureClosed = true; + _impl.figureClosed = true; } + return S_OK; } - void closePath() { + void ClosePath() { if (_impl.geometrySink) { - endFigure(D2D1_FIGURE_END_OPEN); + EndFigure(D2D1_FIGURE_END_OPEN); _impl.geometrySink->Close(); _impl.geometrySink = nullptr; } } - void beginFigure() { - if (_impl.isFigureClosed) { + void BeginFigure() { + if (_impl.figureClosed) { _impl.geometrySink->BeginFigure(_CGPointToD2D_F(_impl.currentPoint), D2D1_FIGURE_BEGIN_FILLED); - _impl.isFigureClosed = false; + _impl.figureClosed = false; } } - void endFigure(D2D1_FIGURE_END figureStatus) { - if (!_impl.isFigureClosed) { + void EndFigure(D2D1_FIGURE_END figureStatus) { + if (!_impl.figureClosed) { _impl.geometrySink->EndFigure(figureStatus); - _impl.isFigureClosed = true; + _impl.figureClosed = true; } } - void initializeGeometries() { + HRESULT InitializeGeometries() { ComPtr factory = _GetD2DFactoryInstance(); - FAIL_FAST_IF_FAILED(factory->CreatePathGeometry(&_impl.pathGeometry)); - FAIL_FAST_IF_FAILED(_impl.pathGeometry->Open(&_impl.geometrySink)); + RETURN_IF_FAILED(factory->CreatePathGeometry(&_impl.pathGeometry)); + RETURN_IF_FAILED(_impl.pathGeometry->Open(&_impl.geometrySink)); + + return S_OK; } }; @@ -125,8 +150,33 @@ CFTypeID CGPathGetTypeID() { } static Boolean __CGPathEqual(CFTypeRef cf1, CFTypeRef cf2) { + if (!cf1 && !cf2) { + return true; + } + RETURN_FALSE_IF(!cf1); + RETURN_FALSE_IF(!cf2); + __CGPath* path1 = (__CGPath*)cf1; __CGPath* path2 = (__CGPath*)cf2; + + path1->ClosePath(); + path2->ClosePath(); + + // ID2D1 Geometries have no isEquals method. However, for two geometries to be equal they are both reported to contain the other. + // Thus we must do two comparisons. + D2D1_GEOMETRY_RELATION relation = D2D1_GEOMETRY_RELATION_UNKNOWN; + RETURN_FALSE_IF_FAILED( + path1->GetPathGeometry()->CompareWithGeometry(path2->GetPathGeometry().Get(), D2D1::IdentityMatrix(), &relation)); + + // Does path 1 contain path 2? + if (relation == D2D1_GEOMETRY_RELATION_IS_CONTAINED) { + RETURN_FALSE_IF_FAILED( + path2->GetPathGeometry()->CompareWithGeometry(path1->GetPathGeometry().Get(), D2D1::IdentityMatrix(), &relation)); + + // Return true if path 2 also contains path 1 + return (relation == D2D1_GEOMETRY_RELATION_IS_CONTAINED ? true : false); + } + return false; } @@ -136,7 +186,7 @@ static Boolean __CGPathEqual(CFTypeRef cf1, CFTypeRef cf2) { CGMutablePathRef CGPathCreateMutable() { __CGPath* mutableRet = __CGPath::CreateInstance(); - mutableRet->initializeGeometries(); + FAIL_FAST_IF_FAILED(mutableRet->InitializeGeometries()); return mutableRet; } @@ -146,9 +196,7 @@ CGMutablePathRef CGPathCreateMutable() { @Notes Creates a mutable copy */ CGPathRef CGPathCreateCopy(CGPathRef path) { - if (path == NULL) { - return NULL; - } + RETURN_NULL_IF(!path); return CGPathCreateMutableCopy(path); } @@ -157,21 +205,19 @@ CGPathRef CGPathCreateCopy(CGPathRef path) { @Status Interoperable */ CGMutablePathRef CGPathCreateMutableCopy(CGPathRef path) { - if (path == NULL) { - return NULL; - } + RETURN_NULL_IF(!path); CGMutablePathRef mutableRet = CGPathCreateMutable(); // In order to call stream and copy the contents of the original path into the // new copy we must close this path. // Otherwise the D2D calls will return that a bad state has been entered. - path->closePath(); + path->ClosePath(); - FAIL_FAST_IF_FAILED(path->_impl.pathGeometry->Stream(mutableRet->_impl.geometrySink.Get())); + FAIL_FAST_IF_FAILED(path->GetPathGeometry()->Stream(mutableRet->GetGeometrySink().Get())); - mutableRet->_impl.currentPoint = path->_impl.currentPoint; - mutableRet->_impl.startingPoint = path->_impl.startingPoint; + mutableRet->SetCurrentPoint(path->GetCurrentPoint()); + mutableRet->SetStartingPoint(path->GetStartingPoint()); return mutableRet; } @@ -180,18 +226,16 @@ CGMutablePathRef CGPathCreateMutableCopy(CGPathRef path) { @Status Interoperable */ void CGPathAddLineToPoint(CGMutablePathRef path, const CGAffineTransform* transform, CGFloat x, CGFloat y) { - if (path == NULL) { - return; - } + RETURN_IF(!path); - path->preparePathForEditing(); + FAIL_FAST_IF_FAILED(path->PreparePathForEditing()); CGPoint pt = __CreateCGPointWithTransform(x, y, transform); - path->beginFigure(); - path->_impl.geometrySink->AddLine(_CGPointToD2D_F(pt)); + path->BeginFigure(); + path->GetGeometrySink()->AddLine(_CGPointToD2D_F(pt)); - path->_impl.currentPoint = pt; + path->SetCurrentPoint(pt); } CGFloat _CGPathControlPointOffsetMultiplier(CGFloat angle) { @@ -323,9 +367,7 @@ void CGPathAddArc(CGMutablePathRef path, CGFloat startAngle, CGFloat endAngle, bool clockwise) { - if (path == NULL) { - return; - } + RETURN_IF(!path); // Normalize the start angle so it's between 0 and 2*pi startAngle = fmod(startAngle, 2.0f * M_PI); @@ -365,22 +407,22 @@ void CGPathAddArc(CGMutablePathRef path, @Status Interoperable */ void CGPathMoveToPoint(CGMutablePathRef path, const CGAffineTransform* transform, CGFloat x, CGFloat y) { - // CGPathMoveToPoint does not update the geometry in any way. CGPaths do not consider these actions to be - // segments of the path and are not considered on CGPathApply. Because of this we do not want to call - // BeginFigure or anything else on the D2DPath because that would modify it's state in an invalid way. - CGPoint pt = __CreateCGPointWithTransform(x, y, transform); + RETURN_IF(!path); - path->_impl.startingPoint = pt; - path->_impl.currentPoint = pt; + // CGPaths do not consider these actions to be segments of the path and are not considered on CGPathApply, thus we should simply end the + // current figure and move the location of this path to the new point. + path->EndFigure(D2D1_FIGURE_END_OPEN); + + CGPoint pt = __CreateCGPointWithTransform(x, y, transform); + path->SetStartingPoint(pt); + path->SetCurrentPoint(pt); } /** @Status Interoperable */ void CGPathAddLines(CGMutablePathRef path, const CGAffineTransform* transform, const CGPoint* points, size_t count) { - if (count == 0 || points == NULL || path == NULL) { - return; - } + RETURN_IF(count == 0 || !points || !path); for (int i = 0; i < count; i++) { CGPathAddLineToPoint(path, transform, points[i].x, points[i].y); @@ -391,9 +433,7 @@ void CGPathAddLines(CGMutablePathRef path, const CGAffineTransform* transform, c @Status Interoperable */ void CGPathAddRect(CGMutablePathRef path, const CGAffineTransform* transform, CGRect rect) { - if (path == NULL) { - return; - } + RETURN_IF(!path); CGPathMoveToPoint(path, transform, CGRectGetMinX(rect), CGRectGetMinY(rect)); @@ -408,23 +448,19 @@ void CGPathAddRect(CGMutablePathRef path, const CGAffineTransform* transform, CG @Notes Ignores Affine Transform */ void CGPathAddPath(CGMutablePathRef path, const CGAffineTransform* transform, CGPathRef toAdd) { - if (path == NULL || toAdd == NULL) { - return; - } + RETURN_IF(!path || !toAdd); // Close the path we're adding and open the path we need to add to. - toAdd->closePath(); - path->preparePathForEditing(); - FAIL_FAST_IF_FAILED(toAdd->_impl.pathGeometry->Stream(path->_impl.geometrySink.Get())); + toAdd->ClosePath(); + FAIL_FAST_IF_FAILED(path->PreparePathForEditing()); + FAIL_FAST_IF_FAILED(toAdd->GetPathGeometry()->Stream(path->GetGeometrySink().Get())); } /** @Status Interoperable */ void CGPathAddEllipseInRect(CGMutablePathRef path, const CGAffineTransform* transform, CGRect rect) { - if (path == NULL) { - return; - } + RETURN_IF(!path); // Determine the control point offset multiplier to create 4 arcs CGFloat offsetMultiplier = _CGPathControlPointOffsetMultiplier(M_PI_2); @@ -456,9 +492,11 @@ void CGPathAddEllipseInRect(CGMutablePathRef path, const CGAffineTransform* tran @Status Interoperable */ void CGPathCloseSubpath(CGMutablePathRef path) { + RETURN_IF(!path); + // Move the current point to the starting point since the line is closed. - path->_impl.currentPoint = path->_impl.startingPoint; - path->endFigure(D2D1_FIGURE_END_CLOSED); + path->SetCurrentPoint(path->GetStartingPoint()); + path->EndFigure(D2D1_FIGURE_END_CLOSED); } /** @@ -467,14 +505,16 @@ void CGPathCloseSubpath(CGMutablePathRef path) { */ CGRect CGPathGetBoundingBox(CGPathRef path) { if (path == NULL) { - return CGRectMake(INFINITY, INFINITY, 0, 0); + return CGRectNull; } D2D1_RECT_F bounds; - path->closePath(); + path->ClosePath(); - FAIL_FAST_IF_FAILED(path->_impl.pathGeometry->GetBounds(D2D1::IdentityMatrix(), &bounds)); + if (FAILED(path->GetPathGeometry()->GetBounds(D2D1::IdentityMatrix(), &bounds))) { + return CGRectNull; + } return _D2DRectToCGRect(bounds); } @@ -485,14 +525,14 @@ CGRect CGPathGetBoundingBox(CGPathRef path) { */ bool CGPathIsEmpty(CGPathRef path) { if (path == NULL) { - return kCFBooleanTrue; + return true; } UINT32 count; - path->closePath(); + path->ClosePath(); - path->_impl.pathGeometry->GetFigureCount(&count); + RETURN_FALSE_IF_FAILED(path->GetPathGeometry()->GetFigureCount(&count)); return count == 0; } @@ -500,10 +540,7 @@ bool CGPathIsEmpty(CGPathRef path) { @Status Interoperable */ void CGPathRelease(CGPathRef path) { - if (path == NULL) { - return; - } - + RETURN_IF(!path); CFRelease(path); } @@ -511,9 +548,7 @@ void CGPathRelease(CGPathRef path) { @Status Interoperable */ CGPathRef CGPathRetain(CGPathRef path) { - if (path == NULL) { - return NULL; - } + RETURN_NULL_IF(!path); CFRetain(path); @@ -626,29 +661,22 @@ void CGPathApply(CGPathRef path, void* info, CGPathApplierFunction function) { } /** - @Status Interoperable - @Notes + @Status Caveat + @Notes eoFill ignored. Default fill pattern for ID2D1 Geometry is used. */ bool CGPathContainsPoint(CGPathRef path, const CGAffineTransform* transform, CGPoint point, bool eoFill) { + RETURN_FALSE_IF(!path); + if (transform) { point = CGPointApplyAffineTransform(point, *transform); } - // check if the point is outside this box already, if it is, return false - CGRect boundingBox = CGPathGetBoundingBox(path); - if (!CGRectContainsPoint(boundingBox, point)) { - return false; - } - CGContextRef context = - CGBitmapContextCreate(0, boundingBox.origin.x + boundingBox.size.width, boundingBox.origin.y + boundingBox.size.height, 1, 1, 0, 0); + BOOL containsPoint = FALSE; - CGContextAddPath(context, path); + path->ClosePath(); + RETURN_FALSE_IF_FAILED(path->GetPathGeometry()->FillContainsPoint(_CGPointToD2D_F(point), D2D1::IdentityMatrix(), &containsPoint)); - bool inPath = CGContextIsPointInPath(context, eoFill, point.x, point.y); - - CGContextRelease(context); - - return inPath; + return (containsPoint ? true : false); } /** @@ -709,7 +737,10 @@ bool CGPathEqualToPath(CGPathRef path1, CGPathRef path2) { @Status Interoperable */ CGPoint CGPathGetCurrentPoint(CGPathRef path) { - return path->_impl.currentPoint; + if (!path) { + return CGPointZero; + } + return path->GetCurrentPoint(); } /** diff --git a/tests/unittests/CoreGraphics/CGPathTests.mm b/tests/unittests/CoreGraphics/CGPathTests.mm index 70e24dd346..5033dff5ee 100644 --- a/tests/unittests/CoreGraphics/CGPathTests.mm +++ b/tests/unittests/CoreGraphics/CGPathTests.mm @@ -87,7 +87,7 @@ void cgPathCompare(NSArray* expected, NSArray* result) { CGRect rect = CGRectMake(2, 4, 8, 16); - CGPathAddRect(path, NULL, rect); + CGPathAddRect(path, nullptr, rect); NSMutableArray* result = [NSMutableArray array]; @@ -116,7 +116,7 @@ void cgPathCompare(NSArray* expected, NSArray* result) { CGRect rect = CGRectMake(40, 40, 200, 40); - CGPathAddEllipseInRect(path, NULL, rect); + CGPathAddEllipseInRect(path, nullptr, rect); NSMutableArray* result = [NSMutableArray array]; @@ -145,7 +145,7 @@ void cgPathCompare(NSArray* expected, NSArray* result) { DISABLED_TEST(CGPath, CGPathApplyAddArc) { CGMutablePathRef path = CGPathCreateMutable(); - CGPathAddArc(path, NULL, 25, 100, 20, M_PI * 1.25, 0, 1); + CGPathAddArc(path, nullptr, 25, 100, 20, M_PI * 1.25, 0, 1); NSMutableArray* result = [NSMutableArray array]; @@ -174,8 +174,8 @@ void cgPathCompare(NSArray* expected, NSArray* result) { DISABLED_TEST(CGPath, CGPathApplyAddArcToPoint) { CGMutablePathRef path = CGPathCreateMutable(); - CGPathMoveToPoint(path, NULL, 400, 400); - CGPathAddArcToPoint(path, NULL, 140, 250, 110, 180, 50); + CGPathMoveToPoint(path, nullptr, 400, 400); + CGPathAddArcToPoint(path, nullptr, 140, 250, 110, 180, 50); NSMutableArray* result = [NSMutableArray array]; @@ -200,8 +200,8 @@ void cgPathCompare(NSArray* expected, NSArray* result) { DISABLED_TEST(CGPath, CGPathAddQuadCurveToPoint) { CGMutablePathRef path = CGPathCreateMutable(); - CGPathMoveToPoint(path, NULL, 400, 400); - CGPathAddQuadCurveToPoint(path, NULL, 140, 250, 110, 180); + CGPathMoveToPoint(path, nullptr, 400, 400); + CGPathAddQuadCurveToPoint(path, nullptr, 140, 250, 110, 180); NSMutableArray* result = [NSMutableArray array]; @@ -224,7 +224,7 @@ void cgPathCompare(NSArray* expected, NSArray* result) { CGRect rect = CGRectMake(2, 4, 8, 16); - CGPathAddEllipseInRect(path1, NULL, rect); + CGPathAddEllipseInRect(path1, nullptr, rect); CGMutablePathRef path2 = CGPathCreateMutableCopy(path1); @@ -233,7 +233,6 @@ void cgPathCompare(NSArray* expected, NSArray* result) { ASSERT_TRUE(CGPathEqualToPath(path1, path2)); CGPathRelease(path1); - CGPathRelease(path2); } @@ -242,13 +241,13 @@ void cgPathCompare(NSArray* expected, NSArray* result) { CGRect rect = CGRectMake(2, 4, 8, 16); - CGPathAddRect(path1, NULL, rect); + CGPathAddRect(path1, nullptr, rect); // Create a copy of the path CGMutablePathRef path2 = CGPathCreateMutableCopy(path1); // Change the copy - CGPathAddEllipseInRect(path2, NULL, rect); + CGPathAddEllipseInRect(path2, nullptr, rect); // Make sure the paths are not the same object ASSERT_NE(path1, path2); @@ -288,7 +287,7 @@ void cgPathCompare(NSArray* expected, NSArray* result) { for (int i = 0; i < 100; i++) { CGRect rect = CGRectMake(i, 4, 8, 16); - CGPathAddRect(path, NULL, rect); + CGPathAddRect(path, nullptr, rect); NSArray* expectedRect = @[ @{ kTypeKey : @(kCGPathElementMoveToPoint), @@ -319,15 +318,15 @@ void cgPathCompare(NSArray* expected, NSArray* result) { CGRect rect1 = CGRectMake(2, 4, 8, 16); - CGPathAddRect(path1, NULL, rect1); + CGPathAddRect(path1, nullptr, rect1); CGMutablePathRef path2 = CGPathCreateMutable(); CGRect rect2 = CGRectMake(4, 4, 8, 16); - CGPathAddRect(path2, NULL, rect2); + CGPathAddRect(path2, nullptr, rect2); - CGPathAddPath(path1, NULL, path2); + CGPathAddPath(path1, nullptr, path2); NSArray* expected1 = @[ @{ kTypeKey : @(kCGPathElementMoveToPoint), @@ -394,19 +393,19 @@ CGPathRef newPathForRoundRect(CGRect rect, CGFloat radius) { CGFloat outsideTop = rect.origin.y; CGFloat outsideLeft = rect.origin.x; - CGPathMoveToPoint(path, NULL, innerRect.origin.x, outsideTop); + CGPathMoveToPoint(path, nullptr, innerRect.origin.x, outsideTop); - CGPathAddLineToPoint(path, NULL, insideRight, outsideTop); - CGPathAddArcToPoint(path, NULL, outsideRight, outsideTop, outsideRight, insideTop, radius); + CGPathAddLineToPoint(path, nullptr, insideRight, outsideTop); + CGPathAddArcToPoint(path, nullptr, outsideRight, outsideTop, outsideRight, insideTop, radius); - CGPathAddLineToPoint(path, NULL, outsideRight, insideBottom); - CGPathAddArcToPoint(path, NULL, outsideRight, outsideBottom, insideRight, outsideBottom, radius); + CGPathAddLineToPoint(path, nullptr, outsideRight, insideBottom); + CGPathAddArcToPoint(path, nullptr, outsideRight, outsideBottom, insideRight, outsideBottom, radius); - CGPathAddLineToPoint(path, NULL, innerRect.origin.x, outsideBottom); - CGPathAddArcToPoint(path, NULL, outsideLeft, outsideBottom, outsideLeft, insideBottom, radius); + CGPathAddLineToPoint(path, nullptr, innerRect.origin.x, outsideBottom); + CGPathAddArcToPoint(path, nullptr, outsideLeft, outsideBottom, outsideLeft, insideBottom, radius); - CGPathAddLineToPoint(path, NULL, outsideLeft, insideTop); - CGPathAddArcToPoint(path, NULL, outsideLeft, outsideTop, innerRect.origin.x, outsideTop, radius); + CGPathAddLineToPoint(path, nullptr, outsideLeft, insideTop); + CGPathAddArcToPoint(path, nullptr, outsideLeft, outsideTop, innerRect.origin.x, outsideTop, radius); CGPathCloseSubpath(path); @@ -424,17 +423,17 @@ CGPathRef newPathForRoundRect(CGRect rect, CGFloat radius) { // test areas outside the rect CGPoint testPoint = CGPointMake(-1.0f, -1.0f); - bool test = CGPathContainsPoint(path, NULL, testPoint, YES); + bool test = CGPathContainsPoint(path, nullptr, testPoint, YES); EXPECT_FALSE(test); testPoint = CGPointMake(testRect.size.width + testRect.origin.x + 1, 0.f); - test = CGPathContainsPoint(path, NULL, testPoint, YES); + test = CGPathContainsPoint(path, nullptr, testPoint, YES); EXPECT_FALSE(test); testPoint = CGPointMake(0.0f, testRect.size.height + testRect.origin.y + 1.0f); - test = CGPathContainsPoint(path, NULL, testPoint, YES); + test = CGPathContainsPoint(path, nullptr, testPoint, YES); EXPECT_FALSE(test); } @@ -450,20 +449,21 @@ CGPathRef newPathForRoundRect(CGRect rect, CGFloat radius) { // test areas inside the rect but NOT in the path CGPoint testPoint = CGPointMake(1.0f, 1.0f); - bool test = CGPathContainsPoint(path, NULL, testPoint, YES); + bool test = CGPathContainsPoint(path, nullptr, testPoint, YES); EXPECT_FALSE(test); testPoint = CGPointMake(testRect.origin.x, 0.0f); - test = CGPathContainsPoint(path, NULL, testPoint, YES); + test = CGPathContainsPoint(path, nullptr, testPoint, YES); EXPECT_FALSE(test); testPoint = CGPointMake(0.0f, testRect.origin.y); - test = CGPathContainsPoint(path, NULL, testPoint, YES); + test = CGPathContainsPoint(path, nullptr, testPoint, YES); EXPECT_FALSE(test); } + DISABLED_TEST(CGPath, CGPathContainsPointShoulders) { CGFloat originX = 10.0f; CGFloat originY = 20.0f; @@ -475,28 +475,28 @@ CGPathRef newPathForRoundRect(CGRect rect, CGFloat radius) { // check the curve OUTSIDE the path's shoulders CGPoint testPoint = CGPointMake(testRect.origin.x + 1.0, testRect.origin.y + 1.0); - bool test = CGPathContainsPoint(path, NULL, testPoint, YES); + bool test = CGPathContainsPoint(path, nullptr, testPoint, YES); EXPECT_FALSE(test); // check the curve just INSIDE the path's shoulders testPoint = CGPointMake(testRect.origin.x + radius, testRect.origin.y + radius); - test = CGPathContainsPoint(path, NULL, testPoint, YES); + test = CGPathContainsPoint(path, nullptr, testPoint, YES); EXPECT_TRUE(test); testPoint = CGPointMake(testRect.origin.x + testRect.size.width - radius - radius, testRect.origin.y + testRect.size.height - 20.0); - test = CGPathContainsPoint(path, NULL, testPoint, YES); + test = CGPathContainsPoint(path, nullptr, testPoint, YES); EXPECT_TRUE(test); testPoint = CGPointMake(testRect.origin.x + radius, testRect.origin.y + testRect.size.height - radius - radius); - test = CGPathContainsPoint(path, NULL, testPoint, YES); + test = CGPathContainsPoint(path, nullptr, testPoint, YES); EXPECT_TRUE(test); testPoint = CGPointMake(testRect.origin.x + testRect.size.width - radius - radius, testRect.origin.y + radius); - test = CGPathContainsPoint(path, NULL, testPoint, YES); + test = CGPathContainsPoint(path, nullptr, testPoint, YES); EXPECT_TRUE(test); } @@ -533,44 +533,44 @@ CGPathRef newPathForRoundRect(CGRect rect, CGFloat radius) { // test areas inside the rect but NOT in the path CGPoint testPoint = CGPointMake(1.0f, 1.0f); - bool test = CGPathContainsPoint(path, NULL, testPoint, NO); + bool test = CGPathContainsPoint(path, nullptr, testPoint, NO); EXPECT_FALSE(test); testPoint = CGPointMake(testRect.origin.x, 0.0f); - test = CGPathContainsPoint(path, NULL, testPoint, NO); + test = CGPathContainsPoint(path, nullptr, testPoint, NO); EXPECT_FALSE(test); testPoint = CGPointMake(0.0f, testRect.origin.y); - test = CGPathContainsPoint(path, NULL, testPoint, NO); + test = CGPathContainsPoint(path, nullptr, testPoint, NO); EXPECT_FALSE(test); // check the curve OUTSIDE the path's shoulders testPoint = CGPointMake(testRect.origin.x + 1.0, testRect.origin.y + 1.0); - test = CGPathContainsPoint(path, NULL, testPoint, NO); + test = CGPathContainsPoint(path, nullptr, testPoint, NO); EXPECT_FALSE(test); // check the curve just INSIDE the path's shoulders testPoint = CGPointMake(testRect.origin.x + radius, testRect.origin.y + radius); - test = CGPathContainsPoint(path, NULL, testPoint, NO); + test = CGPathContainsPoint(path, nullptr, testPoint, NO); EXPECT_TRUE(test); testPoint = CGPointMake(testRect.origin.x + testRect.size.width - radius - radius, testRect.origin.y + testRect.size.height - 20.0); - test = CGPathContainsPoint(path, NULL, testPoint, NO); + test = CGPathContainsPoint(path, nullptr, testPoint, NO); EXPECT_TRUE(test); testPoint = CGPointMake(testRect.origin.x + radius, testRect.origin.y + testRect.size.height - radius - radius); - test = CGPathContainsPoint(path, NULL, testPoint, NO); + test = CGPathContainsPoint(path, nullptr, testPoint, NO); EXPECT_TRUE(test); testPoint = CGPointMake(testRect.origin.x + testRect.size.width - radius - radius, testRect.origin.y + radius); - test = CGPathContainsPoint(path, NULL, testPoint, NO); + test = CGPathContainsPoint(path, nullptr, testPoint, NO); EXPECT_TRUE(test); @@ -603,11 +603,11 @@ void EXPECT_SIZEEQ(CGSize pathSize, CGFloat width, CGFloat height) { EXPECT_POINTEQ(CGPathGetCurrentPoint(path), 0, 0); // Move to a new point - CGPathMoveToPoint(path, NULL, 50, 50); + CGPathMoveToPoint(path, nullptr, 50, 50); EXPECT_POINTEQ(CGPathGetCurrentPoint(path), 50, 50); // Move to another new point - CGPathMoveToPoint(path, NULL, 100, 50); + CGPathMoveToPoint(path, nullptr, 100, 50); EXPECT_POINTEQ(CGPathGetCurrentPoint(path), 100, 50); // Create a copy of this path which should be at the same point @@ -615,7 +615,7 @@ void EXPECT_SIZEEQ(CGSize pathSize, CGFloat width, CGFloat height) { EXPECT_POINTEQ(CGPathGetCurrentPoint(pathCopy), 100, 50); // Move the new path to a new point - CGPathMoveToPoint(pathCopy, NULL, 200, 200); + CGPathMoveToPoint(pathCopy, nullptr, 200, 200); // The original should not have been changed but the new path should have moved EXPECT_POINTEQ(CGPathGetCurrentPoint(path), 100, 50); @@ -628,8 +628,8 @@ void EXPECT_SIZEEQ(CGSize pathSize, CGFloat width, CGFloat height) { TEST(CGPath, CGPathSimpleLines) { // Create a new path at a particular point and draw a line CGMutablePathRef path = CGPathCreateMutable(); - CGPathMoveToPoint(path, NULL, 50, 50); - CGPathAddLineToPoint(path, NULL, 25, 25); + CGPathMoveToPoint(path, nullptr, 50, 50); + CGPathAddLineToPoint(path, nullptr, 25, 25); EXPECT_POINTEQ(CGPathGetCurrentPoint(path), 25, 25); // Get the size of its bounding box @@ -638,7 +638,7 @@ void EXPECT_SIZEEQ(CGSize pathSize, CGFloat width, CGFloat height) { EXPECT_SIZEEQ(boundingBox.size, 25, 25); // Add a line further down to increase the bounding box's size - CGPathAddLineToPoint(path, NULL, 100, 200); + CGPathAddLineToPoint(path, nullptr, 100, 200); boundingBox = CGPathGetBoundingBox(path); @@ -649,8 +649,8 @@ void EXPECT_SIZEEQ(CGSize pathSize, CGFloat width, CGFloat height) { // Create a new path and move it to a new point path = CGPathCreateMutable(); - CGPathMoveToPoint(path, NULL, 50, 50); - CGPathAddLineToPoint(path, NULL, 81, 107); + CGPathMoveToPoint(path, nullptr, 50, 50); + CGPathAddLineToPoint(path, nullptr, 81, 107); // Get the bounding box size of that path boundingBox = CGPathGetBoundingBox(path); @@ -659,7 +659,7 @@ void EXPECT_SIZEEQ(CGSize pathSize, CGFloat width, CGFloat height) { // Create a copy of this path CGMutablePathRef pathCopy = CGPathCreateMutableCopy(path); - CGPathAddLineToPoint(pathCopy, NULL, 200, 200); + CGPathAddLineToPoint(pathCopy, nullptr, 200, 200); // Check that original bounding box has not changed. boundingBox = CGPathGetBoundingBox(path); @@ -681,12 +681,12 @@ void EXPECT_SIZEEQ(CGSize pathSize, CGFloat width, CGFloat height) { TEST(CGPath, CGPathAddPathTest) { CGMutablePathRef path = CGPathCreateMutable(); - CGPathMoveToPoint(path, NULL, 50, 50); - CGPathAddLineToPoint(path, NULL, 75, 75); + CGPathMoveToPoint(path, nullptr, 50, 50); + CGPathAddLineToPoint(path, nullptr, 75, 75); CGMutablePathRef secondPath = CGPathCreateMutable(); - CGPathMoveToPoint(secondPath, NULL, 75, 75); - CGPathAddLineToPoint(secondPath, NULL, 100, 100); + CGPathMoveToPoint(secondPath, nullptr, 75, 75); + CGPathAddLineToPoint(secondPath, nullptr, 100, 100); CGRect boundingBox = CGPathGetBoundingBox(path); EXPECT_POINTEQ(boundingBox.origin, 50, 50); @@ -703,4 +703,220 @@ void EXPECT_SIZEEQ(CGSize pathSize, CGFloat width, CGFloat height) { CGPathRelease(path); CGPathRelease(secondPath); +} + +TEST(CGPath, CGPathRectanglesTest) { + CGRect theRectangle = CGRectMake(50, 50, 100, 100); + CGMutablePathRef path = CGPathCreateWithRect(theRectangle, nullptr); + CGRect boundingBox = CGPathGetBoundingBox(path); + + EXPECT_POINTEQ(boundingBox.origin, theRectangle.origin.x, theRectangle.origin.y); + EXPECT_SIZEEQ(boundingBox.size, theRectangle.size.height, theRectangle.size.width); + + CGPathRelease(path); + path = CGPathCreateMutable(); + + CGPathMoveToPoint(path, nullptr, 50, 50); + CGPathAddLineToPoint(path, nullptr, 100, 100); + CGPathAddRect(path, nullptr, CGRectMake(100, 100, 200, 100)); + + boundingBox = CGPathGetBoundingBox(path); + + EXPECT_POINTEQ(boundingBox.origin, 50, 50); + EXPECT_SIZEEQ(boundingBox.size, 250, 150); + + CGPathRelease(path); +} + +TEST(CGPath, CGPathAddLinesTest) { + CGMutablePathRef path = CGPathCreateMutable(); + + CGPoint points[] = { { 50, 50 }, { 100, 50 }, { 50, 50 }, { 50, 100 } }; + + CGPathMoveToPoint(path, nullptr, 50, 50); + + CGPathAddLines(path, nullptr, points, 4); + + CGRect boundingBox = CGPathGetBoundingBox(path); + + EXPECT_POINTEQ(boundingBox.origin, 50, 50); + EXPECT_SIZEEQ(boundingBox.size, 50, 50); + + CGPoint newPoints[] = { { 100, 100 }, { 200, 25 }, { 100, 100 }, { 25, 200 } }; + + CGPathAddLines(path, nullptr, newPoints, 4); + + boundingBox = CGPathGetBoundingBox(path); + + EXPECT_POINTEQ(boundingBox.origin, 25, 25); + EXPECT_SIZEEQ(boundingBox.size, 175, 175); + + CGPathRelease(path); +} + +TEST(CGPath, CGPathContainsPointTest) { + CGRect theRectangle = CGRectMake(50, 50, 100, 100); + CGMutablePathRef path = CGPathCreateWithRect(theRectangle, nullptr); + + EXPECT_TRUE(CGPathContainsPoint(path, nullptr, CGPointMake(75, 75), NO)); + EXPECT_FALSE(CGPathContainsPoint(path, nullptr, CGPointMake(200, 200), NO)); + + CGPathRelease(path); +} + +static bool testSymmetricEquivalence(CGPathRef path1, CGPathRef path2) { + return (CGPathEqualToPath(path1, path2) && CGPathEqualToPath(path2, path1)); +} + +TEST(CGPath, CGPathEqualsTest) { + CGMutablePathRef path1 = CGPathCreateMutable(); + CGMutablePathRef path2 = CGPathCreateMutable(); + + CGPathMoveToPoint(path1, nullptr, 50, 50); + CGPathAddLineToPoint(path1, nullptr, 75, 75); + + CGPathMoveToPoint(path2, nullptr, 50, 50); + CGPathAddLineToPoint(path2, nullptr, 75, 75); + + EXPECT_TRUE(testSymmetricEquivalence(path1, path2)); + + CGPathAddLineToPoint(path2, nullptr, 100, 100); + + EXPECT_FALSE(testSymmetricEquivalence(path1, path2)); + + CGPathRelease(path1); + CGPathRelease(path2); + + path1 = CGPathCreateMutable(); + path2 = CGPathCreateMutable(); + + CGPathMoveToPoint(path1, nullptr, 50, 50); + CGPathAddLineToPoint(path1, nullptr, 100, 100); + + // Two segments should be the same as one longer segment. + CGPathMoveToPoint(path2, nullptr, 50, 50); + CGPathAddLineToPoint(path2, nullptr, 75, 75); + CGPathAddLineToPoint(path2, nullptr, 100, 100); + + EXPECT_TRUE(testSymmetricEquivalence(path1, path2)); + + // Rectangles + CGRect theRectangle = CGRectMake(50, 50, 100, 100); + CGMutablePathRef rectPath1 = CGPathCreateWithRect(theRectangle, nullptr); + CGMutablePathRef rectPath2 = CGPathCreateWithRect(theRectangle, nullptr); + + EXPECT_TRUE(testSymmetricEquivalence(rectPath1, rectPath2)); + + // Add an extra segment coming off of the rectangle. + CGPathAddLineToPoint(rectPath2, nullptr, 200, 200); + + EXPECT_FALSE(testSymmetricEquivalence(rectPath1, rectPath2)); + + // A small line contained entirely within rect1. + CGMutablePathRef containedPath = CGPathCreateMutable(); + CGPathMoveToPoint(path1, nullptr, 75, 75); + CGPathAddLineToPoint(path1, nullptr, 80, 80); + + EXPECT_FALSE(testSymmetricEquivalence(rectPath1, containedPath)); + + CGMutablePathRef intersectPath = CGPathCreateMutable(); + CGPathMoveToPoint(intersectPath, nullptr, 75, 75); + CGPathAddLineToPoint(intersectPath, nullptr, 250, 250); + + EXPECT_FALSE(testSymmetricEquivalence(rectPath1, intersectPath)); + + CGMutablePathRef removedPath = CGPathCreateMutable(); + CGPathMoveToPoint(removedPath, nullptr, 500, 500); + CGPathAddLineToPoint(removedPath, nullptr, 550, 550); + + EXPECT_FALSE(testSymmetricEquivalence(rectPath1, removedPath)); + + CGRect overLappingRectangle = CGRectMake(75, 75, 100, 100); + CGMutablePathRef strictlyOverlappingRectangle = CGPathCreateWithRect(overLappingRectangle, nullptr); + + EXPECT_FALSE(testSymmetricEquivalence(rectPath1, strictlyOverlappingRectangle)); + + CGRect smallRectangle = CGRectMake(75, 75, 5, 5); + CGMutablePathRef containedRectangle = CGPathCreateWithRect(smallRectangle, nullptr); + + EXPECT_FALSE(testSymmetricEquivalence(rectPath1, containedRectangle)); + + CGPathRelease(rectPath1); + CGPathRelease(rectPath2); + + // Two complicated paths that should be equal. + theRectangle = CGRectMake(50, 50, 100, 100); + rectPath1 = CGPathCreateWithRect(theRectangle, nullptr); + rectPath2 = CGPathCreateWithRect(theRectangle, nullptr); + + CGPathAddLineToPoint(rectPath1, nullptr, 200, 200); + + // The multiple line segments should not affect equality since it's still a straight line to the same point. + CGPathAddLineToPoint(rectPath2, nullptr, 97, 97); + CGPathAddLineToPoint(rectPath2, nullptr, 124, 124); + CGPathAddLineToPoint(rectPath2, nullptr, 139, 139); + CGPathAddLineToPoint(rectPath2, nullptr, 187, 187); + CGPathAddLineToPoint(rectPath2, nullptr, 200, 200); + + // Direction shoudl not matter either + CGPathAddLineToPoint(rectPath1, nullptr, 230, 200); + CGPathAddLineToPoint(rectPath1, nullptr, 230, 230); + CGPathAddLineToPoint(rectPath1, nullptr, 200, 200); + + CGPathAddLineToPoint(rectPath2, nullptr, 230, 230); + CGPathAddLineToPoint(rectPath2, nullptr, 230, 200); + CGPathAddLineToPoint(rectPath2, nullptr, 200, 200); + + EXPECT_TRUE(testSymmetricEquivalence(rectPath1, rectPath2)); + + CGPathRelease(path1); + CGPathRelease(path2); + CGPathRelease(rectPath1); + CGPathRelease(rectPath2); + CGPathRelease(containedPath); + CGPathRelease(intersectPath); + CGPathRelease(removedPath); + CGPathRelease(strictlyOverlappingRectangle); + CGPathRelease(containedRectangle); +} + +TEST(CGPath, SubShapesEqualityTest) { + CGMutablePathRef path1 = CGPathCreateMutable(); + CGMutablePathRef path2 = CGPathCreateMutable(); + + CGPathMoveToPoint(path1, nullptr, 50, 50); + CGPathAddLineToPoint(path1, nullptr, 100, 50); + CGPathAddLineToPoint(path1, nullptr, 100, 100); + CGPathAddLineToPoint(path1, nullptr, 50, 100); + CGPathAddLineToPoint(path1, nullptr, 75, 75); + CGPathAddLineToPoint(path1, nullptr, 100, 100); + CGPathAddLineToPoint(path1, nullptr, 50, 100); + CGPathAddLineToPoint(path1, nullptr, 50, 50); + CGPathCloseSubpath(path1); + + CGPathMoveToPoint(path2, nullptr, 50, 50); + CGPathAddLineToPoint(path2, nullptr, 100, 50); + CGPathAddLineToPoint(path2, nullptr, 100, 100); + CGPathAddLineToPoint(path2, nullptr, 50, 100); + CGPathAddLineToPoint(path2, nullptr, 75, 75); + CGPathAddLineToPoint(path2, nullptr, 100, 100); + CGPathAddLineToPoint(path2, nullptr, 50, 100); + CGPathAddLineToPoint(path2, nullptr, 50, 50); + CGPathCloseSubpath(path2); + + EXPECT_TRUE(testSymmetricEquivalence(path1, path2)); + + CGMutablePathRef path3 = CGPathCreateMutable(); + + CGPathMoveToPoint(path3, nullptr, 50, 50); + CGPathAddLineToPoint(path3, nullptr, 100, 50); + CGPathAddLineToPoint(path3, nullptr, 100, 100); + CGPathAddLineToPoint(path3, nullptr, 50, 100); + CGPathCloseSubpath(path3); + + EXPECT_FALSE(testSymmetricEquivalence(path1, path3)); + + CGPathRelease(path1); + CGPathRelease(path2); + CGPathRelease(path3); } \ No newline at end of file