Last active
August 29, 2015 14:21
-
-
Save griotspeak/1d865a635ea17bd9916e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// NUIBezierPath.h | |
// NUIKit | |
// | |
// Created by Robert Widmann on 10/11/13. | |
// Copyright (c) 2013 CodaFi. All rights reserved. | |
// Released under the MIT license. | |
// | |
#import <Foundation/Foundation.h> | |
#import <CoreGraphics/CoreGraphics.h> | |
#import <NUIKit/NUIKitDefines.h> | |
/// The corners of a rectangle. | |
typedef NS_OPTIONS(NSUInteger, NUIRectCorner) { | |
/// The top-left corner of the rectangle. | |
NUIRectCornerTopLeft = 1 << 0, | |
/// The top-right corner of the rectangle. | |
NUIRectCornerTopRight = 1 << 1, | |
/// The bottom-left corner of the rectangle. | |
NUIRectCornerBottomLeft = 1 << 2, | |
/// The bottom-right corner of the rectangle. | |
NUIRectCornerBottomRight = 1 << 3, | |
/// All corners of the rectangle. | |
NUIRectCornerAllCorners = NUIRectCornerTopLeft | NUIRectCornerTopRight | NUIRectCornerBottomLeft | NUIRectCornerBottomRight, | |
}; | |
/// The NUIBezierPath class lets you define a path consisting of straight and curved line segments | |
/// and render that path in your custom views. You use this class initially to specify just the | |
/// geometry for your path. Paths can define simple shapes such as rectangles, ovals, and arcs or | |
/// they can define complex polygons that incorporate a mixture of straight and curved line | |
/// segments. After defining the shape, you can use additional methods of this class to render the | |
/// path in the current drawing context. | |
/// | |
/// A NUIBezierPath object combines the geometry of a path with attributes that describe the path | |
/// during rendering. You set the geometry and attributes separately and can change them independent | |
/// of one another. Once you have the object configured the way you want it, you can tell it to draw | |
/// itself in the current context. Because the creation, configuration, and rendering process are | |
/// all distinct steps, Bezier path objects can be reused easily in your code. You can even use the | |
/// same object to render the same shape multiple times, perhaps changing the rendering options | |
/// between successive drawing calls. | |
/// | |
/// You set the geometry of a path by manipulating the path’s current point. When you create a new | |
/// empty path object, the current point is undefined and must be set explicitly. To move the | |
/// current point without drawing a segment, you use the moveToPoint: method. All other methods | |
/// result in the addition of either a line or curve segments to the path. The methods for adding | |
/// new segments always assume you are starting at the current point and ending at some new point | |
/// that you specify. After adding the segment, the end point of the new segment automatically | |
/// becomes the current point. | |
/// | |
/// A single Bezier path object can contain any number of open or closed subpaths, where each | |
/// subpath represents a connected series of path segments. Calling the closePath method closes a | |
/// subpath by adding a straight line segment from the current point to the first point in the | |
/// subpath. Calling the moveToPoint: method ends the current subpath (without closing it) and sets | |
/// the starting point of the next subpath. The subpaths of a Bezier path object share the same | |
/// drawing attributes and must be manipulated as a group. To draw subpaths with different | |
/// attributes, you must put each subpath in its own NUIBezierPath object. | |
/// | |
/// After configuring the geometry and attributes of a Bezier path, you draw the path in the current | |
/// graphics context using the stroke and fill methods. The stroke method traces the outline of the | |
/// path using the current stroke color and the attributes of the Bezier path object. Similarly, | |
/// the fill method fills in the area enclosed by the path using the current fill color. | |
/// (You set the stroke and fill color using the UIColor class.) | |
/// | |
/// In addition to using a Bezier path object to draw shapes, you can also use it to define a new | |
/// clipping region. The addClip method intersects the shape represented by the path object with the | |
/// current clipping region of the graphics context. During subsequent drawing, only content that | |
/// lies within the new intersection region is actually rendered to the graphics context. | |
@interface NUIBezierPath : NSObject <NSCopying, NSCoding, NUIQuickLooking> { | |
@private | |
CGMutablePathRef _path; | |
CGFloat *_lineDashPattern; | |
NSUInteger _lineDashPatternCount; | |
CGFloat _lineWidth, _miterLimit, _flatness, _lineDashPhase; | |
CGLineCap _lineCapStyle; | |
CGLineJoin _lineJoinStyle; | |
CGPathRef _immutablePath; | |
BOOL _immutablePathIsValid; | |
} | |
#pragma mark - Convenience Initializers | |
/// Creates and returns a new NUIBezierPath object. | |
+ (NUIBezierPath *)bezierPath; | |
/// Creates and returns a new NUIBezierPath object initialized with a rectangular path. | |
+ (NUIBezierPath *)bezierPathWithRect:(CGRect)rect; | |
/// Creates and returns a new NUIBezierPath object initialized with an oval path inscribed in the | |
/// specified rectangle | |
+ (NUIBezierPath *)bezierPathWithOvalInRect:(CGRect)rect; | |
/// Creates and returns a new NUIBezierPath object initialized with a rounded rectangular path. | |
+ (NUIBezierPath *)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius; | |
/// Creates and returns a new NUIBezierPath object initialized with a rounded rectangular path. | |
+ (NUIBezierPath *)bezierPathWithRoundedRect:(CGRect)rect byRoundingCorners:(NUIRectCorner)corners cornerRadii:(CGSize)cornerRadii; | |
/// Creates and returns a new NUIBezierPath object initialized with an arc of a circle. | |
+ (NUIBezierPath *)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise; | |
/// Creates and returns a new NUIBezierPath object initialized with the contents of a Core Graphics | |
/// path. | |
+ (NUIBezierPath *)bezierPathWithCGPath:(CGPathRef)CGPath; | |
#pragma mark - Path Construction | |
/// Moves the receiver’s current point to the specified location. | |
- (void)moveToPoint:(CGPoint)point; | |
/// Appends a straight line to the receiver’s path. | |
- (void)addLineToPoint:(CGPoint)point; | |
/// Appends a cubic Bézier curve to the receiver’s path. | |
- (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2; | |
/// Appends a quadratic Bézier curve to the receiver’s path. | |
- (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint; | |
/// Appends an arc to the receiver’s path. | |
- (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise; | |
/// Closes the most recently added subpath. | |
- (void)closePath; | |
/// Removes all points from the receiver, effectively deleting all subpaths. | |
- (void)removeAllPoints; | |
/// Appends the contents of the specified path object to the receiver’s path. | |
- (void)appendPath:(NUIBezierPath *)bezierPath; | |
/// Creates and returns a new Bézier path object with the reversed contents of the current path. | |
- (NUIBezierPath *)bezierPathByReversingPath; | |
/// Transforms all points in the path using the specified affine transform matrix. | |
- (void)applyTransform:(CGAffineTransform)transform; | |
#pragma mark - Bézier Path Information | |
/// The Core Graphics representation of the path. | |
@property (nonatomic) CGPathRef CGPath; | |
- (CGPathRef)CGPath NS_RETURNS_INNER_POINTER; | |
/// Indicates whether the reciever contains any valid elements. | |
@property (readonly, getter=isEmpty) BOOL empty; | |
/// The bounding rectangle of the path. | |
@property (nonatomic, readonly) CGRect bounds; | |
/// The current point in the graphics path. | |
@property (nonatomic, readonly) CGPoint currentPoint; | |
/// Returns a Boolean value indicating whether the area enclosed by the receiver contains the | |
/// specified point. | |
- (BOOL)containsPoint:(CGPoint)point; | |
#pragma mark - Drawing Properties | |
/// The line width of the path. | |
/// | |
/// Line width specifies the thickness of the reciever's path with a value of 0.0 representing the | |
/// thinnest possible path the device is capable of drawing. Defaults to 1.0. | |
@property (nonatomic) CGFloat lineWidth; | |
/// The shape of the paths end points when stroked. | |
@property (nonatomic) CGLineCap lineCapStyle; | |
/// The shape of the joints between connected segments of a stroked path. | |
@property (nonatomic) CGLineJoin lineJoinStyle; | |
/// The limiting value that helps avoid spikes at junctions between connected line segments. | |
/// | |
/// Used when lineJoinStyle is kCGLineJoinMiter. | |
@property (nonatomic) CGFloat miterLimit; | |
/// The factor that determines the rendering accuracy for curved path segments. | |
@property (nonatomic) CGFloat flatness; | |
/// A Boolean indicating whether the even-odd winding rule is in use for drawing paths. | |
/// | |
/// Setting this property affects not only drawing, but hit testing and clipping as well. | |
@property (nonatomic) BOOL usesEvenOddFillRule; | |
/// Sets the line-stroking pattern for the path. | |
- (void)setLineDash:(const CGFloat *)pattern count:(NSInteger)count phase:(CGFloat)phase; | |
/// Retrieves the line-stroking pattern for the path. | |
- (void)getLineDash:(CGFloat *)pattern count:(NSInteger *)count phase:(CGFloat *)phase; | |
#pragma mark - Drawing | |
/// Fills the reciever's path using the current fill color and drawing properties. | |
- (void)fill; | |
/// Draws a line along the receiver’s path using the current drawing properties. | |
- (void)stroke; | |
/// Paints the region enclosed by the receiver’s path using the specified blend mode and | |
/// transparency values. | |
/// | |
/// This method does not affect the blend mode or alpha of the current context. | |
- (void)fillWithBlendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha; | |
/// Draws a line along the receiver’s path using the specified blend mode and transparency values. | |
/// | |
/// This method does not affect the blend mode or alpha of the current context. | |
- (void)strokeWithBlendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha; | |
/// Intersects the area enclosed by the receiver’s path with the clipping path of the current | |
/// graphics context and makes the resulting shape the current clipping path. | |
- (void)addClip; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// NUIBezierPath.m | |
// NUIKit | |
// | |
// Created by Robert Widmann on 10/11/13. | |
// Copyright (c) 2013 CodaFi. All rights reserved. | |
// Released under the MIT license. | |
// | |
#import "NUIBezierPath.h" | |
#import "NUIGraphics.h" | |
#import "NUIColor.h" | |
#import "NUIDebugObject.h" | |
#import <AppKit/NSBezierPath.h> | |
@interface NSBezierPath (NUIPrivate) | |
+ (instancetype)bezierPathWithQuartzPath:(CGPathRef)path; | |
@end | |
@implementation NSBezierPath (NUIPrivate) | |
static void NUITransferPath(void *info, const CGPathElement *element) { | |
NSBezierPath *path = info; | |
CGPoint *points = element->points; | |
switch (element->type) { | |
case kCGPathElementMoveToPoint: | |
[path moveToPoint:NSMakePoint(points[0].x, points[0].y)]; | |
break; | |
case kCGPathElementAddLineToPoint: | |
[path lineToPoint:NSMakePoint(points[0].x, points[0].y)]; | |
break; | |
case kCGPathElementAddQuadCurveToPoint: { | |
NSPoint currentPoint = [path currentPoint]; | |
NSPoint interpolatedPoint = NSMakePoint((currentPoint.x + 2 * points[0].x) / 3, (currentPoint.y + 2 * points[0].y) / 3); | |
[path curveToPoint:NSMakePoint(points[1].x, points[1].y) controlPoint1:interpolatedPoint controlPoint2:interpolatedPoint]; | |
break; | |
} | |
case kCGPathElementAddCurveToPoint: | |
[path curveToPoint:NSMakePoint(points[2].x, points[2].y) controlPoint1:NSMakePoint(points[0].x, points[0].y) controlPoint2:NSMakePoint(points[1].x, points[1].y)]; | |
break; | |
case kCGPathElementCloseSubpath: | |
[path closePath]; | |
break; | |
} | |
} | |
+ (instancetype)bezierPathWithQuartzPath:(CGPathRef)path { | |
NSBezierPath *bezierPath = NSBezierPath.bezierPath; | |
CGPathApply(path, bezierPath, NUITransferPath); | |
return bezierPath; | |
} | |
@end | |
@implementation NUIBezierPath | |
#pragma mark - Lifecycle | |
- (id)init { | |
CGMutablePathRef ref = CGPathCreateMutable(); | |
self = [self _initWithCGMutablePath:ref]; | |
CFRelease(ref); | |
return self; | |
} | |
- (id)_initWithCGMutablePath:(CGMutablePathRef)ref { | |
self = [super init]; | |
if (ref == NULL) { | |
NSAssert(0, @"Attempted to initialize an NUIBezierPath with a NULL CGPath"); | |
NUI_RELEASE_MRCONLY(self); | |
} else { | |
_path = (CGMutablePathRef)CFRetain(ref); | |
_lineWidth = 1.0; | |
_flatness = 0.6; | |
_miterLimit = 10.0; | |
} | |
return self; | |
} | |
static CGMutablePathRef NUIDecodeBezierPathElements(NSData *data) { | |
if (!data) { | |
return NULL; | |
} | |
CGMutablePathRef result = CGPathCreateMutable(); | |
int expectingPoints = -1; | |
CGPathElementType type = 0; | |
const char *dataBuf = data.bytes; | |
char c = *dataBuf; | |
CGPoint *points = malloc(sizeof(CGPoint) * 3); | |
if (points == NULL) { | |
return result; | |
} | |
while ((c = *(dataBuf++)) != '\0') { | |
if (expectingPoints == -1) { | |
switch (c) { | |
case 'M': | |
expectingPoints = 1; | |
type = kCGPathElementMoveToPoint; | |
break; | |
case 'L': | |
expectingPoints = 1; | |
type = kCGPathElementAddLineToPoint; | |
break; | |
case 'Q': | |
expectingPoints = 2; | |
type = kCGPathElementAddQuadCurveToPoint; | |
break; | |
case 'C': | |
expectingPoints = 3; | |
type = kCGPathElementAddCurveToPoint; | |
break; | |
case 'S': | |
expectingPoints = 0; | |
type = kCGPathElementCloseSubpath; | |
break; | |
default: | |
NSCAssert(0, @"Unexpected character %c while parsing path data.", c); | |
break; | |
} | |
} | |
for (NSUInteger i = 0; i < expectingPoints; i++) { | |
float x = 0, y = 0; | |
NSCAssert(sscanf(dataBuf, "{%g, %g}", &x, &y) == 2, @"Unable to parse point format in data buffer."); | |
points[i] = (CGPoint){ x, y }; | |
while (*(dataBuf++) != '}'); | |
} | |
switch (type) { | |
case kCGPathElementMoveToPoint: | |
NSCAssert(expectingPoints == 1, @"Expected 1 point, but got %d points", expectingPoints); | |
CGPathMoveToPoint(result, NULL, points[0].x, points[0].y); | |
break; | |
case kCGPathElementAddLineToPoint: | |
NSCAssert(expectingPoints == 1, @"Expected 1 point, but got %d points", expectingPoints); | |
CGPathAddLineToPoint(result, NULL, points[0].x, points[0].y); | |
break; | |
case kCGPathElementAddQuadCurveToPoint: | |
NSCAssert(expectingPoints == 2, @"Expected 2 points, but got %d points", expectingPoints); | |
CGPathAddQuadCurveToPoint(result, NULL, points[0].x, points[0].y, points[1].x, points[1].y); | |
break; | |
case kCGPathElementAddCurveToPoint: | |
NSCAssert(expectingPoints == 3, @"Expected 3 points, but got %d points", expectingPoints); | |
CGPathAddCurveToPoint(result, NULL, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y); | |
break; | |
case kCGPathElementCloseSubpath: | |
NSCAssert(expectingPoints == 0, @"Expected 0 points, but got %d points", expectingPoints); | |
CGPathCloseSubpath(result); | |
break; | |
} | |
expectingPoints = -1; | |
} | |
free(points); | |
return result; | |
} | |
- (id)initWithCoder:(NSCoder *)aDecoder { | |
CGMutablePathRef mutablePath = NUIDecodeBezierPathElements([aDecoder decodeObjectForKey:@"NUIBezierPathCGPathDataKey"]); | |
if (mutablePath != NULL) { | |
self = [self _initWithCGMutablePath:mutablePath]; | |
CFRelease(mutablePath); | |
_lineDashPatternCount = [aDecoder decodeIntegerForKey:@"NUIBezierPathLineDashPatternCountKey"]; | |
if (_lineDashPatternCount != 0) { | |
NSUInteger len = 0; | |
const uint8_t *bytes = [aDecoder decodeBytesForKey:@"NUIBezierPathLineDashPatternKey" returnedLength:&len]; | |
if (len != _lineDashPatternCount * sizeof(CGFloat)) { | |
NSAssert(0, @""); | |
} | |
_lineDashPattern = malloc(_lineDashPatternCount * sizeof(CGFloat)); | |
if (_lineDashPattern != NULL) { | |
for (NSUInteger i = 0; i < _lineDashPatternCount; i++) { | |
_lineDashPattern[i] = bytes[i]; | |
} | |
} | |
} | |
_lineWidth = [aDecoder decodeFloatForKey:@"NUIBezierPathLineWidthKey"]; | |
_miterLimit = [aDecoder decodeFloatForKey:@"NUIBezierPathMiterLimitKey"]; | |
_flatness = [aDecoder decodeFloatForKey:@"NUIBezierPathFlatnessKey"]; | |
_lineDashPhase = [aDecoder decodeFloatForKey:@"NUIBezierPathLineDashPhaseKey"]; | |
_lineCapStyle = (CGLineCap)[aDecoder decodeIntegerForKey:@"NUIBezierPathLineCapStyleKey"]; | |
_lineJoinStyle = (CGLineJoin)[aDecoder decodeIntegerForKey:@"NUIBezierPathLineJoinStyleKey"]; | |
_usesEvenOddFillRule = [aDecoder decodeBoolForKey:@"NUIBezierPathUsesEvenOddFillRuleKey"]; | |
} | |
return self; | |
} | |
static void NUIEncodeBezierPathElements(void *info, const CGPathElement *element) { | |
NSMutableString *data = info; | |
switch (element->type) { | |
case kCGPathElementMoveToPoint: { | |
char buf[21]; | |
snprintf(buf, sizeof(buf), "M{%g, %g}", element->points[0].x, element->points[0].y); | |
[data appendFormat:@"%s", buf]; | |
} | |
break; | |
case kCGPathElementAddLineToPoint: { | |
char buf[21]; | |
snprintf(buf, sizeof(buf), "L{%g, %g}", element->points[0].x, element->points[0].y); | |
[data appendFormat:@"%s", buf]; | |
} | |
break; | |
case kCGPathElementAddQuadCurveToPoint: { | |
char buf[21]; | |
snprintf(buf, sizeof(buf), "Q{%g, %g}", element->points[0].x, element->points[0].y); | |
[data appendFormat:@"%s", buf]; | |
snprintf(buf, sizeof(buf), "{%g, %g}", element->points[1].x, element->points[1].y); | |
[data appendFormat:@"%s", buf]; | |
} | |
break; | |
case kCGPathElementAddCurveToPoint: { | |
char buf[21]; | |
snprintf(buf, sizeof(buf), "C{%g, %g}", element->points[0].x, element->points[0].y); | |
[data appendFormat:@"%s", buf]; | |
snprintf(buf, sizeof(buf), "{%g, %g}", element->points[1].x, element->points[1].y); | |
[data appendFormat:@"%s", buf]; | |
snprintf(buf, sizeof(buf), "{%g, %g}", element->points[2].x, element->points[2].y); | |
[data appendFormat:@"%s", buf]; | |
} | |
break; | |
case kCGPathElementCloseSubpath: { | |
const char *buf = "S"; | |
[data appendFormat:@"%s", buf]; | |
} | |
break; | |
} | |
} | |
- (void)encodeWithCoder:(NSCoder *)aCoder { | |
NSMutableString *string = [NSMutableString string]; | |
CGPathApply(_path, string, NUIEncodeBezierPathElements); | |
[aCoder encodeObject:[NSData dataWithBytes:string.UTF8String length:string.length] forKey:@"NUIBezierPathCGPathDataKey"]; | |
[aCoder encodeInteger:_lineDashPatternCount forKey:@"NUIBezierPathLineDashPatternCountKey"]; | |
if (_lineDashPattern != NULL && _lineDashPatternCount != 0) { | |
[aCoder encodeBytes:(void *)_lineDashPattern length:_lineDashPatternCount * sizeof(CGFloat) forKey:@"NUIBezierPathLineDashPatternKey"]; | |
} | |
[aCoder encodeFloat:_lineWidth forKey:@"NUIBezierPathLineWidthKey"]; | |
[aCoder encodeFloat:_miterLimit forKey:@"NUIBezierPathMiterLimitKey"]; | |
[aCoder encodeFloat:_flatness forKey:@"NUIBezierPathFlatnessKey"]; | |
[aCoder encodeFloat:_lineDashPhase forKey:@"NUIBezierPathLineDashPhaseKey"]; | |
[aCoder encodeInteger:_lineCapStyle forKey:@"NUIBezierPathLineCapStyleKey"]; | |
[aCoder encodeInteger:_lineJoinStyle forKey:@"NUIBezierPathLineJoinStyleKey"]; | |
[aCoder encodeBool:_usesEvenOddFillRule forKey:@"NUIBezierPathUsesEvenOddFillRuleKey"]; | |
} | |
- (void)dealloc { | |
if (_lineDashPattern != NULL) { | |
free(_lineDashPattern); | |
} | |
if (_path != NULL) { | |
CFRelease(_path); | |
} | |
if (_immutablePath != NULL) { | |
CGPathRelease(_immutablePath); | |
} | |
NUI_DEALLOC_MRCONLY; | |
} | |
+ (NUIBezierPath *)bezierPath { | |
CGMutablePathRef ref = CGPathCreateMutable(); | |
NUIBezierPath *path = [[self alloc] _initWithCGMutablePath:ref]; | |
CFRelease(ref); | |
return NUI_AUTORELEASE_MRCONLY(path); | |
} | |
+ (NUIBezierPath *)bezierPathWithRect:(CGRect)rect { | |
NUIBezierPath *path = [self.class bezierPath]; | |
CGPathAddRect([path _mutablePath], NULL, rect); | |
return path; | |
} | |
+ (NUIBezierPath *)bezierPathWithOvalInRect:(CGRect)rect { | |
NUIBezierPath *path = [self.class bezierPath]; | |
CGPathAddEllipseInRect([path _mutablePath], NULL, rect); | |
return path; | |
} | |
+ (NUIBezierPath *)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius { | |
return [self bezierPathWithRoundedRect:rect byRoundingCorners:NUIRectCornerAllCorners cornerRadii:(CGSize){ cornerRadius, cornerRadius }]; | |
} | |
+ (NUIBezierPath *)bezierPathWithRoundedRect:(CGRect)rect byRoundingCorners:(NUIRectCorner)corners cornerRadii:(CGSize)cornerRadii { | |
CGMutablePathRef pathRef = CGPathCreateMutable(); | |
CGFloat minx = CGRectGetMinX(rect), miny = CGRectGetMinY(rect); | |
CGFloat maxx = CGRectGetMaxX(rect), maxy = CGRectGetMaxY(rect); | |
if (corners & NUIRectCornerBottomLeft) { | |
CGPathMoveToPoint(pathRef, NULL, minx, miny + cornerRadii.height); | |
CGPathAddCurveToPoint(pathRef, NULL, minx, miny + cornerRadii.height, minx, miny, minx + cornerRadii.width, miny); | |
} else { | |
CGPathMoveToPoint(pathRef, NULL, minx, miny); | |
} | |
if (corners & NUIRectCornerBottomRight) { | |
CGPathAddLineToPoint(pathRef, NULL, maxx - cornerRadii.width, miny); | |
CGPathAddCurveToPoint(pathRef, NULL, maxx - cornerRadii.width, miny, maxx, miny, maxx, miny + cornerRadii.height); | |
} else { | |
CGPathAddLineToPoint(pathRef, NULL, maxx, miny); | |
} | |
if (corners & NUIRectCornerTopRight) { | |
CGPathAddLineToPoint(pathRef, NULL, maxx, maxy - cornerRadii.height); | |
CGPathAddCurveToPoint(pathRef, NULL, maxx, maxy - cornerRadii.height, maxx, maxy, maxx - cornerRadii.width, maxy); | |
} else { | |
CGPathAddLineToPoint(pathRef, NULL, maxx, maxy); | |
} | |
if (corners & NUIRectCornerTopLeft) { | |
CGPathAddLineToPoint(pathRef, NULL, minx + cornerRadii.width, maxy); | |
CGPathAddCurveToPoint(pathRef, NULL, minx + cornerRadii.width, maxy, minx, maxy, minx, maxy - cornerRadii.height); | |
} else { | |
CGPathAddLineToPoint(pathRef, NULL, minx, maxy); | |
} | |
CGPathCloseSubpath(pathRef); | |
NUIBezierPath *path = [[self alloc] _initWithCGMutablePath:pathRef]; | |
CFRelease(pathRef); | |
return NUI_AUTORELEASE_MRCONLY(path); | |
} | |
+ (NUIBezierPath *)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise { | |
CGMutablePathRef ref = CGPathCreateMutable(); | |
CGPathAddArc(ref, NULL, center.x, center.y, radius, startAngle, endAngle, clockwise); | |
NUIBezierPath *path = [[self alloc] _initWithCGMutablePath:ref]; | |
CFRelease(ref); | |
return NUI_AUTORELEASE_MRCONLY(path); | |
} | |
+ (NUIBezierPath *)bezierPathWithCGPath:(CGPathRef)CGPath { | |
NUIBezierPath *path = nil; | |
if (CGPath == NULL) { | |
NSAssert(0, @"Attempted to initialize an NUIBezierPath with a nil CGPath"); | |
} else { | |
CGMutablePathRef mutablePath = CGPathCreateMutableCopy(CGPath); | |
path = [[self.class alloc] _initWithCGMutablePath:mutablePath]; | |
CFRelease(mutablePath); | |
} | |
return NUI_AUTORELEASE_MRCONLY(path); | |
} | |
typedef struct _NUIRecord { | |
CGPathElementType type; | |
CGPoint point1; | |
CGPoint point2; | |
CGPoint point3; | |
} NUIRecord; | |
typedef struct _NUIRecorder { | |
NUIRecord *buffer; | |
NSUInteger pad; | |
NSUInteger count; | |
} NUIRecorder; | |
static void NUICountBezierPathElements(void *info, const CGPathElement *element) { | |
(*(NSUInteger *)info)++; | |
} | |
static void NUIFlipBezierPathElements(void *info, const CGPathElement *element) { | |
NUIRecorder *recorder = (NUIRecorder *)info; | |
NSUInteger idx = recorder->count--; | |
recorder->buffer[idx].type = element->type; | |
switch (element->type) { | |
case kCGPathElementMoveToPoint: | |
case kCGPathElementAddLineToPoint: { | |
recorder->buffer[idx].point1 = *element->points; | |
} | |
break; | |
case kCGPathElementAddQuadCurveToPoint: { | |
recorder->buffer[idx].point1 = element->points[1]; | |
recorder->buffer[idx].point2 = *element->points; | |
} | |
break; | |
case kCGPathElementAddCurveToPoint: { | |
recorder->buffer[idx].point1 = element->points[2]; | |
recorder->buffer[idx].point2 = *element->points; | |
recorder->buffer[idx].point3 = element->points[1]; | |
} | |
break; | |
default: { | |
recorder->buffer[idx].point1 = element->points[1]; | |
recorder->buffer[idx].point2 = *element->points; | |
} | |
break; | |
} | |
} | |
- (NUIBezierPath *)bezierPathByReversingPath { | |
NUIRecorder pathRecorder = {NULL, 0, 0}; | |
CGPathApply(_path, &pathRecorder.count, NUICountBezierPathElements); | |
NSUInteger count = pathRecorder.count; | |
if (count > 1) { | |
pathRecorder.buffer = malloc(count * sizeof(NUIRecord)); | |
CGPathApply(_path, &pathRecorder, NUIFlipBezierPathElements); | |
NUIBezierPath *reversedPath = [[self.class alloc] init]; | |
BOOL needsClose = NO; | |
for (NSUInteger i = 0; i < count; i++) { | |
NUIRecord record = pathRecorder.buffer[i]; | |
switch (record.type) { | |
case kCGPathElementMoveToPoint: | |
[reversedPath moveToPoint:record.point1]; | |
break; | |
case kCGPathElementAddLineToPoint: | |
[reversedPath addLineToPoint:record.point1]; | |
break; | |
case kCGPathElementAddQuadCurveToPoint: | |
[reversedPath addQuadCurveToPoint:record.point1 controlPoint:record.point2]; | |
break; | |
case kCGPathElementAddCurveToPoint: | |
[reversedPath addCurveToPoint:record.point1 controlPoint1:record.point2 controlPoint2:record.point3]; | |
break; | |
case kCGPathElementCloseSubpath: | |
needsClose = YES; | |
break; | |
} | |
} | |
if (needsClose) { | |
[reversedPath closePath]; | |
} | |
if (pathRecorder.count <= 0) { | |
if (pathRecorder.buffer != NULL) { | |
free(pathRecorder.buffer); | |
} | |
} | |
return [reversedPath autorelease]; | |
} | |
return [[self copy] autorelease]; | |
} | |
- (void)moveToPoint:(CGPoint)point { | |
CGPathMoveToPoint(_path, NULL, point.x, point.y); | |
_immutablePathIsValid = NO; | |
} | |
- (void)addLineToPoint:(CGPoint)point { | |
CGPathAddLineToPoint(_path, NULL, point.x, point.y); | |
_immutablePathIsValid = NO; | |
} | |
- (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2 { | |
CGPathAddCurveToPoint(_path, NULL, controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, endPoint.x, endPoint.y); | |
_immutablePathIsValid = NO; | |
} | |
- (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint { | |
CGPathAddQuadCurveToPoint(_path, NULL, controlPoint.x, controlPoint.y, endPoint.x, endPoint.y); | |
_immutablePathIsValid = NO; | |
} | |
- (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise { | |
CGPathAddArc(_path, NULL, center.x, center.y, radius, startAngle, endAngle, clockwise); | |
_immutablePathIsValid = NO; | |
} | |
- (void)appendPath:(NUIBezierPath *)bezierPath { | |
CGPathAddPath(_path, NULL, [bezierPath _mutablePath]); | |
_immutablePathIsValid = NO; | |
} | |
- (void)applyTransform:(CGAffineTransform)transform { | |
CGMutablePathRef pathRef = CGPathCreateMutable(); | |
CGPathAddPath(pathRef, &transform, _path); | |
CFRelease(_path); | |
_path = pathRef; | |
_immutablePathIsValid = NO; | |
} | |
- (void)closePath { | |
CGPathCloseSubpath(_path); | |
_immutablePathIsValid = NO; | |
} | |
- (void)removeAllPoints { | |
CFRelease(_path); | |
_path = CGPathCreateMutable(); | |
_immutablePathIsValid = NO; | |
} | |
- (void)setCGPath:(CGPathRef)CGPath { | |
if (CGPath == NULL) { | |
NSAssert(0, @"Attempted to initialize an NUIBezierPath with a nil CGPath"); | |
} else { | |
CFRelease(_path); | |
_path = CGPathCreateMutableCopy(CGPath); | |
_immutablePathIsValid = NO; | |
} | |
} | |
- (CGPathRef)CGPath { | |
if (_immutablePathIsValid == NO) { | |
CGPathRelease(_immutablePath); | |
_immutablePath = CGPathCreateCopy(_path); | |
_immutablePathIsValid = YES; | |
} | |
return _immutablePath; | |
} | |
- (BOOL)isEmpty { | |
return CGPathIsEmpty(_path); | |
} | |
- (CGRect)bounds { | |
return CGPathGetBoundingBox(_path); | |
} | |
- (CGPoint)currentPoint { | |
return CGPathGetCurrentPoint(_path); | |
} | |
- (BOOL)containsPoint:(CGPoint)point { | |
return CGPathContainsPoint(_path, NULL, point, _usesEvenOddFillRule); | |
} | |
- (void)setLineDash:(const CGFloat *)pattern count:(NSInteger)count phase:(CGFloat)phase { | |
if (_lineDashPattern != NULL) { | |
free(_lineDashPattern); | |
} | |
if ((pattern == NULL) || (count <= 0)) { | |
_lineDashPattern = NULL; | |
_lineDashPatternCount = 0; | |
_lineDashPhase = 0.f; | |
} else { | |
_lineDashPattern = malloc(sizeof(CGFloat *) * count); | |
memcpy(_lineDashPattern, pattern, count); | |
_lineDashPatternCount = count; | |
_lineDashPhase = phase; | |
} | |
} | |
- (void)getLineDash:(CGFloat *)pattern count:(NSInteger *)count phase:(CGFloat *)phase { | |
if (count != NULL) { | |
*count = _lineDashPatternCount; | |
} | |
if (phase != NULL) { | |
*phase = _lineDashPhase; | |
} | |
if ((pattern != NULL) && (_lineDashPattern != NULL)) { | |
if (_lineDashPatternCount != 0) { | |
CGFloat patternCopy[_lineDashPatternCount]; | |
for (int i = 0; i < _lineDashPatternCount; i++) { | |
patternCopy[i] = _lineDashPattern[i]; | |
} | |
pattern = patternCopy; | |
} | |
} | |
} | |
- (void)fill { | |
CGContextRef context = NUIGraphicsGetCurrentContext(); | |
CGContextSaveGState(context); | |
CGContextSetFlatness(context, _flatness); | |
CGContextAddPath(context, _path); | |
if (_usesEvenOddFillRule) { | |
CGContextEOFillPath(context); | |
} else { | |
CGContextFillPath(context); | |
} | |
CGContextRestoreGState(context); | |
} | |
- (void)stroke { | |
CGContextRef context = NUIGraphicsGetCurrentContext(); | |
CGContextSaveGState(context); | |
CGContextSetLineWidth(context, _lineWidth); | |
CGContextSetLineJoin(context, _lineJoinStyle); | |
CGContextSetLineCap(context, _lineCapStyle); | |
CGContextSetMiterLimit(context, _miterLimit); | |
CGContextSetFlatness(context, _flatness); | |
if (_lineDashPatternCount != 0) { | |
CGContextSetLineDash(context, _lineDashPhase, _lineDashPattern, _lineDashPatternCount); | |
} | |
CGContextAddPath(context, _path); | |
CGContextStrokePath(context); | |
CGContextRestoreGState(context); | |
} | |
- (void)fillWithBlendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha { | |
CGContextRef context = NUIGraphicsGetCurrentContext(); | |
CGContextSaveGState(context); | |
CGContextSetBlendMode(context, blendMode); | |
CGContextSetAlpha(context, alpha); | |
CGContextSetFlatness(context, _flatness); | |
CGContextAddPath(context, _path); | |
if (_usesEvenOddFillRule) { | |
CGContextEOFillPath(context); | |
} else { | |
CGContextFillPath(context); | |
} | |
CGContextRestoreGState(context); | |
} | |
- (void)strokeWithBlendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha { | |
CGContextRef context = NUIGraphicsGetCurrentContext(); | |
CGContextSaveGState(context); | |
CGContextSetBlendMode(context, blendMode); | |
CGContextSetAlpha(context, alpha); | |
CGContextSetLineWidth(context, _lineWidth); | |
CGContextSetLineJoin(context, _lineJoinStyle); | |
CGContextSetLineCap(context, _lineCapStyle); | |
CGContextSetMiterLimit(context, _miterLimit); | |
CGContextSetFlatness(context, _flatness); | |
if (_lineDashPatternCount != 0) { | |
CGContextSetLineDash(context, _lineDashPhase, _lineDashPattern, _lineDashPatternCount); | |
} | |
CGContextAddPath(context, _path); | |
CGContextStrokePath(context); | |
CGContextRestoreGState(context); | |
} | |
- (void)addClip { | |
CGContextRef context = NUIGraphicsGetCurrentContext(); | |
CGContextAddPath(context, _path); | |
if (_usesEvenOddFillRule) { | |
CGContextEOFillPath(context); | |
} else { | |
CGContextFillPath(context); | |
} | |
} | |
- (CGMutablePathRef)_mutablePath { | |
return (CGMutablePathRef)_path; | |
} | |
-(void)setLineWidth:(CGFloat)lineWidth { | |
_lineWidth = lineWidth; | |
} | |
- (CGFloat)lineWidth { | |
return _lineWidth; | |
} | |
- (void)setLineCapStyle:(CGLineCap)lineCapStyle { | |
_lineCapStyle = lineCapStyle; | |
} | |
- (CGLineCap)lineCapStyle { | |
return _lineCapStyle; | |
} | |
- (void)setLineJoinStyle:(CGLineJoin)lineJoinStyle { | |
_lineJoinStyle = lineJoinStyle; | |
} | |
- (CGLineJoin)lineJoinStyle { | |
return _lineJoinStyle; | |
} | |
- (void)setMiterLimit:(CGFloat)miterLimit { | |
_miterLimit = miterLimit; | |
} | |
- (CGFloat)miterLimit { | |
return _miterLimit; | |
} | |
- (void)setFlatness:(CGFloat)flatness { | |
_flatness = flatness; | |
} | |
- (CGFloat)flatness { | |
return _flatness; | |
} | |
#pragma - NSCopying | |
- (id)copyWithZone:(NSZone *)zone { | |
NUIBezierPath *path = [[NUIBezierPath allocWithZone:zone] _initWithCGMutablePath:_path]; | |
path->_lineWidth = _lineWidth; | |
path->_miterLimit = _miterLimit; | |
path->_flatness = _flatness; | |
path->_lineDashPhase = _lineDashPhase; | |
path->_lineDashPatternCount = _lineDashPatternCount; | |
path->_lineCapStyle = _lineCapStyle; | |
path->_lineJoinStyle = _lineJoinStyle; | |
path->_usesEvenOddFillRule = _usesEvenOddFillRule; | |
if (_lineDashPatternCount != 0) { | |
path->_lineDashPattern = malloc(_lineDashPatternCount << 2); | |
memcpy(path->_lineDashPattern, _lineDashPattern, _lineDashPatternCount << 2); | |
} | |
return path; | |
} | |
#pragma mark - Xcode 5 | |
- (id)debugQuickLookObject { | |
return [(id<NUIQuickLooking>)[NSBezierPath bezierPathWithQuartzPath:self.CGPath] debugQuickLookObject]; | |
} | |
@end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment