Index: ui/gfx/path_mac_unittest.mm |
diff --git a/ui/gfx/path_mac_unittest.mm b/ui/gfx/path_mac_unittest.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d1d4a99234e38574b21376612bb89fb843b8a890 |
--- /dev/null |
+++ b/ui/gfx/path_mac_unittest.mm |
@@ -0,0 +1,258 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#import "ui/gfx/path_mac.h" |
+ |
+#include <cmath> |
+#include <vector> |
+ |
+#import <Cocoa/Cocoa.h> |
+ |
+#include "testing/gtest/include/gtest/gtest.h" |
+#include "third_party/skia/include/core/SkRegion.h" |
+#include "ui/gfx/geometry/rect.h" |
+#include "ui/gfx/geometry/rect_conversions.h" |
+#include "ui/gfx/path.h" |
+ |
+namespace gfx { |
+ |
+namespace { |
+ |
+// Returns the point at a distance of |radius| from the point (|centre_x|, |
+// |centre_y|), and angle |degrees| from the positive horizontal axis, measured |
+// anti-clockwise. |
+NSPoint GetRadialPoint(double radius, |
+ double degrees, |
+ double centre_x, |
+ double centre_y) { |
+ const double radian = (degrees * SK_ScalarPI) / 180; |
+ return NSMakePoint(centre_x + radius * std::cos(radian), |
+ centre_y + radius * std::sin(radian)); |
+} |
+ |
+// Returns the area of a circle with the given |radius|. |
+double CalculateCircleArea(double radius) { |
+ return SK_ScalarPI * radius * radius; |
+} |
+ |
+// Returns the area of a simple polygon. |path| should represent a simple |
+// polygon. |
+double CalculatePolygonArea(NSBezierPath* path) { |
+ // If path represents a single polygon, it will have MoveTo, followed by |
+ // multiple LineTo, followed By ClosePath, followed by another MoveTo |
+ // NSBezierPathElement. |
+ const size_t element_count = [path elementCount]; |
+ NSPoint points[3]; |
+ std::vector<NSPoint> poly; |
+ |
+ for (size_t i = 0; i < element_count - 1; i++) { |
+ NSBezierPathElement element = |
+ [path elementAtIndex:i associatedPoints:points]; |
+ poly.push_back(points[0]); |
+ DCHECK_EQ(element, |
+ i ? (i == element_count - 2 ? NSClosePathBezierPathElement |
+ : NSLineToBezierPathElement) |
+ : NSMoveToBezierPathElement); |
+ } |
+ DCHECK_EQ([path elementAtIndex:element_count - 1], NSMoveToBezierPathElement); |
+ |
+ // Shoelace Algorithm to find the area of a simple polygon. |
+ DCHECK(NSEqualPoints(poly.front(), poly.back())); |
+ double area = 0; |
+ for (size_t i = 0; i < poly.size() - 1; i++) |
+ area += poly[i].x * poly[i + 1].y - poly[i].y * poly[i + 1].x; |
+ |
+ return std::fabs(area) / 2.0; |
+} |
+ |
+// Returns the area of a rounded rectangle with the given |width|, |height| and |
+// |radius|. |
+double CalculateRoundedRectangleArea(double width, |
+ double height, |
+ double radius) { |
+ const double inside_width = width - 2 * radius; |
+ const double inside_height = height - 2 * radius; |
+ return inside_width * inside_height + |
+ 2 * radius * (inside_width + inside_height) + |
+ CalculateCircleArea(radius); |
+} |
+ |
+// Returns the bounding box of |path| as a Rect. |
+Rect GetBoundingBox(NSBezierPath* path) { |
+ const NSRect bounds = [path bounds]; |
+ return ToNearestRect(RectF(bounds.origin.x, bounds.origin.y, |
+ bounds.size.width, bounds.size.height)); |
+} |
+ |
+} // namespace |
+ |
+// Check that empty NSBezierPath is returned for empty SkPath. |
+TEST(CreateNSBezierPathFromSkPathTest, EmptyPath) { |
+ NSBezierPath* result = CreateNSBezierPathFromSkPath(SkPath()); |
+ EXPECT_TRUE([result isEmpty]); |
+} |
+ |
+// Check that the returned NSBezierPath has the correct winding rule. |
+TEST(CreateNSBezierPathFromSkPathTest, FillType) { |
+ SkPath path; |
+ path.setFillType(SkPath::kWinding_FillType); |
+ NSBezierPath* result = CreateNSBezierPathFromSkPath(path); |
+ EXPECT_EQ(NSNonZeroWindingRule, [result windingRule]); |
+ |
+ path.setFillType(SkPath::kEvenOdd_FillType); |
+ result = CreateNSBezierPathFromSkPath(path); |
+ EXPECT_EQ(NSEvenOddWindingRule, [result windingRule]); |
+} |
+ |
+// Check that a path containing multiple subpaths, in this case two rectangles, |
+// is correctly converted to a NSBezierPath. |
+TEST(CreateNSBezierPathFromSkPathTest, TwoRectanglesPath) { |
+ const SkRect rects[] = { |
+ {0, 0, 50, 50}, {100, 100, 150, 150}, |
+ }; |
+ const NSPoint inside_points[] = { |
+ {1, 1}, {1, 49}, {49, 49}, {49, 1}, {25, 25}, |
+ {101, 101}, {101, 149}, {149, 149}, {149, 101}, {125, 125}}; |
+ const NSPoint outside_points[] = {{-1, -1}, {-1, 51}, {51, 51}, {51, -1}, |
+ {99, 99}, {99, 151}, {151, 151}, {151, 99}, |
+ {75, 75}, {-5, -5}}; |
+ ASSERT_EQ(arraysize(inside_points), arraysize(outside_points)); |
+ const Rect expected_bounds(0, 0, 150, 150); |
+ |
+ SkPath path; |
+ path.addRect(rects[0]); |
+ path.addRect(rects[1]); |
+ NSBezierPath* result = CreateNSBezierPathFromSkPath(path); |
+ |
+ // Check points near the boundary of the path and verify that they are |
+ // reported correctly as being inside/outside the path. |
+ for (size_t i = 0; i < arraysize(inside_points); i++) { |
+ EXPECT_TRUE([result containsPoint:inside_points[i]]); |
+ EXPECT_FALSE([result containsPoint:outside_points[i]]); |
+ } |
+ |
+ // Check that the returned result has the correct bounding box. GetBoundingBox |
+ // rounds the coordinates to nearest integer values. |
+ EXPECT_EQ(expected_bounds, GetBoundingBox(result)); |
+} |
+ |
+// Test that an SKPath containing a circle is converted correctly to a |
+// NSBezierPath. |
+TEST(CreateNSBezierPathFromSkPathTest, CirclePath) { |
+ const int kRadius = 5; |
+ const int kCentreX = 10; |
+ const int kCentreY = 15; |
+ const double kCushion = 0.1; |
+ // Expected bounding box of the circle. |
+ const Rect expected_bounds(kCentreX - kRadius, kCentreY - kRadius, |
+ 2 * kRadius, 2 * kRadius); |
+ |
+ SkPath path; |
+ path.addCircle(SkIntToScalar(kCentreX), SkIntToScalar(kCentreY), |
+ SkIntToScalar(kRadius)); |
+ NSBezierPath* result = CreateNSBezierPathFromSkPath(path); |
+ |
+ // Check points near the boundary of the circle and verify that they are |
+ // reported correctly as being inside/outside the path. |
+ for (size_t deg = 0; deg < 360; deg++) { |
+ NSPoint inside_point = |
+ GetRadialPoint(kRadius - kCushion, deg, kCentreX, kCentreY); |
+ NSPoint outside_point = |
+ GetRadialPoint(kRadius + kCushion, deg, kCentreX, kCentreY); |
+ EXPECT_TRUE([result containsPoint:inside_point]); |
+ EXPECT_FALSE([result containsPoint:outside_point]); |
+ } |
+ |
+ // Check that the returned result has the correct bounding box. GetBoundingBox |
+ // rounds the coordinates to nearest integer values. |
+ EXPECT_EQ(expected_bounds, GetBoundingBox(result)); |
+ |
+ // Check area of converted path is correct up to a certain tolerance value. To |
+ // find the area of the NSBezierPath returned, flatten it i.e. convert it to a |
+ // polygon. |
+ [NSBezierPath setDefaultFlatness:0.01]; |
+ NSBezierPath* polygon = [result bezierPathByFlatteningPath]; |
+ const double kTolerance = 0.14; |
+ EXPECT_NEAR(CalculateCircleArea(kRadius), CalculatePolygonArea(polygon), |
+ kTolerance); |
+} |
+ |
+// Test that an SKPath containing a rounded rectangle is converted correctly to |
+// a NSBezierPath. |
+TEST(CreateNSBezierPathFromSkPathTest, RoundedRectanglePath) { |
+ const int kRectangleWidth = 50; |
+ const int kRectangleHeight = 100; |
+ const int kCornerRadius = 5; |
+ const double kCushion = 0.1; |
+ // Expected bounding box of the rounded rectangle. |
+ const Rect expected_bounds(kRectangleWidth, kRectangleHeight); |
+ |
+ SkRRect rrect; |
+ rrect.setRectXY(SkRect::MakeWH(kRectangleWidth, kRectangleHeight), |
+ kCornerRadius, kCornerRadius); |
+ |
+ const NSPoint inside_points[] = { |
+ // Bottom left corner. |
+ {kCornerRadius / 2.0, kCornerRadius / 2.0}, |
+ // Bottom right corner. |
+ {kRectangleWidth - kCornerRadius / 2.0, kCornerRadius / 2.0}, |
+ // Top Right corner. |
+ {kRectangleWidth - kCornerRadius / 2.0, |
+ kRectangleHeight - kCornerRadius / 2.0}, |
+ // Top left corner. |
+ {kCornerRadius / 2.0, kRectangleHeight - kCornerRadius / 2.0}, |
+ // Bottom middle. |
+ {kRectangleWidth / 2.0, kCushion}, |
+ // Right middle. |
+ {kRectangleWidth - kCushion, kRectangleHeight / 2.0}, |
+ // Top middle. |
+ {kRectangleWidth / 2.0, kRectangleHeight - kCushion}, |
+ // Left middle. |
+ {kCushion, kRectangleHeight / 2.0}}; |
+ const NSPoint outside_points[] = { |
+ // Bottom left corner. |
+ {0, 0}, |
+ // Bottom right corner. |
+ {kRectangleWidth, 0}, |
+ // Top right corner. |
+ {kRectangleWidth, kRectangleHeight}, |
+ // Top left corner. |
+ {0, kRectangleHeight}, |
+ // Bottom middle. |
+ {kRectangleWidth / 2.0, -kCushion}, |
+ // Right middle. |
+ {kRectangleWidth + kCushion, kRectangleHeight / 2.0}, |
+ // Top middle. |
+ {kRectangleWidth / 2.0, kRectangleHeight + kCushion}, |
+ // Left middle. |
+ {-kCushion, kRectangleHeight / 2.0}}; |
+ ASSERT_EQ(arraysize(inside_points), arraysize(outside_points)); |
+ |
+ SkPath path; |
+ path.addRRect(rrect); |
+ NSBezierPath* result = CreateNSBezierPathFromSkPath(path); |
+ |
+ // Check points near the boundary of the path and verify that they are |
+ // reported correctly as being inside/outside the path. |
+ for (size_t i = 0; i < arraysize(inside_points); i++) { |
+ EXPECT_TRUE([result containsPoint:inside_points[i]]); |
+ EXPECT_FALSE([result containsPoint:outside_points[i]]); |
+ } |
+ |
+ // Check that the returned result has the correct bounding box. GetBoundingBox |
+ // rounds the coordinates to nearest integer values. |
+ EXPECT_EQ(expected_bounds, GetBoundingBox(result)); |
+ |
+ // Check area of converted path is correct up to a certain tolerance value. To |
+ // find the area of the NSBezierPath returned, flatten it i.e. convert it to a |
+ // polygon. |
+ [NSBezierPath setDefaultFlatness:0.01]; |
+ NSBezierPath* polygon = [result bezierPathByFlatteningPath]; |
+ const double kTolerance = 0.14; |
+ EXPECT_NEAR(CalculateRoundedRectangleArea(kRectangleWidth, kRectangleHeight, |
+ kCornerRadius), |
+ CalculatePolygonArea(polygon), kTolerance); |
+} |
+ |
+} // namespace gfx |