1//
2//  NSBezierPath+MCAdditions.m
3//
4//  Created by Sean Patrick O'Brien on 4/1/08.
5//  Copyright 2008 MolokoCacao. All rights reserved.
6//
7
8#import "NSBezierPath+MCAdditions.h"
9
10#import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSBezierPath+CGPath.h"
11
12// remove/comment out this line of you don't want to use undocumented functions
13#define MCBEZIER_USE_PRIVATE_FUNCTION
14
15#ifdef MCBEZIER_USE_PRIVATE_FUNCTION
16extern CGPathRef CGContextCopyPath(CGContextRef context);
17#endif
18
19static void CGPathCallback(void *info, const CGPathElement *element)
20{
21	NSBezierPath *path = info;
22	CGPoint *points = element->points;
23
24	switch (element->type) {
25		case kCGPathElementMoveToPoint:
26		{
27			[path moveToPoint:NSMakePoint(points[0].x, points[0].y)];
28			break;
29		}
30		case kCGPathElementAddLineToPoint:
31		{
32			[path lineToPoint:NSMakePoint(points[0].x, points[0].y)];
33			break;
34		}
35		case kCGPathElementAddQuadCurveToPoint:
36		{
37			// NOTE: This is untested.
38			NSPoint currentPoint = [path currentPoint];
39			NSPoint interpolatedPoint = NSMakePoint((currentPoint.x + 2*points[0].x) / 3, (currentPoint.y + 2*points[0].y) / 3);
40			[path curveToPoint:NSMakePoint(points[1].x, points[1].y) controlPoint1:interpolatedPoint controlPoint2:interpolatedPoint];
41			break;
42		}
43		case kCGPathElementAddCurveToPoint:
44		{
45			[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)];
46			break;
47		}
48		case kCGPathElementCloseSubpath:
49		{
50			[path closePath];
51			break;
52		}
53	}
54}
55
56@implementation NSBezierPath (MCAdditions)
57
58+ (NSBezierPath *)bezierPathWithCGPath:(CGPathRef)pathRef
59{
60	NSBezierPath *path = [NSBezierPath bezierPath];
61	CGPathApply(pathRef, path, CGPathCallback);
62	
63	return path;
64}
65
66- (NSBezierPath *)pathWithStrokeWidth:(CGFloat)strokeWidth
67{
68#ifdef MCBEZIER_USE_PRIVATE_FUNCTION
69	NSBezierPath *path = [self copy];
70	CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
71	CGPathRef pathRef = [path gtm_CGPath];
72	[path release];
73	
74	CGContextSaveGState(context);
75		
76	CGContextBeginPath(context);
77	CGContextAddPath(context, pathRef);
78	CGContextSetLineWidth(context, strokeWidth);
79	CGContextReplacePathWithStrokedPath(context);
80	CGPathRef strokedPathRef = CGContextCopyPath(context);
81	CGContextBeginPath(context);
82	NSBezierPath *strokedPath = [NSBezierPath bezierPathWithCGPath:strokedPathRef];
83	
84	CGContextRestoreGState(context);
85	
86	CFRelease(pathRef);
87	CFRelease(strokedPathRef);
88	
89	return strokedPath;
90#else
91	return nil;
92#endif  // MCBEZIER_USE_PRIVATE_FUNCTION
93}
94
95- (void)fillWithInnerShadow:(NSShadow *)shadow
96{
97	[NSGraphicsContext saveGraphicsState];
98	
99	NSSize offset = shadow.shadowOffset;
100	NSSize originalOffset = offset;
101	CGFloat radius = shadow.shadowBlurRadius;
102	NSRect bounds = NSInsetRect(self.bounds, -(ABS(offset.width) + radius), -(ABS(offset.height) + radius));
103
104	// The context's user transform isn't automatically applied to shadow offsets.
105        offset.height += bounds.size.height;
106	shadow.shadowOffset = offset;
107	NSAffineTransform *transform = [NSAffineTransform transform];
108	if ([[NSGraphicsContext currentContext] isFlipped])
109		[transform translateXBy:0 yBy:bounds.size.height];
110	else
111		[transform translateXBy:0 yBy:-bounds.size.height];
112	
113	NSBezierPath *drawingPath = [NSBezierPath bezierPathWithRect:bounds];
114	[drawingPath setWindingRule:NSEvenOddWindingRule];
115	[drawingPath appendBezierPath:self];
116	[drawingPath transformUsingAffineTransform:transform];
117	
118	[self addClip];
119	[shadow set];
120	[[NSColor blackColor] set];
121	[drawingPath fill];
122	
123	shadow.shadowOffset = originalOffset;
124	
125	[NSGraphicsContext restoreGraphicsState];
126}
127
128- (void)drawBlurWithColor:(NSColor *)color radius:(CGFloat)radius
129{
130	NSRect bounds = NSInsetRect(self.bounds, -radius, -radius);
131	NSShadow *shadow = [[NSShadow alloc] init];
132	shadow.shadowOffset = NSMakeSize(0, bounds.size.height);
133	shadow.shadowBlurRadius = radius;
134	shadow.shadowColor = color;
135	NSBezierPath *path = [self copy];
136	NSAffineTransform *transform = [NSAffineTransform transform];
137	if ([[NSGraphicsContext currentContext] isFlipped])
138		[transform translateXBy:0 yBy:bounds.size.height];
139	else
140		[transform translateXBy:0 yBy:-bounds.size.height];
141	[path transformUsingAffineTransform:transform];
142	
143	[NSGraphicsContext saveGraphicsState];
144	
145	[shadow set];
146	[[NSColor blackColor] set];
147	NSRectClip(bounds);
148	[path fill];
149	
150	[NSGraphicsContext restoreGraphicsState];
151	
152	[path release];
153	[shadow release];
154}
155
156// Credit for the next two methods goes to Matt Gemmell
157- (void)strokeInside
158{
159    /* Stroke within path using no additional clipping rectangle. */
160    [self strokeInsideWithinRect:NSZeroRect];
161}
162
163- (void)strokeInsideWithinRect:(NSRect)clipRect
164{
165    NSGraphicsContext *thisContext = [NSGraphicsContext currentContext];
166    float lineWidth = [self lineWidth];
167    
168    /* Save the current graphics context. */
169    [thisContext saveGraphicsState];
170    
171    /* Double the stroke width, since -stroke centers strokes on paths. */
172    [self setLineWidth:(lineWidth * 2.0)];
173    
174    /* Clip drawing to this path; draw nothing outwith the path. */
175    [self setClip];
176    
177    /* Further clip drawing to clipRect, usually the view's frame. */
178    if (clipRect.size.width > 0.0 && clipRect.size.height > 0.0) {
179        [NSBezierPath clipRect:clipRect];
180    }
181    
182    /* Stroke the path. */
183    [self stroke];
184    
185    /* Restore the previous graphics context. */
186    [thisContext restoreGraphicsState];
187    [self setLineWidth:lineWidth];
188}
189
190@end
191