diff --git a/IJSVGExample/IJSVGExample.xcodeproj/project.xcworkspace/xcuserdata/curtishard.xcuserdatad/UserInterfaceState.xcuserstate b/IJSVGExample/IJSVGExample.xcodeproj/project.xcworkspace/xcuserdata/curtishard.xcuserdatad/UserInterfaceState.xcuserstate index fe0baa3..94ed568 100644 Binary files a/IJSVGExample/IJSVGExample.xcodeproj/project.xcworkspace/xcuserdata/curtishard.xcuserdatad/UserInterfaceState.xcuserstate and b/IJSVGExample/IJSVGExample.xcodeproj/project.xcworkspace/xcuserdata/curtishard.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/source/IJSVG.m b/source/IJSVG.m index 334453c..1d485c7 100644 --- a/source/IJSVG.m +++ b/source/IJSVG.m @@ -576,28 +576,8 @@ static NSColor * _baseColor = nil; // for this to work, we need to add the clip so when // drawing occurs, it doesnt go outside the path bounds [path.path addClip]; - - // apply any gradient fill transforms - for( IJSVGTransform * transform in path.gradientTransforms ) - { - [IJSVGTransform performTransform:transform - inContext:ref]; - } - - if( [path.fillGradient isKindOfClass:[IJSVGLinearGradient class]] ) - { - // linear gradient - NSGradient * gradient = [path.fillGradient gradient];; - [gradient drawInBezierPath:path.path - angle:path.fillGradient.angle]; - } else if( [path.fillGradient isKindOfClass:[IJSVGRadialGradient class]] ) - { - // radial gradient - // very rudimentary at the moment - IJSVGRadialGradient * radGrad = (IJSVGRadialGradient *)path.fillGradient; - [radGrad.gradient drawInBezierPath:path.path - relativeCenterPosition:NSZeroPoint]; - } + [path.fillGradient drawInContextRef:ref + path:path]; } CGContextRestoreGState(ref); } else { diff --git a/source/IJSVGColor.m b/source/IJSVGColor.m index 26837e2..a10c84c 100644 --- a/source/IJSVGColor.m +++ b/source/IJSVGColor.m @@ -221,6 +221,7 @@ static NSMutableDictionary * _colorTree = nil; + (NSColor *)colorFromPredefinedColorName:(NSString *)name { NSString * hex = nil; + name = [name.lowercaseString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; if( ( hex = [_colorTree objectForKey:name] ) == nil ) return nil; return [[self class] colorFromHEXString:hex diff --git a/source/IJSVGGradient.h b/source/IJSVGGradient.h index ad12ce0..8df8705 100644 --- a/source/IJSVGGradient.h +++ b/source/IJSVGGradient.h @@ -8,18 +8,28 @@ #import #import "IJSVGDef.h" +#import "IJSVGTransform.h" @interface IJSVGGradient : IJSVGDef { NSGradient * gradient; + CGGradientRef CGGradient; CGFloat angle; + CGPoint startPoint; + CGPoint endPoint; } @property ( nonatomic, retain ) NSGradient * gradient; @property ( nonatomic, assign ) CGFloat angle; +@property ( nonatomic, assign ) CGPoint startPoint; +@property ( nonatomic, assign ) CGPoint endPoint; +@property ( nonatomic, assign ) CGGradientRef CGGradient; + (CGFloat *)computeColorStopsFromString:(NSXMLElement *)element colors:(NSArray **)someColors; +- (CGGradientRef)CGGradient; +- (void)drawInContextRef:(CGContextRef)ctx + path:(IJSVGPath *)path; @end diff --git a/source/IJSVGGradient.m b/source/IJSVGGradient.m index 9f8f293..ff95bb0 100644 --- a/source/IJSVGGradient.m +++ b/source/IJSVGGradient.m @@ -10,12 +10,14 @@ @implementation IJSVGGradient -@synthesize gradient; -@synthesize angle; +@synthesize gradient, CGGradient; +@synthesize angle, startPoint, endPoint; - (void)dealloc { [gradient release], gradient = nil; + if( CGGradient != nil ) + CGGradientRelease(CGGradient); [super dealloc]; } @@ -23,6 +25,8 @@ { IJSVGGradient * clone = [super copyWithZone:zone]; clone.gradient = [[self.gradient copy] autorelease]; + clone.startPoint = self.startPoint; + clone.endPoint = self.endPoint; return clone; } @@ -55,6 +59,15 @@ NSColor * stopColor = [IJSVGColor colorFromHEXString:[[stop attributeForName:@"stop-color"] stringValue] alpha:stopOpacity]; + // no hex, grab it from predefined + if( stopColor == nil ) + { + stopColor = [IJSVGColor colorFromPredefinedColorName:[[stop attributeForName:@"stop-color"] stringValue]]; + if( stopColor != nil && stopOpacity != 1.f ) + stopColor = [IJSVGColor changeAlphaOnColor:stopColor + to:stopOpacity]; + } + // add it into the array if( stopColor != nil ) [(NSMutableArray *)colors addObject:stopColor]; @@ -84,4 +97,33 @@ return stopsParams; } +- (CGGradientRef)CGGradient +{ + // store it in the cache + if(CGGradient != nil) + return CGGradient; + + // actually create the gradient + NSInteger num = self.gradient.numberOfColorStops; + CGFloat * locations = malloc(sizeof(CGFloat)*num); + CFMutableArrayRef colors = CFArrayCreateMutable(kCFAllocatorDefault, (CFIndex)num, &kCFTypeArrayCallBacks); + for( NSInteger i = 0; i < num; i++ ) + { + NSColor * color; + [self.gradient getColor:&color + location:&locations[i] + atIndex:i]; + CFArrayAppendValue(colors, color.CGColor); + } + CGGradientRef result = CGGradientCreateWithColors(self.gradient.colorSpace.CGColorSpace, colors, locations); + CFRelease(colors); + free(locations); + return CGGradient = result; +} + +- (void)drawInContextRef:(CGContextRef)ctx + path:(IJSVGPath *)path +{ +} + @end diff --git a/source/IJSVGLinearGradient.h b/source/IJSVGLinearGradient.h index 5601bd0..57c8d7d 100644 --- a/source/IJSVGLinearGradient.h +++ b/source/IJSVGLinearGradient.h @@ -12,6 +12,8 @@ @interface IJSVGLinearGradient : IJSVGGradient + (NSGradient *)parseGradient:(NSXMLElement *)element - gradient:(IJSVGLinearGradient *)aGradient; + gradient:(IJSVGLinearGradient *)aGradient + startPoint:(CGPoint *)startPoint + endPoint:(CGPoint *)endPoint; @end diff --git a/source/IJSVGLinearGradient.m b/source/IJSVGLinearGradient.m index d7bc0a5..95e15b2 100644 --- a/source/IJSVGLinearGradient.m +++ b/source/IJSVGLinearGradient.m @@ -13,6 +13,8 @@ + (NSGradient *)parseGradient:(NSXMLElement *)element gradient:(IJSVGLinearGradient *)aGradient + startPoint:(CGPoint *)startPoint + endPoint:(CGPoint *)endPoint { // assume its a vertical / horizonal CGFloat x1 = [[[element attributeForName:@"x1"] stringValue] floatValue]; @@ -20,6 +22,9 @@ CGFloat y1 = [[[element attributeForName:@"y1"] stringValue] floatValue]; CGFloat y2 = [[[element attributeForName:@"y2"] stringValue] floatValue]; + *startPoint = CGPointMake( x1, y1); + *endPoint = CGPointMake( x2, y2); + // horizontal if( y1 == y2 && x1 != x2 ) aGradient.angle = 0.f; @@ -49,4 +54,27 @@ return grad; } +- (void)drawInContextRef:(CGContextRef)ctx + path:(IJSVGPath *)path +{ + // grab the start and end point + CGPoint aStartPoint = self.startPoint; + CGPoint aEndPoint = self.endPoint; + + // convert the nsgradient to a CGGradient + CGGradientRef gRef = [self CGGradient]; + + // apply transform for each point + for( IJSVGTransform * transform in self.transforms ) + { + CGAffineTransform trans = transform.CGAffineTransform; + aStartPoint = CGPointApplyAffineTransform(aStartPoint, trans); + aEndPoint = CGPointApplyAffineTransform(aEndPoint, trans); + } + + // draw the gradient + CGGradientDrawingOptions opt = kCGGradientDrawsBeforeStartLocation|kCGGradientDrawsAfterEndLocation; + CGContextDrawLinearGradient(ctx, gRef, aStartPoint, aEndPoint, opt); +} + @end diff --git a/source/IJSVGNode.h b/source/IJSVGNode.h index 6e7881c..78b0558 100644 --- a/source/IJSVGNode.h +++ b/source/IJSVGNode.h @@ -88,7 +88,6 @@ static CGFloat IJSVGInheritedFloatValue = -99.9999991; IJSVGNode * parentNode; IJSVGGroup * clipPath; NSArray * transforms; - NSArray * gradientTransforms; IJSVGWindingRule windingRule; IJSVGLineCapStyle lineCapStyle; @@ -120,7 +119,6 @@ static CGFloat IJSVGInheritedFloatValue = -99.9999991; @property ( nonatomic, assign ) IJSVGLineCapStyle lineCapStyle; @property ( nonatomic, assign ) IJSVGLineJoinStyle lineJoinStyle; @property ( nonatomic, retain ) NSArray * transforms; -@property ( nonatomic, retain ) NSArray * gradientTransforms; @property ( nonatomic, retain ) IJSVGDef * def; @property ( nonatomic, retain ) IJSVGGradient * fillGradient; @property ( nonatomic, assign ) CGFloat * strokeDashArray; diff --git a/source/IJSVGNode.m b/source/IJSVGNode.m index 7c238d1..88dabd1 100644 --- a/source/IJSVGNode.m +++ b/source/IJSVGNode.m @@ -28,7 +28,6 @@ @synthesize identifier; @synthesize parentNode; @synthesize transforms; -@synthesize gradientTransforms; @synthesize windingRule; @synthesize def; @synthesize fillGradient; @@ -46,7 +45,6 @@ [unicode release], unicode = nil; [fillGradient release], fillGradient = nil; [transforms release], transforms = nil; - [gradientTransforms release], gradientTransforms = nil; [fillColor release], fillColor = nil; [strokeColor release], strokeColor = nil; [identifier release], identifier = nil; @@ -127,7 +125,6 @@ self.usesDefaultFillColor = node.usesDefaultFillColor; self.transforms = node.transforms; - self.gradientTransforms = node.gradientTransforms; self.def = node.def; self.windingRule = node.windingRule; self.lineCapStyle = node.lineCapStyle; diff --git a/source/IJSVGParser.m b/source/IJSVGParser.m index af7bc41..3c3aeee 100644 --- a/source/IJSVGParser.m +++ b/source/IJSVGParser.m @@ -117,8 +117,7 @@ *error = [[[NSError alloc] initWithDomain:IJSVGErrorDomain code:code userInfo:nil] autorelease]; - if( _document != nil ) - [_document release], _document = nil; + [_document release], _document = nil; [self release], self = nil; return nil; } @@ -130,7 +129,8 @@ return YES; // check the viewbox - if( NSEqualRects( self.viewBox, NSZeroRect ) ) + if( NSEqualRects( self.viewBox, NSZeroRect ) || + self.size.width == 0 || self.size.height == 0 ) { if( error != NULL ) *error = [[[NSError alloc] initWithDomain:IJSVGErrorDomain @@ -165,15 +165,24 @@ // there is no view box so find the width and height CGFloat w = [[[svgElement attributeForName:@"width"] stringValue] floatValue]; CGFloat h = [[[svgElement attributeForName:@"height"] stringValue] floatValue]; + if( h == 0.f && w != 0.f ) + h = w; + else if( w == 0.f && h != 0.f ) + w = h; viewBox = NSMakeRect( 0.f, 0.f, w, h ); } + // parse the width and height.... CGFloat w = [[[svgElement attributeForName:@"width"] stringValue] floatValue]; CGFloat h = [[[svgElement attributeForName:@"height"] stringValue] floatValue]; if( w == 0.f && h == 0.f ) { w = viewBox.size.width; h = viewBox.size.height; + } else if( w == 0 && h != 0.f ) { + w = viewBox.size.width; + } else if( h == 0 && w != 0.f ) { + h = viewBox.size.height; } proposedViewSize = NSMakeSize( w, h ); @@ -369,7 +378,7 @@ if( defID != nil ) { IJSVGGradient * grad = (IJSVGGradient *)[node defForID:defID]; node.fillGradient = grad; - node.gradientTransforms = grad.gradientTransforms; + grad.transforms = grad.transforms; } else { // change the fill color over if its allowed node.fillColor = [IJSVGColor colorFromString:[fillAttribute stringValue]]; @@ -391,9 +400,9 @@ { NSMutableArray * tran = [[[NSMutableArray alloc] init] autorelease]; [tran addObjectsFromArray:[IJSVGTransform transformsForString:[gradTransformAttribute stringValue]]]; - if( node.gradientTransforms != nil ) - [tran addObjectsFromArray:node.gradientTransforms]; - node.gradientTransforms = tran; + if( node.transforms != nil ) + [tran addObjectsFromArray:node.transforms]; + node.transforms = tran; } // winding rule @@ -437,7 +446,7 @@ if( defID != nil ) { IJSVGGradient * grad = (IJSVGGradient *)[node defForID:defID]; node.fillGradient = grad; - node.gradientTransforms = grad.gradientTransforms; + node.transforms = grad.transforms; } } else { if( [IJSVGColor computeColor:fill] != nil ) @@ -795,21 +804,34 @@ IJSVGLinearGradient * grad = [[[IJSVGLinearGradient alloc] init] autorelease]; grad.type = aType; [grad applyPropertiesFromNode:node]; + grad.gradient = [[[(IJSVGGradient *)node gradient] copy] autorelease]; + CGPoint startPoint, endPoint; [IJSVGLinearGradient parseGradient:element - gradient:grad]; + gradient:grad + startPoint:&startPoint + endPoint:&endPoint]; [self _parseElementForCommonAttributes:element node:grad]; + grad.startPoint = startPoint; + grad.endPoint = endPoint; [parentGroup addDef:grad]; continue; } IJSVGLinearGradient * gradient = [[[IJSVGLinearGradient alloc] init] autorelease]; gradient.type = aType; + + CGPoint startPoint, endPoint; gradient.gradient = [IJSVGLinearGradient parseGradient:element - gradient:gradient]; + gradient:gradient + startPoint:&startPoint + endPoint:&endPoint]; + [self _parseElementForCommonAttributes:element node:gradient]; + gradient.startPoint = startPoint; + gradient.endPoint = endPoint; [parentGroup addDef:gradient]; continue; } @@ -827,18 +849,30 @@ grad.type = aType; [grad applyPropertiesFromNode:node]; grad.gradient = [[[(IJSVGGradient *)node gradient] copy] autorelease]; + + CGPoint startPoint, endPoint; [IJSVGRadialGradient parseGradient:element - gradient:grad]; + gradient:grad + startPoint:&startPoint + endPoint:&endPoint]; [self _parseElementForCommonAttributes:element node:grad]; + grad.startPoint = startPoint; + grad.endPoint = endPoint; [parentGroup addDef:grad]; continue; } IJSVGRadialGradient * gradient = [[[IJSVGRadialGradient alloc] init] autorelease]; gradient.type = aType; + + CGPoint startPoint, endPoint; gradient.gradient = [IJSVGRadialGradient parseGradient:element - gradient:gradient]; + gradient:gradient + startPoint:&startPoint + endPoint:&endPoint]; + gradient.startPoint = startPoint; + gradient.endPoint = endPoint; [self _parseElementForCommonAttributes:element node:gradient]; [parentGroup addDef:gradient]; diff --git a/source/IJSVGRadialGradient.h b/source/IJSVGRadialGradient.h index 17e4f68..b9613c2 100644 --- a/source/IJSVGRadialGradient.h +++ b/source/IJSVGRadialGradient.h @@ -26,6 +26,8 @@ @property ( nonatomic, assign ) CGFloat radius; + (NSGradient *)parseGradient:(NSXMLElement *)element - gradient:(IJSVGRadialGradient *)gradient; + gradient:(IJSVGRadialGradient *)gradient + startPoint:(CGPoint *)startPoint + endPoint:(CGPoint *)endPoint; @end diff --git a/source/IJSVGRadialGradient.m b/source/IJSVGRadialGradient.m index a3e4635..036f004 100644 --- a/source/IJSVGRadialGradient.m +++ b/source/IJSVGRadialGradient.m @@ -16,11 +16,25 @@ @synthesize fy; @synthesize radius; +- (id)copyWithZone:(NSZone *)zone +{ + IJSVGRadialGradient * grad = [super copyWithZone:zone]; + grad.fx = self.fx; + grad.fy = self.fy; + grad.cx = self.cx; + grad.cy = self.cy; + grad.radius = self.radius; + grad.startPoint = self.startPoint; + grad.endPoint = self.endPoint; + return grad; +} + + (NSGradient *)parseGradient:(NSXMLElement *)element gradient:(IJSVGRadialGradient *)gradient + startPoint:(CGPoint *)startPoint + endPoint:(CGPoint *)endPoint { - // assume its a vertical / horizonal gradient.cx = [[[element attributeForName:@"cx"] stringValue] floatValue]/100; gradient.cy = [[[element attributeForName:@"cy"] stringValue] floatValue]/100; @@ -43,6 +57,9 @@ if( gradient.gradient != nil ) return nil; + *startPoint = CGPointMake(gradient.cx, gradient.cy); + *endPoint = CGPointMake(gradient.fx, gradient.fy); + NSArray * colors = nil; CGFloat * colorStops = [[self class] computeColorStopsFromString:element colors:&colors]; NSGradient * ret = [[[NSGradient alloc] initWithColors:colors @@ -52,4 +69,28 @@ return ret; } +- (void)drawInContextRef:(CGContextRef)ctx + path:(IJSVGPath *)path +{ + // apply any transforms to the current context + CGRect bounds = path.path.bounds; + CGContextTranslateCTM( ctx, bounds.origin.x, bounds.origin.y ); + + for( IJSVGTransform * transform in self.transforms ) + { + transform = [[transform copy] autorelease]; + [transform recalculateWithBounds:bounds]; + CGAffineTransform trans = transform.CGAffineTransform; + CGContextConcatCTM(ctx, trans); + } + + CGPoint sp = CGPointMake( bounds.size.width*self.cx, bounds.size.height*self.cy); + CGPoint ep = CGPointMake( bounds.size.width*self.fx, bounds.size.height*self.fy); + + // draw the gradient + CGGradientDrawingOptions options = kCGGradientDrawsBeforeStartLocation|kCGGradientDrawsAfterEndLocation; + CGGradientRef grad = self.CGGradient; + CGContextDrawRadialGradient(ctx, grad, sp, 0.f, ep, bounds.size.height*self.radius, options); +} + @end diff --git a/source/IJSVGTransform.h b/source/IJSVGTransform.h index d00b814..99cedc7 100644 --- a/source/IJSVGTransform.h +++ b/source/IJSVGTransform.h @@ -35,5 +35,7 @@ typedef NS_OPTIONS( NSInteger, IJSVGTransformCommand ) { + (void)performTransform:(IJSVGTransform *)transform inContext:(CGContextRef)context; + (NSBezierPath *)transformedPath:(IJSVGPath *)path; +- (CGAffineTransform)CGAffineTransform; +- (void)recalculateWithBounds:(CGRect)bounds; @end diff --git a/source/IJSVGTransform.m b/source/IJSVGTransform.m index 9fc6e07..8cf1a85 100644 --- a/source/IJSVGTransform.m +++ b/source/IJSVGTransform.m @@ -21,6 +21,32 @@ [super dealloc]; } +- (id)copyWithZone:(NSZone *)zone +{ + IJSVGTransform * trans = [[[self class] alloc] init]; + trans.command = self.command; + trans.parameters = (CGFloat*)malloc(sizeof(CGFloat)*self.parameterCount); + trans.sort = sort; + trans.parameterCount = self.parameterCount; + memcpy( trans.parameters, self.parameters, sizeof(CGFloat)*self.parameterCount); + return trans; +} + +- (void)recalculateWithBounds:(CGRect)bounds +{ + CGFloat max = bounds.size.width>bounds.size.height?bounds.size.width:bounds.size.height; + switch (self.command) { + case IJSVGTransformCommandRotate: { + if( self.parameterCount == 1 ) + return; + self.parameters[1] = self.parameters[1]*max; + self.parameters[2] = self.parameters[2]*max; + } + default: + return; + } +} + + (IJSVGTransformCommand)commandForCommandString:(NSString *)str { if( [str isEqualToString:@"matrix"] ) @@ -135,6 +161,16 @@ case IJSVGTransformCommandRotate: { if( transform.parameterCount == 1 ) [at rotateByDegrees:transform.parameters[0]]; + else { + CGFloat centerX = transform.parameters[1]; + CGFloat centerY = transform.parameters[2]; + CGFloat angle = transform.parameters[0]*(M_PI/180.f); + [at translateXBy:centerX + yBy:centerY]; + [at rotateByRadians:angle]; + [at translateXBy:-1.f*centerX + yBy:-1.f*centerY]; + } break; } @@ -187,8 +223,17 @@ case IJSVGTransformCommandRotate: { // these are in radians, not degrees if( transform.parameterCount == 1 ) + { CGContextRotateCTM( context, (transform.parameters[0] / 180) * M_PI); - // need support for rotate around a point + } else { + // need support for rotate around a point + CGFloat centerX = transform.parameters[1]; + CGFloat centerY = transform.parameters[2]; + CGFloat angle = transform.parameters[0]*(M_PI/180.f); + CGContextTranslateCTM(context, centerX, centerY); + CGContextRotateCTM(context, angle); + CGContextTranslateCTM(context, -1.f*centerX, -1.f*centerY); + } } // do nothing @@ -199,4 +244,57 @@ } +- (CGAffineTransform)CGAffineTransform +{ + switch(self.command) + { + // matrix + case IJSVGTransformCommandMatrix: { + return CGAffineTransformMake( self.parameters[0], + self.parameters[1], + self.parameters[2], + self.parameters[3], + self.parameters[4], + self.parameters[5]); + } + + // translate + case IJSVGTransformCommandTranslate: { + if(self.parameterCount == 1) + return CGAffineTransformMakeTranslation( self.parameters[0], 0 ); + return CGAffineTransformMakeTranslation(self.parameters[0], self.parameters[1]); + } + + // scale + case IJSVGTransformCommandScale: { + if(self.parameterCount == 1) + return CGAffineTransformMakeScale( self.parameters[0], self.parameters[0]); + return CGAffineTransformMakeScale( self.parameters[0], self.parameters[1]); + } + + // rotate + case IJSVGTransformCommandRotate: { + if(self.parameterCount == 1) + return CGAffineTransformMakeRotation((self.parameters[0]/180) * M_PI); + else { + CGFloat centerX = self.parameters[1]; + CGFloat centerY = self.parameters[2]; + CGFloat angle = self.parameters[0]*(M_PI/180.f); + CGAffineTransform def = CGAffineTransformIdentity; + def = CGAffineTransformTranslate(def, centerX, centerY); + def = CGAffineTransformRotate(def, angle); + def = CGAffineTransformTranslate(def, -1.f*centerX, -1.f*centerY); + return def; + } + break; + } + + // do nothing + case IJSVGTransformCommandNotImplemented: { + return CGAffineTransformIdentity; + } + } + return CGAffineTransformIdentity; +} + @end