/** NSBezierPath.m The NSBezierPath class Copyright (C) 1999 Free Software Foundation, Inc. Author: Enrico Sersale Date: Dec 1999 Modified: Fred Kiefer Date: January 2001 This file is part of the GNUstep GUI Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111 - 1307, USA. */ #include #include #include #include #include #ifndef PI #define PI 3.1415926535897932384626433 #endif // This magic number is 4 *(sqrt(2) -1)/3 #define KAPPA 0.5522847498 #define INVALIDATE_CACHE() [self _invalidateCache] static void flatten(NSPoint coeff[], float flatness, NSBezierPath *path); static Class NSBezierPath_concrete_class = nil; static NSWindingRule default_winding_rule = NSNonZeroWindingRule; static float default_line_width = 1.0; static float default_flatness = 1.0; static NSLineJoinStyle default_line_join_style = NSMiterLineJoinStyle; static NSLineCapStyle default_line_cap_style = NSButtLineCapStyle; static float default_miter_limit = 10.0; @interface NSBezierPath (PrivateMethods) - (void)_invalidateCache; - (void)_recalculateBounds; @end @class GSBezierPath; @implementation NSBezierPath + (void)initialize { if(self == [NSBezierPath class]) NSBezierPath_concrete_class = [GSBezierPath class]; } + (void)_setConcreteClass:(Class)c { NSBezierPath_concrete_class = c; } + (Class)_concreteClass { return NSBezierPath_concrete_class; } // // Creating common paths // + (id) allocWithZone: (NSZone*)z { if (self != NSBezierPath_concrete_class) { return [NSBezierPath_concrete_class alloc]; } else { return NSAllocateObject (self, 0, z); } } + (id)bezierPath { return AUTORELEASE ([[NSBezierPath_concrete_class alloc] init]); } + (NSBezierPath *)bezierPathWithRect: (NSRect)aRect { NSBezierPath *path; NSPoint p; path = [NSBezierPath bezierPath]; [path moveToPoint: aRect.origin]; p.x = aRect.origin.x + aRect.size.width; p.y = aRect.origin.y; [path lineToPoint: p]; p.x = aRect.origin.x + aRect.size.width; p.y = aRect.origin.y + aRect.size.height; [path lineToPoint: p]; p.x = aRect.origin.x; p.y = aRect.origin.y + aRect.size.height; [path lineToPoint: p]; [path closePath]; return path; } + (NSBezierPath *)bezierPathWithOvalInRect: (NSRect)rect { NSBezierPath *path; NSPoint p, p1, p2; double originx = rect.origin.x; double originy = rect.origin.y; double width = rect.size.width; double height = rect.size.height; double hdiff = width / 2 * KAPPA; double vdiff = height / 2 * KAPPA; path = [NSBezierPath bezierPath]; p = NSMakePoint(originx + width / 2, originy + height); [path moveToPoint: p]; p = NSMakePoint(originx, originy + height / 2); p1 = NSMakePoint(originx + width / 2 - hdiff, originy + height); p2 = NSMakePoint(originx, originy + height / 2 + vdiff); [path curveToPoint: p controlPoint1: p1 controlPoint2: p2]; p = NSMakePoint(originx + width / 2, originy); p1 = NSMakePoint(originx, originy + height / 2 - vdiff); p2 = NSMakePoint(originx + width / 2 - hdiff, originy); [path curveToPoint: p controlPoint1: p1 controlPoint2: p2]; p = NSMakePoint(originx + width, originy + height / 2); p1 = NSMakePoint(originx + width / 2 + hdiff, originy); p2 = NSMakePoint(originx + width, originy + height / 2 - vdiff); [path curveToPoint: p controlPoint1: p1 controlPoint2: p2]; p = NSMakePoint(originx + width / 2, originy + height); p1 = NSMakePoint(originx + width, originy + height / 2 + vdiff); p2 = NSMakePoint(originx + width / 2 + hdiff, originy + height); [path curveToPoint: p controlPoint1: p1 controlPoint2: p2]; return path; } // // Immediate mode drawing of common paths // + (void)fillRect: (NSRect)aRect { PSrectfill(NSMinX(aRect), NSMinY(aRect), NSWidth(aRect), NSHeight(aRect)); } + (void)strokeRect: (NSRect)aRect { PSrectstroke(NSMinX(aRect), NSMinY(aRect), NSWidth(aRect), NSHeight(aRect)); } + (void)clipRect: (NSRect)aRect { PSrectclip(NSMinX(aRect), NSMinY(aRect), NSWidth(aRect), NSHeight(aRect)); } + (void)strokeLineFromPoint: (NSPoint)point1 toPoint: (NSPoint)point2 { NSBezierPath *path = [NSBezierPath bezierPath]; [path moveToPoint: point1]; [path lineToPoint: point2]; [path stroke]; } + (void)drawPackedGlyphs: (const char *)packedGlyphs atPoint: (NSPoint)aPoint { NSBezierPath *path = [NSBezierPath bezierPath]; [path moveToPoint: aPoint]; [path appendBezierPathWithPackedGlyphs: packedGlyphs]; [path stroke]; } // // Default path rendering parameters // + (void)setDefaultMiterLimit:(float)limit { default_miter_limit = limit; // Do we need this? PSsetmiterlimit(limit); } + (float)defaultMiterLimit { return default_miter_limit; } + (void)setDefaultFlatness:(float)flatness { default_flatness = flatness; PSsetflat(flatness); } + (float)defaultFlatness { return default_flatness; } + (void)setDefaultWindingRule:(NSWindingRule)windingRule { default_winding_rule = windingRule; } + (NSWindingRule)defaultWindingRule { return default_winding_rule; } + (void)setDefaultLineCapStyle:(NSLineCapStyle)lineCapStyle { default_line_cap_style = lineCapStyle; PSsetlinecap(lineCapStyle); } + (NSLineCapStyle)defaultLineCapStyle { return default_line_cap_style; } + (void)setDefaultLineJoinStyle:(NSLineJoinStyle)lineJoinStyle { default_line_join_style = lineJoinStyle; PSsetlinejoin(lineJoinStyle); } + (NSLineJoinStyle)defaultLineJoinStyle { return default_line_join_style; } + (void)setDefaultLineWidth:(float)lineWidth { default_line_width = lineWidth; PSsetlinewidth(lineWidth); } + (float)defaultLineWidth { return default_line_width; } - (id) init { [super init]; // Those values come from the default. [self setLineWidth: default_line_width]; [self setFlatness: default_flatness]; [self setLineCapStyle: default_line_cap_style]; [self setLineJoinStyle: default_line_join_style]; [self setMiterLimit: default_miter_limit]; [self setWindingRule: default_winding_rule]; // Set by allocation //_bounds = NSZeroRect; //_controlPointBounds = NSZeroRect; //_cachesBezierPath = NO; //_cacheImage = nil; //_dash_count = 0; //_dash_phase = 0; //_dash_pattern = NULL; return self; } - (void) dealloc { if(_cacheImage != nil) RELEASE(_cacheImage); if (_dash_pattern != NULL) NSZoneFree([self zone], _dash_pattern); [super dealloc]; } // // Path construction // - (void)moveToPoint:(NSPoint)aPoint { [self subclassResponsibility:_cmd]; } - (void)lineToPoint:(NSPoint)aPoint { [self subclassResponsibility:_cmd]; } - (void)curveToPoint:(NSPoint)aPoint controlPoint1:(NSPoint)controlPoint1 controlPoint2:(NSPoint)controlPoint2 { [self subclassResponsibility:_cmd]; } - (void)closePath { [self subclassResponsibility:_cmd]; } - (void)removeAllPoints { [self subclassResponsibility:_cmd]; } // // Relative path construction // - (void)relativeMoveToPoint:(NSPoint)aPoint { NSPoint p = [self currentPoint]; p.x = p.x + aPoint.x; p.y = p.y + aPoint.y; [self moveToPoint: p]; } - (void)relativeLineToPoint:(NSPoint)aPoint { NSPoint p = [self currentPoint]; p.x = p.x + aPoint.x; p.y = p.y + aPoint.y; [self lineToPoint: p]; } - (void)relativeCurveToPoint:(NSPoint)aPoint controlPoint1:(NSPoint)controlPoint1 controlPoint2:(NSPoint)controlPoint2 { NSPoint p = [self currentPoint]; aPoint.x = p.x + aPoint.x; aPoint.y = p.y + aPoint.y; controlPoint1.x = p.x + controlPoint1.x; controlPoint1.y = p.y + controlPoint1.y; controlPoint2.x = p.x + controlPoint2.x; controlPoint2.y = p.y + controlPoint2.y; [self curveToPoint: aPoint controlPoint1: controlPoint1 controlPoint2: controlPoint2]; } // // Path rendering parameters // - (float)lineWidth { return _lineWidth; } - (void)setLineWidth:(float)lineWidth { _lineWidth = lineWidth; } - (NSLineCapStyle)lineCapStyle { return _lineCapStyle; } - (void)setLineCapStyle:(NSLineCapStyle)lineCapStyle { _lineCapStyle = lineCapStyle; } - (NSLineJoinStyle)lineJoinStyle { return _lineJoinStyle; } - (void)setLineJoinStyle:(NSLineJoinStyle)lineJoinStyle { _lineJoinStyle = lineJoinStyle; } - (NSWindingRule)windingRule { return _windingRule; } - (void)setWindingRule:(NSWindingRule)windingRule { _windingRule = windingRule; } - (void)setFlatness:(float)flatness { _flatness = flatness; } - (float)flatness { return _flatness; } - (void)setMiterLimit:(float)limit { _miterLimit = limit; } - (float)miterLimit { return _miterLimit; } - (void)getLineDash:(float *)pattern count:(int *)count phase:(float *)phase { // FIXME: How big is the pattern array? // We assume that this value is in count! if (count != NULL) { if (*count < _dash_count) { *count = _dash_count; return; } *count = _dash_count; } if (phase != NULL) *phase = _dash_phase; memcpy(pattern, _dash_pattern, _dash_count * sizeof(float)); } - (void)setLineDash:(const float *)pattern count:(int)count phase:(float)phase { NSZone *myZone = [self zone]; if ((pattern == NULL) || (count == 0)) { if (_dash_pattern != NULL) { NSZoneFree(myZone, _dash_pattern); _dash_pattern = NULL; } _dash_count = 0; _dash_phase = 0.0; return; } if (_dash_pattern == NULL) _dash_pattern = NSZoneMalloc(myZone, count * sizeof(float)); else NSZoneRealloc(myZone, _dash_pattern, count * sizeof(float)); _dash_count = count; _dash_phase = phase; memcpy(_dash_pattern, pattern, _dash_count * sizeof(float)); } // // Path operations // - (void)stroke { NSGraphicsContext *ctxt = GSCurrentContext(); if(_cachesBezierPath) { NSRect bounds = [self bounds]; NSPoint origin = bounds.origin; // FIXME: I don't see how this should work with color changes if(_cacheImage == nil) { _cacheImage = [[NSImage alloc] initWithSize: bounds.size]; [_cacheImage lockFocus]; DPStranslate(ctxt, -origin.x, -origin.y); [ctxt GSSendBezierPath: self]; DPSstroke(ctxt); [_cacheImage unlockFocus]; } [_cacheImage compositeToPoint: origin operation: NSCompositeCopy]; } else { [ctxt GSSendBezierPath: self]; DPSstroke(ctxt); } } - (void)fill { NSGraphicsContext *ctxt = GSCurrentContext(); if(_cachesBezierPath) { NSRect bounds = [self bounds]; NSPoint origin = bounds.origin; // FIXME: I don't see how this should work with color changes if(_cacheImage == nil) { _cacheImage = [[NSImage alloc] initWithSize: bounds.size]; [_cacheImage lockFocus]; DPStranslate(ctxt, -origin.x, -origin.y); [ctxt GSSendBezierPath: self]; if([self windingRule] == NSNonZeroWindingRule) DPSfill(ctxt); else DPSeofill(ctxt); [_cacheImage unlockFocus]; } [_cacheImage compositeToPoint: origin operation: NSCompositeCopy]; } else { [ctxt GSSendBezierPath: self]; if([self windingRule] == NSNonZeroWindingRule) DPSfill(ctxt); else DPSeofill(ctxt); } } - (void)addClip { NSGraphicsContext *ctxt = GSCurrentContext(); [ctxt GSSendBezierPath: self]; if([self windingRule] == NSNonZeroWindingRule) DPSclip(ctxt); else DPSeoclip(ctxt); } - (void)setClip { NSGraphicsContext *ctxt = GSCurrentContext(); DPSinitclip(ctxt); [ctxt GSSendBezierPath: self]; if([self windingRule] == NSNonZeroWindingRule) DPSclip(ctxt); else DPSeoclip(ctxt); } // // Path modifications. // - (NSBezierPath *)bezierPathByFlatteningPath { NSBezierPath *path = [isa bezierPath]; NSBezierPathElement type; NSPoint pts[3]; NSPoint coeff[4]; NSPoint p, last_p; int i, count; BOOL first = YES; count = [self elementCount]; for(i = 0; i < count; i++) { type = [self elementAtIndex: i associatedPoints: pts]; switch(type) { case NSMoveToBezierPathElement: [path moveToPoint: pts[0]]; last_p = p = pts[0]; first = NO; break; case NSLineToBezierPathElement: [path lineToPoint: pts[0]]; p = pts[0]; if (first) { last_p = pts[0]; first = NO; } break; case NSCurveToBezierPathElement: coeff[0] = p; coeff[1] = pts[0]; coeff[2] = pts[1]; coeff[3] = pts[2]; flatten(coeff, [self flatness], path); p = pts[2]; if (first) { last_p = pts[2]; first = NO; } break; case NSClosePathBezierPathElement: [path closePath]; p = last_p; break; default: break; } } return path; } - (NSBezierPath *) bezierPathByReversingPath { NSBezierPath *path = [isa bezierPath]; NSBezierPathElement type, last_type; NSPoint pts[3]; NSPoint p, cp1, cp2; int i, j, count; BOOL closed = NO; last_type = NSMoveToBezierPathElement; count = [self elementCount]; for(i = count - 1; i >= 0; i--) { type = [self elementAtIndex: i associatedPoints: pts]; switch(type) { case NSMoveToBezierPathElement: p = pts[0]; break; case NSLineToBezierPathElement: p = pts[0]; break; case NSCurveToBezierPathElement: cp1 = pts[0]; cp2 = pts[1]; p = pts[2]; break; case NSClosePathBezierPathElement: // find the first point of segment for (j = i - 1; j >= 0; j--) { type = [self elementAtIndex: i associatedPoints: pts]; if (type == NSMoveToBezierPathElement) { p = pts[0]; break; } } // FIXME: What to do if we don't find a move element? break; default: break; } switch(last_type) { case NSMoveToBezierPathElement: if (closed) { [path closePath]; closed = NO; } [path moveToPoint: p]; break; case NSLineToBezierPathElement: [path lineToPoint: p]; break; case NSCurveToBezierPathElement: [path curveToPoint: p controlPoint1: cp2 controlPoint2: cp1]; break; case NSClosePathBezierPathElement: closed = YES; break; default: break; } last_type = type; } if (closed) [path closePath]; return self; } // // Applying transformations. // - (void) transformUsingAffineTransform: (NSAffineTransform *)transform { NSBezierPathElement type; NSPoint pts[3]; int i, count; count = [self elementCount]; for(i = 0; i < count; i++) { type = [self elementAtIndex: i associatedPoints: pts]; switch(type) { case NSMoveToBezierPathElement: case NSLineToBezierPathElement: pts[0] = [transform transformPoint: pts[0]]; [self setAssociatedPoints: pts atIndex: i]; break; case NSCurveToBezierPathElement: pts[0] = [transform transformPoint: pts[0]]; pts[1] = [transform transformPoint: pts[1]]; pts[2] = [transform transformPoint: pts[2]]; [self setAssociatedPoints: pts atIndex: i]; break; case NSClosePathBezierPathElement: break; default: break; } } INVALIDATE_CACHE(); } // // Path info // - (BOOL) isEmpty { return ([self elementCount] == 0); } - (NSPoint) currentPoint { NSBezierPathElement type; NSPoint points[3]; int i, count; count = [self elementCount]; if (!count) [NSException raise: NSGenericException format: @"No current Point in NSBezierPath"]; type = [self elementAtIndex: count - 1 associatedPoints: points]; switch(type) { case NSMoveToBezierPathElement: case NSLineToBezierPathElement: return points[0]; break; case NSCurveToBezierPathElement: return points[2]; break; case NSClosePathBezierPathElement: // We have to find the last move element and take its point for (i = count - 2; i >= 0; i--) { type = [self elementAtIndex: i associatedPoints: points]; if (type == NSMoveToBezierPathElement) return points[0]; } break; default: break; } return NSZeroPoint; } - (NSRect) controlPointBounds { if (_shouldRecalculateBounds) [self _recalculateBounds]; return _controlPointBounds; } - (NSRect) bounds { if (_shouldRecalculateBounds) [self _recalculateBounds]; return _bounds; } // // Elements // - (int) elementCount { [self subclassResponsibility:_cmd]; return 0; } - (NSBezierPathElement) elementAtIndex: (int)index associatedPoints: (NSPoint *)points { [self subclassResponsibility:_cmd]; return 0; } - (NSBezierPathElement) elementAtIndex: (int)index { return [self elementAtIndex: index associatedPoints: NULL]; } - (void)setAssociatedPoints:(NSPoint *)points atIndex:(int)index { [self subclassResponsibility:_cmd]; } // // Appending common paths // - (void) appendBezierPath: (NSBezierPath *)aPath { NSBezierPathElement type; NSPoint points[3]; int i, count; count = [aPath elementCount]; for (i = 0; i < count; i++) { type = [aPath elementAtIndex: i associatedPoints: points]; switch(type) { case NSMoveToBezierPathElement: [self moveToPoint: points[0]]; break; case NSLineToBezierPathElement: [self lineToPoint: points[0]]; break; case NSCurveToBezierPathElement: [self curveToPoint: points[2] controlPoint1: points[0] controlPoint2: points[1]]; break; case NSClosePathBezierPathElement: [self closePath]; break; default: break; } } } - (void)appendBezierPathWithRect:(NSRect)rect { [self appendBezierPath: [isa bezierPathWithRect: rect]]; } - (void)appendBezierPathWithPoints:(NSPoint *)points count:(int)count { int i; if (!count) return; if ([self isEmpty]) { [self moveToPoint: points[0]]; } else { [self lineToPoint: points[0]]; } for (i = 1; i < count; i++) [self lineToPoint: points[i]]; } - (void) appendBezierPathWithOvalInRect: (NSRect)aRect { [self appendBezierPath: [isa bezierPathWithOvalInRect: aRect]]; } /* startAngle and endAngle are in degrees, counterclockwise, from the x axis */ - (void) appendBezierPathWithArcWithCenter: (NSPoint)center radius: (float)radius startAngle: (float)startAngle endAngle: (float)endAngle clockwise: (BOOL)clockwise { float startAngle_rad, endAngle_rad, diff; NSPoint p0, p1, p2, p3; /* We use the Postscript prescription for managing the angles and drawing the arc. See the documentation for `arc' and `arcn' in the Postscript Reference. */ if (clockwise) { /* This modification of the angles is the postscript prescription. */ while (startAngle < endAngle) endAngle -= 360; /* This is used when we draw a clockwise quarter of circumference. By adding diff at the starting angle of the quarter, we get the ending angle. diff is negative because we draw clockwise. */ diff = - PI / 2; } else { /* This modification of the angles is the postscript prescription. */ while (endAngle < startAngle) endAngle += 360; /* This is used when we draw a counterclockwise quarter of circumference. By adding diff at the starting angle of the quarter, we get the ending angle. diff is positive because we draw counterclockwise. */ diff = PI / 2; } /* Convert the angles to radians */ startAngle_rad = PI * startAngle / 180; endAngle_rad = PI * endAngle / 180; /* Start point */ p0 = NSMakePoint (center.x + radius * cos (startAngle_rad), center.y + radius * sin (startAngle_rad)); if ([self elementCount] == 0) { [self moveToPoint: p0]; } else { NSPoint ps = [self currentPoint]; if (p0.x != ps.x || p0.y != ps.y) { [self lineToPoint: p0]; } } while ((clockwise) ? (startAngle_rad > endAngle_rad) : (startAngle_rad < endAngle_rad)) { /* Add a quarter circle */ if ((clockwise) ? (startAngle_rad + diff >= endAngle_rad) : (startAngle_rad + diff <= endAngle_rad)) { float sin_start = sin (startAngle_rad); float cos_start = cos (startAngle_rad); float sign = (clockwise) ? -1.0 : 1.0; p1 = NSMakePoint (center.x + radius * (cos_start - KAPPA * sin_start * sign), center.y + radius * (sin_start + KAPPA * cos_start * sign)); p2 = NSMakePoint (center.x + radius * (-sin_start * sign + KAPPA * cos_start), center.y + radius * (cos_start * sign + KAPPA * sin_start)); p3 = NSMakePoint (center.x + radius * (-sin_start * sign), center.y + radius * cos_start * sign); [self curveToPoint: p3 controlPoint1: p1 controlPoint2: p2]; startAngle_rad += diff; } else { /* Add the missing bit * We require that the arc be less than a semicircle. * The arc may go either clockwise or counterclockwise. * The approximation is a very simple one: a single curve * whose middle two control points are a fraction F of the way * to the intersection of the tangents, where * F = (4/3) / (1 + sqrt (1 + (d / r)^2)) * where r is the radius and d is the distance from either tangent * point to the intersection of the tangents. This produces * a curve whose center point, as well as its ends, lies on * the desired arc. */ NSPoint ps = [self currentPoint]; /* tangent is the tangent of half the angle */ float tangent = tan ((endAngle_rad - startAngle_rad) / 2); /* trad is the distance from either tangent point to the intersection of the tangents */ float trad = radius * tangent; /* pt is the intersection of the tangents */ NSPoint pt = NSMakePoint (ps.x - trad * sin (startAngle_rad), ps.y + trad * cos (startAngle_rad)); /* This is F - in this expression we need to compute (trad/radius)^2, which is simply tangent^2 */ float f = (4.0 / 3.0) / (1.0 + sqrt (1.0 + (tangent * tangent))); p1 = NSMakePoint (ps.x + (pt.x - ps.x) * f, ps.y + (pt.y - ps.y) * f); p3 = NSMakePoint(center.x + radius * cos (endAngle_rad), center.y + radius * sin (endAngle_rad)); p2 = NSMakePoint (p3.x + (pt.x - p3.x) * f, p3.y + (pt.y - p3.y) * f); [self curveToPoint: p3 controlPoint1: p1 controlPoint2: p2]; break; } } } - (void) appendBezierPathWithArcWithCenter: (NSPoint)center radius: (float)radius startAngle: (float)startAngle endAngle: (float)endAngle { [self appendBezierPathWithArcWithCenter: center radius: radius startAngle: startAngle endAngle: endAngle clockwise: NO]; } - (void) appendBezierPathWithArcFromPoint: (NSPoint)point1 toPoint: (NSPoint)point2 radius: (float)radius { float x1, y1; float dx1, dy1, dx2, dy2; float l, a1, a2; NSPoint p; p = [self currentPoint]; x1 = point1.x; y1 = point1.y; dx1 = p.x - x1; dy1 = p.y - y1; l= dx1*dx1 + dy1*dy1; if (l <= 0) { [self lineToPoint: point1]; return; } l = 1/sqrt(l); dx1 *= l; dy1 *= l; dx2 = point2.x - x1; dy2 = point2.y - y1; l = dx2*dx2 + dy2*dy2; if (l <= 0) { [self lineToPoint: point1]; return; } l = 1/sqrt(l); dx2 *= l; dy2 *= l; l = dx1*dx2 + dy1*dy2; if (l < -0.999) { [self lineToPoint: point1]; return; } l = radius/sin(acos(l)); p.x = x1 + (dx1 + dx2)*l; p.y = y1 + (dy1 + dy2)*l; if (dx1 < -1) a1 = 180; else if (dx1 > 1) a1 = 0; else a1 = acos(dx1)/PI*180; if (dy1 < 0) { a1 = -a1; } if (dx2 < -1) a2 = 180; else if (dx2 > 1) a2 = 0; else a2 = acos(dx2)/PI*180; if (dy2 < 0) { a2 = -a2; } l = dx1*dy2 - dx2*dy1; if (l < 0) { a2 = a2 - 90; a1 = a1 + 90; [self appendBezierPathWithArcWithCenter: p radius: radius startAngle: a1 endAngle: a2 clockwise: NO]; } else { a2 = a2 + 90; a1 = a1 - 90; [self appendBezierPathWithArcWithCenter: p radius: radius startAngle: a1 endAngle: a2 clockwise: YES]; } } - (void)appendBezierPathWithGlyph:(NSGlyph)glyph inFont:(NSFont *)font { // TODO } - (void)appendBezierPathWithGlyphs:(NSGlyph *)glyphs count:(int)count inFont:(NSFont *)font { // TODO } - (void)appendBezierPathWithPackedGlyphs:(const char *)packedGlyphs { // TODO } // // Hit detection // - (BOOL)containsPoint:(NSPoint)point { [self subclassResponsibility:_cmd]; return NO; } // // Caching paths // // // Caching // - (BOOL)cachesBezierPath { return _cachesBezierPath; } - (void)setCachesBezierPath:(BOOL)flag { _cachesBezierPath = flag; if(!flag) INVALIDATE_CACHE(); } // // NSCoding protocol // - (void)encodeWithCoder:(NSCoder *)aCoder { NSBezierPathElement type; NSPoint pts[3]; int i, count; float f; f = [self lineWidth]; [aCoder encodeValueOfObjCType: @encode(float) at: &f]; i = [self lineCapStyle]; [aCoder encodeValueOfObjCType: @encode(int) at: &i]; i = [self lineJoinStyle]; [aCoder encodeValueOfObjCType: @encode(int) at: &i]; i = [self windingRule]; [aCoder encodeValueOfObjCType: @encode(int) at: &i]; [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_cachesBezierPath]; count = [self elementCount]; [aCoder encodeValueOfObjCType: @encode(int) at: &count]; for(i = 0; i < count; i++) { type = [self elementAtIndex: i associatedPoints: pts]; [aCoder encodeValueOfObjCType: @encode(NSBezierPathElement) at: &type]; switch(type) { case NSMoveToBezierPathElement: case NSLineToBezierPathElement: [aCoder encodeValueOfObjCType: @encode(NSPoint) at: &pts[0]]; break; case NSCurveToBezierPathElement: [aCoder encodeValueOfObjCType: @encode(NSPoint) at: &pts[0]]; [aCoder encodeValueOfObjCType: @encode(NSPoint) at: &pts[1]]; [aCoder encodeValueOfObjCType: @encode(NSPoint) at: &pts[2]]; break; case NSClosePathBezierPathElement: break; default: break; } } } - (id)initWithCoder:(NSCoder *)aCoder { NSBezierPathElement type; NSPoint pts[3]; int i, count; float f; // We have to init the place to store the elements [self init]; [aCoder decodeValueOfObjCType: @encode(float) at: &f]; [self setLineWidth: f]; [aCoder decodeValueOfObjCType: @encode(int) at: &i]; [self setLineCapStyle: i]; [aCoder decodeValueOfObjCType: @encode(int) at: &i]; [self setLineJoinStyle: i]; [aCoder decodeValueOfObjCType: @encode(int) at: &i]; [self setWindingRule: i]; [aCoder decodeValueOfObjCType: @encode(BOOL) at: &_cachesBezierPath]; _cacheImage = nil; _shouldRecalculateBounds = YES; [aCoder decodeValueOfObjCType: @encode(int) at: &count]; for(i = 0; i < count; i++) { [aCoder decodeValueOfObjCType: @encode(NSBezierPathElement) at: &type]; switch(type) { case NSMoveToBezierPathElement: [aCoder decodeValueOfObjCType: @encode(NSPoint) at: &pts[0]]; [self moveToPoint: pts[0]]; case NSLineToBezierPathElement: [aCoder decodeValueOfObjCType: @encode(NSPoint) at: &pts[0]]; [self lineToPoint: pts[0]]; break; case NSCurveToBezierPathElement: [aCoder decodeValueOfObjCType: @encode(NSPoint) at: &pts[0]]; [aCoder decodeValueOfObjCType: @encode(NSPoint) at: &pts[1]]; [aCoder decodeValueOfObjCType: @encode(NSPoint) at: &pts[2]]; [self curveToPoint: pts[0] controlPoint1: pts[1] controlPoint2: pts[2]]; break; case NSClosePathBezierPathElement: [self closePath]; break; default: break; } } return self; } // // NSCopying Protocol // - (id)copyWithZone:(NSZone *)zone { NSBezierPath *path = (NSBezierPath*)NSCopyObject (self, 0, zone); if(_cachesBezierPath && _cacheImage) path->_cacheImage = [_cacheImage copy]; if (_dash_pattern != NULL) { float *pattern = NSZoneMalloc(zone, _dash_count * sizeof(float)); memcpy(pattern, _dash_pattern, _dash_count * sizeof(float)); _dash_pattern = pattern; } return path; } @end @implementation NSBezierPath (PrivateMethods) - (void) _invalidateCache { _shouldRecalculateBounds = YES; DESTROY(_cacheImage); } - (void)_recalculateBounds { NSBezierPathElement type; NSPoint p, last_p; NSPoint pts[3]; // This will compute three intermediate points per curve double x, y, t, k = 0.25; float maxx, minx, maxy, miny; float cpmaxx, cpminx, cpmaxy, cpminy; int i, count; BOOL first = YES; count = [self elementCount]; if(!count) { _bounds = NSZeroRect; _controlPointBounds = NSZeroRect; return; } // Some big starting values maxx = maxy = cpmaxx = cpmaxy = -1E9; minx = miny = cpminx = cpminy = 1E9; for(i = 0; i < count; i++) { type = [self elementAtIndex: i associatedPoints: pts]; switch(type) { case NSMoveToBezierPathElement: last_p = pts[0]; // NO BREAK case NSLineToBezierPathElement: if (first) { maxx = minx = cpmaxx = cpminx = pts[0].x; maxy = miny = cpmaxy = cpminy = pts[0].y; last_p = pts[0]; first = NO; } else { if(pts[0].x > maxx) maxx = pts[0].x; if(pts[0].x < minx) minx = pts[0].x; if(pts[0].y > maxy) maxy = pts[0].y; if(pts[0].y < miny) miny = pts[0].y; if(pts[0].x > cpmaxx) cpmaxx = pts[0].x; if(pts[0].x < cpminx) cpminx = pts[0].x; if(pts[0].y > cpmaxy) cpmaxy = pts[0].y; if(pts[0].y < cpminy) cpminy = pts[0].y; } p = pts[0]; break; case NSCurveToBezierPathElement: if (first) { maxx = minx = cpmaxx = cpminx = pts[0].x; maxy = miny = cpmaxy = cpminy = pts[0].y; p = last_p = pts[0]; first = NO; } if(pts[2].x > maxx) maxx = pts[2].x; if(pts[2].x < minx) minx = pts[2].x; if(pts[2].y > maxy) maxy = pts[2].y; if(pts[2].y < miny) miny = pts[2].y; if(pts[0].x > cpmaxx) cpmaxx = pts[0].x; if(pts[0].x < cpminx) cpminx = pts[0].x; if(pts[0].y > cpmaxy) cpmaxy = pts[0].y; if(pts[0].y < cpminy) cpminy = pts[0].y; if(pts[1].x > cpmaxx) cpmaxx = pts[1].x; if(pts[1].x < cpminx) cpminx = pts[1].x; if(pts[1].y > cpmaxy) cpmaxy = pts[1].y; if(pts[1].y < cpminy) cpminy = pts[1].y; if(pts[2].x > cpmaxx) cpmaxx = pts[2].x; if(pts[2].x < cpminx) cpminx = pts[2].x; if(pts[2].y > cpmaxy) cpmaxy = pts[2].y; if(pts[2].y < cpminy) cpminy = pts[2].y; for(t = k; t <= 1+k; t += k) { x = (p.x+t*(-p.x*3+t*(3*p.x-p.x*t)))+ t*(3*pts[0].x+t*(-6*pts[0].x+pts[0].x*3*t))+ t*t*(pts[1].x*3-pts[1].x*3*t)+pts[2].x*t*t*t; y = (p.y+t*(-p.y*3+t*(3*p.y-p.y*t)))+ t*(3*pts[0].y+t*(-6*pts[0].y+pts[0].y*3*t))+ t*t*(pts[1].y*3-pts[1].y*3*t)+pts[2].y*t*t*t; if(x > cpmaxx) cpmaxx = x; if(x < cpminx) cpminx = x; if(y > cpmaxy) cpmaxy = y; if(y < cpminy) cpminy = y; } p = pts[2]; break; case NSClosePathBezierPathElement: // This does not add to the bounds, but changes the current point p = last_p; break; default: break; } } _bounds = NSMakeRect(minx, miny, maxx - minx, maxy - miny); _controlPointBounds = NSMakeRect(cpminx, cpminy, cpmaxx - cpminx, cpmaxy - cpminy); _shouldRecalculateBounds = NO; } @end typedef struct _PathElement { NSBezierPathElement type; NSPoint points[3]; } PathElement; //#define GSUNION_TYPES GSUNION_OBJ #define GSI_ARRAY_TYPES 0 #define GSI_ARRAY_TYPE PathElement #define GSI_ARRAY_NO_RETAIN #define GSI_ARRAY_NO_RELEASE #ifdef GSIArray #undef GSIArray #endif #include @interface GSBezierPath : NSBezierPath { GSIArray pathElements; BOOL flat; } @end @implementation GSBezierPath - (id)init { NSZone *zone; self = [super init]; zone = GSObjCZone(self); pathElements = NSZoneMalloc(zone, sizeof(GSIArray_t)); GSIArrayInitWithZoneAndCapacity(pathElements, zone, 8); flat = YES; return self; } - (void)dealloc { GSIArrayEmpty(pathElements); NSZoneFree(GSObjCZone(self), pathElements); [super dealloc]; } // // Path construction // - (void)moveToPoint:(NSPoint)aPoint { PathElement elem; elem.type = NSMoveToBezierPathElement; elem.points[0] = aPoint; GSIArrayAddItem(pathElements, (GSIArrayItem)elem); INVALIDATE_CACHE(); } - (void)lineToPoint:(NSPoint)aPoint { PathElement elem; elem.type = NSLineToBezierPathElement; elem.points[0] = aPoint; GSIArrayAddItem(pathElements, (GSIArrayItem)elem); INVALIDATE_CACHE(); } - (void) curveToPoint: (NSPoint)aPoint controlPoint1: (NSPoint)controlPoint1 controlPoint2: (NSPoint)controlPoint2 { PathElement elem; elem.type = NSCurveToBezierPathElement; elem.points[0] = controlPoint1; elem.points[1] = controlPoint2; elem.points[2] = aPoint; GSIArrayAddItem(pathElements, (GSIArrayItem)elem); flat = NO; INVALIDATE_CACHE(); } - (void)closePath { PathElement elem; elem.type = NSClosePathBezierPathElement; GSIArrayAddItem(pathElements, (GSIArrayItem)elem); INVALIDATE_CACHE(); } - (void)removeAllPoints { GSIArrayRemoveAllItems(pathElements); INVALIDATE_CACHE(); } // // Elements // - (int)elementCount { return GSIArrayCount(pathElements); } - (NSBezierPathElement)elementAtIndex:(int)index associatedPoints:(NSPoint *)points { PathElement elm = GSIArrayItemAtIndex(pathElements, index).ext; NSBezierPathElement type = elm.type; if (points != NULL) { if(type == NSMoveToBezierPathElement || type == NSLineToBezierPathElement) { points[0] = elm.points[0]; } else if(type == NSCurveToBezierPathElement) { points[0] = elm.points[0]; points[1] = elm.points[1]; points[2] = elm.points[2]; } } return type; } - (void)setAssociatedPoints:(NSPoint *)points atIndex:(int)index { PathElement elm = GSIArrayItemAtIndex(pathElements, index).ext; NSBezierPathElement type = elm.type; switch(type) { case NSMoveToBezierPathElement: case NSLineToBezierPathElement: elm.points[0] = points[0]; break; case NSCurveToBezierPathElement: elm.points[0] = points[0]; elm.points[1] = points[1]; elm.points[2] = points[2]; break; case NSClosePathBezierPathElement: break; default: break; } GSIArraySetItemAtIndex(pathElements, (GSIArrayItem)elm, index); INVALIDATE_CACHE(); } // // Path modifications. // - (NSBezierPath *)bezierPathByFlatteningPath { if (flat) return self; return [super bezierPathByFlatteningPath]; } - (void) transformUsingAffineTransform: (NSAffineTransform *)transform { NSBezierPathElement type; int i, count; PathElement *elments = (PathElement *)GSIArrayItems(pathElements); SEL transformPointSel = @selector(transformPoint:); NSPoint (*transformPointImp)(NSAffineTransform*, SEL, NSPoint); transformPointImp = (NSPoint (*)(NSAffineTransform*, SEL, NSPoint)) [transform methodForSelector: transformPointSel]; count = GSIArrayCount(pathElements); for(i = 0; i < count; i++) { type = elments[i].type; switch(type) { case NSMoveToBezierPathElement: case NSLineToBezierPathElement: elments[i].points[0] = (*transformPointImp)(transform, transformPointSel, elments[i].points[0]); break; case NSCurveToBezierPathElement: elments[i].points[0] = (*transformPointImp)(transform, transformPointSel, elments[i].points[0]); elments[i].points[1] = (*transformPointImp)(transform, transformPointSel, elments[i].points[1]); elments[i].points[2] = (*transformPointImp)(transform, transformPointSel, elments[i].points[2]); break; case NSClosePathBezierPathElement: break; default: break; } } INVALIDATE_CACHE(); } - (void) appendBezierPath: (NSBezierPath *)aPath { PathElement elem; int i, count; if (![aPath isKindOfClass: isa]) { [super appendBezierPath: aPath]; return; } flat = flat && ((GSBezierPath*)aPath)->flat; count = [aPath elementCount]; for (i = 0; i < count; i++) { elem = GSIArrayItemAtIndex(((GSBezierPath*)aPath)->pathElements, i).ext; GSIArrayAddItem(pathElements, (GSIArrayItem)elem); } INVALIDATE_CACHE(); } // // NSCopying Protocol // - (id)copyWithZone:(NSZone *)zone { GSBezierPath *path = [super copyWithZone: zone]; path->pathElements = GSIArrayCopyWithZone(pathElements, zone); return path; } // // Hit detection // #define PMAX 10000 - (BOOL)containsPoint:(NSPoint)point { NSPoint draftPolygon[PMAX]; int pcount = 0; // Coordinates of the current point double cx, cy; // Coordinates of the last point double lx, ly; int i; int Rcross = 0; int Lcross = 0; NSBezierPathElement bpt; NSPoint p, pts[3]; double x, y, t, k = 0.25; int count = [self elementCount]; if(!count) return NO; if (!NSPointInRect(point, [self bounds])) return NO; // FIXME: This does not handle multiple segments! for(i = 0; i < count; i++) { bpt = [self elementAtIndex: i associatedPoints: pts]; if(bpt == NSMoveToBezierPathElement || bpt == NSLineToBezierPathElement) { draftPolygon[pcount].x = pts[0].x; draftPolygon[pcount].y = pts[0].y; pcount++; } else if(bpt == NSCurveToBezierPathElement) { if(pcount) { p.x = draftPolygon[pcount -1].x; p.y = draftPolygon[pcount -1].y; } else { p.x = pts[0].x; p.y = pts[0].y; } for(t = k; t <= 1+k; t += k) { x = (p.x+t*(-p.x*3+t*(3*p.x-p.x*t)))+ t*(3*pts[0].x+t*(-6*pts[0].x+pts[0].x*3*t))+ t*t*(pts[1].x*3-pts[1].x*3*t)+pts[2].x*t*t*t; y = (p.y+t*(-p.y*3+t*(3*p.y-p.y*t)))+ t*(3*pts[0].y+t*(-6*pts[0].y+pts[0].y*3*t))+ t*t*(pts[1].y*3-pts[1].y*3*t)+pts[2].y*t*t*t; draftPolygon[pcount].x = x; draftPolygon[pcount].y = y; pcount++; } } // Simple overflow check if (pcount == PMAX) return NO; } lx = draftPolygon[pcount - 1].x - point.x; ly = draftPolygon[pcount - 1].y - point.y; for(i = 0; i < pcount; i++) { cx = draftPolygon[i].x - point.x; cy = draftPolygon[i].y - point.y; if(cx == 0 && cy == 0) // on a vertex return NO; if((cy > 0) && !(ly > 0)) { if (((cx * ly - lx * cy) / (ly - cy)) > 0) Rcross++; } if((cy < 0 ) && !(ly < 0)) { if (((cx * ly - lx * cy) / (ly - cy)) < 0); Lcross++; } lx = cx; ly = cy; } if((Rcross % 2) != (Lcross % 2)) // On the border return NO; if((Rcross % 2) == 1) return YES; else return NO; } @end // GSBezierPath static void flatten(NSPoint coeff[], float flatness, NSBezierPath *path) { // Check if the Bezier path defined by the four points has the given flatness. // If not split it up in the middle and recurse. // Otherwise add the end point to the path. BOOL flat = YES; // This criteria for flatness is based on code from Libart which has the // following copyright: /* Libart_LGPL - library of basic graphic primitives * Copyright (C) 1998 Raph Levien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ double x1_0, y1_0; double x3_2, y3_2; double x3_0, y3_0; double z3_0_dot; double z1_dot, z2_dot; double z1_perp, z2_perp; double max_perp_sq; x3_0 = coeff[3].x - coeff[0].x; y3_0 = coeff[3].y - coeff[0].y; x3_2 = coeff[3].x - coeff[2].x; y3_2 = coeff[3].y - coeff[2].y; x1_0 = coeff[1].x - coeff[0].x; y1_0 = coeff[1].y - coeff[0].y; z3_0_dot = x3_0 * x3_0 + y3_0 * y3_0; if (z3_0_dot < 0.001) flat = YES; else { max_perp_sq = flatness * flatness * z3_0_dot; z1_perp = y1_0 * x3_0 - x1_0 * y3_0; if (z1_perp * z1_perp > max_perp_sq) flat = NO; else { z2_perp = y3_2 * x3_0 - x3_2 * y3_0; if (z2_perp * z2_perp > max_perp_sq) flat = NO; else { z1_dot = x1_0 * x3_0 + y1_0 * y3_0; if (z1_dot < 0 && z1_dot * z1_dot > max_perp_sq) flat = NO; else { z2_dot = x3_2 * x3_0 + y3_2 * y3_0; if (z2_dot < 0 && z2_dot * z2_dot > max_perp_sq) flat = NO; else { if ((z1_dot + z1_dot > z3_0_dot) || (z2_dot + z2_dot > z3_0_dot)) flat = NO; } } } } } if (!flat) { NSPoint bleft[4], bright[4]; bleft[0] = coeff[0]; bleft[1].x = (coeff[0].x + coeff[1].x) / 2; bleft[1].y = (coeff[0].y + coeff[1].y) / 2; bleft[2].x = (coeff[0].x + 2*coeff[1].x + coeff[2].x) / 4; bleft[2].y = (coeff[0].y + 2*coeff[1].y + coeff[2].y) / 4; bleft[3].x = (coeff[0].x + 3*(coeff[1].x + coeff[2].x) + coeff[3].x) / 8; bleft[3].y = (coeff[0].y + 3*(coeff[1].y + coeff[2].y) + coeff[3].y) / 8; bright[0].x = bleft[3].x; bright[0].y = bleft[3].y; bright[1].x = (coeff[3].x + 2*coeff[2].x + coeff[1].x) / 4; bright[1].y = (coeff[3].y + 2*coeff[2].y + coeff[1].y) / 4; bright[2].x = (coeff[3].x + coeff[2].x) / 2; bright[2].y = (coeff[3].y + coeff[2].y) / 2; bright[3] = coeff[3]; flatten(bleft, flatness, path); flatten(bright, flatness, path); } else { //[path lineToPoint: coeff[1]]; //[path lineToPoint: coeff[2]]; [path lineToPoint: coeff[3]]; } }