Skip to content

Instantly share code, notes, and snippets.

@griotspeak
Last active August 29, 2015 14:21
Show Gist options
  • Save griotspeak/1d865a635ea17bd9916e to your computer and use it in GitHub Desktop.
Save griotspeak/1d865a635ea17bd9916e to your computer and use it in GitHub Desktop.
//
// 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
//
// 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