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 81be11a..ab7ee28 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 18345af..81554c8 100644 --- a/source/IJSVG.m +++ b/source/IJSVG.m @@ -198,7 +198,7 @@ static NSColor * _baseColor = nil; } - (void)_drawInRect:(NSRect)rect - context:(CGContextRef)ref + context:(CGContextRef)ref { // prep for draw... [self _beginDraw:rect]; @@ -211,6 +211,20 @@ static NSColor * _baseColor = nil; // to translate the context so its centered CGFloat tX = round(rect.size.width/2-(_group.size.width/2)*_scale); CGFloat tY = round(rect.size.height/2-(_group.size.height/2)*_scale); + tX -= _group.viewBox.origin.x*_scale; + tY -= _group.viewBox.origin.y*_scale; + + // we also need to calculate the viewport so we can clip + // the drawing if needed + NSRect viewPort = NSZeroRect; + viewPort.origin.x = tX; + viewPort.origin.y = tY; + viewPort.size.width = _group.size.width*_scale; + viewPort.size.height = _group.size.height*_scale; + + // clip any drawing to the view port + [[NSBezierPath bezierPathWithRect:viewPort] addClip]; + CGContextTranslateCTM( ref, tX, tY ); CGContextScaleCTM( ref, _scale, _scale ); @@ -225,6 +239,12 @@ static NSColor * _baseColor = nil; } CGContextRestoreGState(ref); + +} + +- (NSRect)computedViewPort +{ + return NSMakeRect( 0.f, 0.f, 200.f, 200.f); } - (void)_recursiveColors:(IJSVGGroup *)group @@ -366,6 +386,7 @@ static NSColor * _baseColor = nil; // scale it CGContextSetAlpha( context, opacity ); + CGContextTranslateCTM( context, node.x, node.y); // perform any transforms for( IJSVGTransform * transform in node.transforms ) @@ -373,6 +394,7 @@ static NSColor * _baseColor = nil; [IJSVGTransform performTransform:transform inContext:context]; } + } - (void)_drawPath:(IJSVGPath *)path @@ -434,7 +456,7 @@ static NSColor * _baseColor = nil; // use that one instead CGFloat lineWidth = 1.f; if( path.strokeWidth != 0.f ) - lineWidth = path.strokeWidth; + lineWidth = fabs(path.strokeWidth); [path.path setLineCapStyle:(NSLineCapStyle)path.lineCapStyle]; [path.strokeColor setStroke]; [path.path setLineWidth:lineWidth]; diff --git a/source/IJSVGCache.m b/source/IJSVGCache.m index f9c8f55..049fdf4 100644 --- a/source/IJSVGCache.m +++ b/source/IJSVGCache.m @@ -12,7 +12,7 @@ @implementation IJSVGCache // 5MB -#define MAX_CACHE_SIZE 5000000 +#define MAX_CACHE_SIZE 10000000 static NSCache * _cache = nil; static BOOL _enabled = YES; diff --git a/source/IJSVGColor.h b/source/IJSVGColor.h index af16d89..fd84be2 100644 --- a/source/IJSVGColor.h +++ b/source/IJSVGColor.h @@ -160,6 +160,7 @@ typedef NS_ENUM( NSInteger, IJSVGPredefinedColor ) { @interface IJSVGColor : NSObject ++ (NSColor *)computeColor:(id)colour; + (NSColor *)colorFromString:(NSString *)string; + (NSColor *)colorFromHEXString:(NSString *)string alpha:(CGFloat)alpha; diff --git a/source/IJSVGColor.m b/source/IJSVGColor.m index 235ca90..f148dd2 100644 --- a/source/IJSVGColor.m +++ b/source/IJSVGColor.m @@ -175,6 +175,13 @@ static NSMutableDictionary * _colorTree = nil; }); } ++ (NSColor *)computeColor:(id)colour +{ + if( [colour isKindOfClass:[NSColor class]] ) + return colour; + return nil; +} + + (NSColor *)colorFromString:(NSString *)string { NSColor * color = [[self class] colorFromPredefinedColorName:string]; diff --git a/source/IJSVGCommand.m b/source/IJSVGCommand.m index 04f7157..2de487c 100644 --- a/source/IJSVGCommand.m +++ b/source/IJSVGCommand.m @@ -44,7 +44,7 @@ static NSMutableDictionary * _classes = nil; type = [IJSVGUtils typeForCommandString:self.command]; commandClass = [[self class] commandClassForCommandLetter:self.command]; parameters = [IJSVGUtils commandParameters:str - count:¶meterCount]; + count:¶meterCount]; requiredParameters = [self.commandClass requiredParameterCount]; // now work out the sets of parameters we have diff --git a/source/IJSVGCommandMove.m b/source/IJSVGCommandMove.m index 53b7536..3b1eac0 100644 --- a/source/IJSVGCommandMove.m +++ b/source/IJSVGCommandMove.m @@ -7,6 +7,7 @@ // #import "IJSVGCommandMove.h" +#import "IJSVGCommandLineTo.h" @implementation IJSVGCommandMove @@ -28,6 +29,19 @@ type:(IJSVGCommandType)type path:(IJSVGPath *)path { + // move to's allow more then one move to, but if there are more then one, + // we need to run the line to instead...who knew! + if( command.commandClass == [self class]) + { + [IJSVGCommandLineTo runWithParams:params + paramCount:count + previousCommand:command + type:type + path:path]; + return; + } + + // actual move to command if( type == IJSVGCommandTypeAbsolute ) { [[path currentSubpath] moveToPoint:NSMakePoint( params[0], params[1])]; diff --git a/source/IJSVGGroup.m b/source/IJSVGGroup.m index eaf99ee..65008d9 100644 --- a/source/IJSVGGroup.m +++ b/source/IJSVGGroup.m @@ -25,9 +25,16 @@ return self; } +- (void)prepareFromCopy +{ + children = [[NSMutableArray alloc] init]; +} + - (id)copyWithZone:(NSZone *)zone { - IJSVGGroup * node = [[self class] allocWithZone:zone]; + IJSVGGroup * node = [super copyWithZone:zone]; + [node prepareFromCopy]; + for( IJSVGNode * childNode in self.children ) { childNode = [[childNode copy] autorelease]; @@ -44,7 +51,8 @@ - (void)addChild:(id)child { - [children addObject:child]; + if( child != nil ) + [children addObject:child]; } - (NSArray *)children @@ -52,4 +60,9 @@ return children; } +- (NSString *)description +{ + return [NSString stringWithFormat:@"%@ - %ld",[super description],self.children.count]; +} + @end diff --git a/source/IJSVGNode.m b/source/IJSVGNode.m index 33d6995..422bd94 100644 --- a/source/IJSVGNode.m +++ b/source/IJSVGNode.m @@ -137,6 +137,9 @@ self.opacity = 0.f; self.fillOpacity = 1.f; self.strokeOpacity = 1.f; + self.strokeWidth = IJSVGInheritedFloatValue; + self.windingRule = IJSVGWindingRuleInherit; + self.lineCapStyle = IJSVGWindingRuleInherit; if( flag ) def = [[IJSVGDef alloc] init]; } diff --git a/source/IJSVGParser.m b/source/IJSVGParser.m index 136a073..6e62fa0 100644 --- a/source/IJSVGParser.m +++ b/source/IJSVGParser.m @@ -154,6 +154,15 @@ node:(IJSVGNode *)node { + // x and y + NSXMLNode * xAttribute = [element attributeForName:@"x"]; + if( xAttribute != nil ) + node.x = [[xAttribute stringValue] floatValue]; + + NSXMLNode * yAttribute = [element attributeForName:@"y"]; + if( yAttribute ) + node.y = [[yAttribute stringValue] floatValue]; + // any clippath? NSXMLNode * clipPathAttribute = [element attributeForName:@"clip-path"]; if( clipPathAttribute != nil ) @@ -217,15 +226,12 @@ if( defID != nil ) node.fillGradient = (IJSVGGradient *)[node defForID:defID]; else { - // change the fill color over if its allowed + // change the fill color over if its allowed node.fillColor = [IJSVGColor colorFromString:[fillAttribute stringValue]]; if( node.fillOpacity != 1.f ) node.fillColor = [IJSVGColor changeAlphaOnColor:node.fillColor to:node.fillOpacity]; } - } else { - node.usesDefaultFillColor = YES; - node.fillColor = [NSColor blackColor]; } // stroke width @@ -241,7 +247,13 @@ // transforms NSXMLNode * transformAttribute = [element attributeForName:@"transform"]; if( transformAttribute != nil ) - node.transforms = [IJSVGTransform transformsForString:[transformAttribute stringValue]]; + { + NSMutableArray * tran = [[[NSMutableArray alloc] init] autorelease]; + [tran addObjectsFromArray:[IJSVGTransform transformsForString:[transformAttribute stringValue]]]; + if( node.transforms != nil ) + [tran addObjectsFromArray:node.transforms]; + node.transforms = tran; + } // winding rule NSXMLNode * windingRuleAttribute = [element attributeForName:@"fill-rule"]; @@ -254,12 +266,98 @@ // width NSXMLNode * widthAttribute = [element attributeForName:@"width"]; if( widthAttribute != nil ) - node.width = [IJSVGUtils floatValue:[widthAttribute stringValue]]; + { + if( [[widthAttribute stringValue] isEqualToString:@"100%"] ) + node.width = self.viewBox.size.width; + else + node.width = [IJSVGUtils floatValue:[widthAttribute stringValue]]; + } // height NSXMLNode * heightAttribute = [element attributeForName:@"height"]; if( heightAttribute != nil ) - node.height = [IJSVGUtils floatValue:[heightAttribute stringValue]]; + { + if( [[heightAttribute stringValue] isEqualToString:@"100%"] ) + node.height = self.viewBox.size.height; + else + node.height = [IJSVGUtils floatValue:[heightAttribute stringValue]]; + } + + // now we need to work out if there is any style...apparently this is a thing now, + // people use the style attribute... -_- + // style + NSXMLNode * styleNode = [element attributeForName:@"style"]; + if( styleNode != nil ) + { + IJSVGStyle * style = [IJSVGStyle parseStyleString:[styleNode stringValue]]; + + // fill color + NSColor * fill = nil; + if( ( fill = [style property:@"fill"] ) != nil ) + { + if( [fill isKindOfClass:[NSString class]] ) + { + NSString * defID = [IJSVGUtils defURL:(NSString *)fill]; + if( defID != nil ) + node.fillGradient = (IJSVGGradient *)[node defForID:defID]; + } else { + if( [IJSVGColor computeColor:fill] != nil ) + node.fillColor = fill; + } + } + + // fill opacity + NSNumber * num = nil; + if( ( num = [style property:@"fill-opacity"] ) != nil ) + node.fillOpacity = num.floatValue; + + // stroke colour + NSColor * stroke = nil; + if( ( stroke = [style property:@"stroke"] ) != nil ) + { + if( [IJSVGColor computeColor:stroke] ) + node.strokeColor = stroke; + } + + // stroke width + if( [style property:@"stroke-width"] != 0 ) + node.strokeWidth = [[style property:@"stroke-width"] floatValue]; + + // line cap style + if( [style property:@"stroke-linecap"] != nil ) + node.lineCapStyle = [IJSVGUtils lineCapStyleForString:[style property:@"stroke-line-cap"]]; + + // opacity + if( [style property:@"opacity"] != nil ) + node.opacity = [[style property:@"opacity"] floatValue]; + + // stroke opacity + if( [style property:@"stroke-opacity"] != nil ) + node.strokeOpacity = [[style property:@"stroke-opacity"] floatValue]; + + // dash + if( [style property:@"stroke-dasharray"] != nil ) + { + id prop = [style property:@"stroke-dasharray"]; + if( [prop isKindOfClass:[NSString class]] ) + { + NSInteger paramCount = 0; + CGFloat * params = [IJSVGUtils commandParameters:prop + count:¶mCount]; + node.strokeDashArray = params; + node.strokeDashArrayCount = paramCount; + } + } + + // dash offset + if( [style property:@"stroke-dashoffset"] != nil ) + node.strokeDashOffset = [[style property:@"stroke-dashoffset"] floatValue]; + + // winding rule + if( [style property:@"fill-rule"] != nil ) + node.windingRule = [IJSVGUtils windingRuleForString:[style property:@"fill-rule"]]; + + } } @@ -276,16 +374,16 @@ default: case IJSVGNodeTypeNotFound: continue; - - // def + + // def case IJSVGNodeTypeDef: { [self _parseBlock:element intoGroup:parentGroup def:YES]; continue; } - - // group + + // group case IJSVGNodeTypeGroup: { IJSVGGroup * group = [[[IJSVGGroup alloc] init] autorelease]; group.type = aType; @@ -305,12 +403,12 @@ def:NO]; // could be defined - if( flag ) + if( flag || [element attributeForName:@"id"] != nil ) [parentGroup addDef:group]; continue; } - // path + // path case IJSVGNodeTypePath: { IJSVGPath * path = [[[IJSVGPath alloc] init] autorelease]; path.type = aType; @@ -327,12 +425,12 @@ [parentGroup addChild:path]; // could be defined - if( flag ) + if( flag || [element attributeForName:@"id"] != nil ) [parentGroup addDef:path]; continue; } - // polygon + // polygon case IJSVGNodeTypePolygon: { IJSVGPath * path = [[[IJSVGPath alloc] init] autorelease]; path.type = aType; @@ -349,12 +447,12 @@ [parentGroup addChild:path]; // could be defined - if( flag ) + if( flag || [element attributeForName:@"id"] != nil ) [parentGroup addDef:path]; continue; } - - // polyline + + // polyline case IJSVGNodeTypePolyline: { IJSVGPath * path = [[[IJSVGPath alloc] init] autorelease]; path.type = aType; @@ -371,12 +469,12 @@ [parentGroup addChild:path]; // could be defined - if( flag ) + if( flag || [element attributeForName:@"id"] != nil ) [parentGroup addDef:path]; continue; } - // rect + // rect case IJSVGNodeTypeRect: { IJSVGPath * path = [[[IJSVGPath alloc] init] autorelease]; path.type = aType; @@ -384,21 +482,22 @@ path.parentNode = parentGroup; // find common attributes - [self _parseElementForCommonAttributes:element - node:path]; [self _parseRect:element intoPath:path]; + [self _parseElementForCommonAttributes:element + node:path]; + if( !flag ) [parentGroup addChild:path]; // could be defined - if( flag ) + if( flag || [element attributeForName:@"id"] != nil ) [parentGroup addDef:path]; continue; } - // line + // line case IJSVGNodeTypeLine: { IJSVGPath * path = [[[IJSVGPath alloc] init] autorelease]; path.type = aType; @@ -413,12 +512,12 @@ [parentGroup addChild:path]; // could be defined - if( flag ) + if( flag || [element attributeForName:@"id"] != nil ) [parentGroup addDef:path]; continue; } - - // circle + + // circle case IJSVGNodeTypeCircle: { IJSVGPath * path = [[[IJSVGPath alloc] init] autorelease]; path.type = aType; @@ -435,12 +534,12 @@ [parentGroup addChild:path]; // could be defined - if( flag ) + if( flag || [element attributeForName:@"id"] != nil) [parentGroup addDef:path]; continue; } - // ellipse + // ellipse case IJSVGNodeTypeEllipse: { IJSVGPath * path = [[[IJSVGPath alloc] init] autorelease]; path.type = aType; @@ -457,16 +556,34 @@ [parentGroup addChild:path]; // could be defined - if( flag ) + if( flag || [element attributeForName:@"id"] != nil ) [parentGroup addDef:path]; continue; } - // use + // use case IJSVGNodeTypeUse: { + NSString * xlink = [[element attributeForName:@"xlink:href"] stringValue]; NSString * xlinkID = [xlink substringFromIndex:1]; IJSVGNode * node = [parentGroup defForID:xlinkID]; + + // could be an alias, aswell as a def... + if( [element attributeForName:@"id"] != nil ) + { + IJSVGNode * theNode = [[node copy] autorelease]; + theNode.parentNode = parentGroup; + + // grab common attributes + [self _parseElementForCommonAttributes:element + node:theNode]; + + // add them + [parentGroup addDef:theNode]; + [parentGroup addChild:theNode]; + continue; + } + if( node != nil ) { // copy the node @@ -480,9 +597,10 @@ } continue; } - - // linear gradient + + // linear gradient case IJSVGNodeTypeLinearGradient: { + IJSVGLinearGradient * gradient = [[[IJSVGLinearGradient alloc] init] autorelease]; gradient.type = aType; gradient.gradient = [IJSVGLinearGradient parseGradient:element @@ -493,7 +611,7 @@ continue; } - // radial gradient + // radial gradient case IJSVGNodeTypeRadialGradient: { IJSVGRadialGradient * gradient = [[[IJSVGRadialGradient alloc] init] autorelease]; gradient.type = aType; @@ -505,8 +623,9 @@ continue; } - // clippath + // clippath case IJSVGNodeTypeClipPath: { + IJSVGGroup * group = [[[IJSVGGroup alloc] init] autorelease]; group.type = aType; group.name = subName; @@ -537,8 +656,6 @@ // invalid command if( command == nil || command.length == 0 ) return; - - command = [IJSVGUtils cleanCommandString:command]; NSRegularExpression * exp = [IJSVGUtils commandNameRegex]; __block NSString * previousCommand = nil; __block NSTextCheckingResult * previousMatch = nil; @@ -547,22 +664,22 @@ options:0 range:NSMakeRange( 0, command.length ) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) - { - @autoreleasepool { - if( previousMatch != nil ) - { - NSUInteger length = result.range.location - previousMatch.range.location; - NSString * commandString = [command substringWithRange:NSMakeRange(previousMatch.range.location,length)]; - [self _parseCommandString:commandString - previousCommand:previousCommand - intoPath:path]; - [previousCommand release], previousCommand = nil; - previousCommand = [commandString copy]; - [previousMatch release], previousMatch = nil; - } - } - previousMatch = [result retain]; - }]; + { + @autoreleasepool { + if( previousMatch != nil ) + { + NSUInteger length = result.range.location - previousMatch.range.location; + NSString * commandString = [command substringWithRange:NSMakeRange(previousMatch.range.location,length)]; + [self _parseCommandString:commandString + previousCommand:previousCommand + intoPath:path]; + [previousCommand release], previousCommand = nil; + previousCommand = [commandString copy]; + [previousMatch release], previousMatch = nil; + } + } + previousMatch = [result retain]; + }]; NSRange range = NSMakeRange(previousMatch.range.location, command.length-previousMatch.range.location); [self _parseCommandString:[command substringWithRange:range] @@ -594,7 +711,7 @@ for( IJSVGCommand * subCommand in [command subCommands] ) { [subCommand.commandClass runWithParams:subCommand.parameters - paramCount:subCommand.parameterCount + paramCount:subCommand.parameterCount previousCommand:preCommand type:subCommand.type path:path]; @@ -681,17 +798,31 @@ [self _parsePathCommandData:str intoPath:path]; free(params); - + } - (void)_parseRect:(NSXMLElement *)element intoPath:(IJSVGPath *)path { + CGFloat aX = [[[element attributeForName:@"x"] stringValue] floatValue]; CGFloat aY = [[[element attributeForName:@"y"] stringValue] floatValue]; - CGFloat aWidth = [[[element attributeForName:@"width"] stringValue] floatValue]; - CGFloat aHeight = [[[element attributeForName:@"height"] stringValue] floatValue]; - [path overwritePath:[NSBezierPath bezierPathWithRect:NSMakeRect( aX, aY, aWidth, aHeight)]]; + + CGFloat aWidth = [IJSVGUtils floatValue:[[element attributeForName:@"width"] stringValue] + fallBackForPercent:self.viewBox.size.width]; + CGFloat aHeight = [IJSVGUtils floatValue:[[element attributeForName:@"height"] stringValue] + fallBackForPercent:self.viewBox.size.width]; + + CGFloat rX = [[[element attributeForName:@"rx"] stringValue] floatValue]; + CGFloat rY = [[[element attributeForName:@"ry"] stringValue] floatValue]; + + [element removeAttributeForName:@"x"]; + [element removeAttributeForName:@"y"]; + [element removeAttributeForName:@"width"]; + [element removeAttributeForName:@"height"]; + [path overwritePath:[NSBezierPath bezierPathWithRoundedRect:NSMakeRect( aX, aY, aWidth, aHeight) + xRadius:rX + yRadius:rY]]; } @end diff --git a/source/IJSVGStyle.m b/source/IJSVGStyle.m index 85f4195..2a34e9b 100644 --- a/source/IJSVGStyle.m +++ b/source/IJSVGStyle.m @@ -42,7 +42,7 @@ { static NSRegularExpression * _reg = nil; static dispatch_once_t onceToken; - IJSVGStyle * style = [[[[self class] alloc] init] autorelease]; + IJSVGStyle * style = [[[self class] alloc] init]; dispatch_once(&onceToken, ^{ _reg = [[NSRegularExpression alloc] initWithPattern:@"([a-zA-Z\\-]+)\\:([^;]+)\\;?" options:0 @@ -52,14 +52,14 @@ options:0 range:NSMakeRange( 0, string.length ) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) - { - NSString * key = [string substringWithRange:[result rangeAtIndex:1]]; - NSString * value = [string substringWithRange:[result rangeAtIndex:2]]; - [[self class] computeStyleProperty:key - value:value - style:style]; - }]; - return style; + { + NSString * key = [string substringWithRange:[result rangeAtIndex:1]]; + NSString * value = [string substringWithRange:[result rangeAtIndex:2]]; + [[self class] computeStyleProperty:key + value:value + style:style]; + }]; + return [style autorelease]; } + (NSString *)trimString:(NSString *)string @@ -67,40 +67,30 @@ return [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } ++ (NSArray *)allowedColourKeys +{ + return @[@"fill",@"stroke-colour",@"stop-color",@"stroke"]; +} + + (void)computeStyleProperty:(NSString *)key - value:(NSString *)value - style:(IJSVGStyle *)style + value:(NSString *)value + style:(IJSVGStyle *)style { key = [[self class] trimString:key]; value = [[self class] trimString:value]; id val = nil; - if( [value length] > 4 ) - { - // is RGBA value - if( [[value substringToIndex:3] isEqualToString:@"rgb"] ) - { - NSInteger count = 0; - CGFloat * params = [IJSVGUtils commandParameters:value - count:&count]; - CGFloat alpha = 1; - if( count == 4 ) - alpha = params[3]; - val = [NSColor colorWithCalibratedRed:params[0]/255 - green:params[1]/255 - blue:params[2]/255 - alpha:alpha]; - free(params); - } else if( [[value substringToIndex:1] isEqualToString:@"#"] ) { - // hex value - val = [IJSVGColor colorFromHEXString:value - alpha:1.f]; - } - } - // value is numeric, convert to a float - if( [[self class] isNumeric:value] ) - val = @([value floatValue]); - + // is it a color? + NSColor * color = [IJSVGColor colorFromString:value]; + if( color == nil || ![[self allowedColourKeys] containsObject:key] ) + { + // value is numeric, convert to a float + val = value; + if( [[self class] isNumeric:value] ) + val = @([value floatValue]); + } else + val = color; + // set the value if( val != nil ) [style setPropertyValue:val @@ -110,8 +100,7 @@ + (BOOL)isNumeric:(NSString *)string { - NSCharacterSet * nonNumbers = [[NSCharacterSet decimalDigitCharacterSet] invertedSet]; - return [string rangeOfCharacterFromSet:nonNumbers].location == NSNotFound; + return [[NSScanner scannerWithString:string] scanFloat:NULL]; } - (NSString *)description diff --git a/source/IJSVGTransform.h b/source/IJSVGTransform.h index b0e0864..9aefe56 100644 --- a/source/IJSVGTransform.h +++ b/source/IJSVGTransform.h @@ -22,12 +22,14 @@ typedef NS_OPTIONS( NSInteger, IJSVGTransformCommand ) { IJSVGTransformCommand command; CGFloat * parameters; NSInteger parameterCount; + NSInteger sort; } @property ( nonatomic, assign ) IJSVGTransformCommand command; @property ( nonatomic, assign ) CGFloat * parameters; @property ( nonatomic, assign ) NSInteger parameterCount; +@property ( nonatomic, assign ) NSInteger sort; + (NSArray *)transformsForString:(NSString *)string; + (void)performTransform:(IJSVGTransform *)transform diff --git a/source/IJSVGTransform.m b/source/IJSVGTransform.m index 071142d..4fb70e2 100644 --- a/source/IJSVGTransform.m +++ b/source/IJSVGTransform.m @@ -13,6 +13,7 @@ @synthesize command; @synthesize parameters; @synthesize parameterCount; +@synthesize sort; - (void)dealloc { @@ -33,6 +34,23 @@ return IJSVGTransformCommandNotImplemented; } ++ (NSInteger)sortForTransformCommand:(IJSVGTransformCommand)command +{ + switch (command) { + case IJSVGTransformCommandScale: + return 0; + case IJSVGTransformCommandRotate: + return 1; + case IJSVGTransformCommandMatrix: + return 2; + case IJSVGTransformCommandTranslate: + return -1; + default: + return 10; + } + return 10; +} + + (NSArray *)transformsForString:(NSString *)string { static NSRegularExpression * _reg = nil; @@ -48,22 +66,23 @@ options:0 range:NSMakeRange( 0, string.length ) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) - { - NSString * command = [string substringWithRange:[result rangeAtIndex:1]]; - IJSVGTransformCommand commandType = [[self class] commandForCommandString:command]; - if( commandType == IJSVGTransformCommandNotImplemented ) - return; - - // create the transform - NSString * params = [string substringWithRange:[result rangeAtIndex:2]]; - IJSVGTransform * transform = [[[[self class] alloc] init] autorelease]; - NSInteger count = 0; - transform.command = commandType; - transform.parameters = [IJSVGUtils commandParameters:params - count:&count]; - transform.parameterCount = count; - [transforms addObject:transform]; - }]; + { + NSString * command = [string substringWithRange:[result rangeAtIndex:1]]; + IJSVGTransformCommand commandType = [[self class] commandForCommandString:command]; + if( commandType == IJSVGTransformCommandNotImplemented ) + return; + + // create the transform + NSString * params = [string substringWithRange:[result rangeAtIndex:2]]; + IJSVGTransform * transform = [[[[self class] alloc] init] autorelease]; + NSInteger count = 0; + transform.command = commandType; + transform.parameters = [IJSVGUtils commandParameters:params + count:&count]; + transform.parameterCount = count; + transform.sort = [[self class] sortForTransformCommand:commandType]; + [transforms addObject:transform]; + }]; } return transforms; } @@ -73,7 +92,7 @@ { switch( transform.command ) { - // matrix + // matrix case IJSVGTransformCommandMatrix: { CGContextConcatCTM( context, CGAffineTransformMake( transform.parameters[0], transform.parameters[1], @@ -83,8 +102,8 @@ transform.parameters[5])); break; } - - // translate + + // translate case IJSVGTransformCommandTranslate: { if( transform.parameterCount == 1 ) CGContextTranslateCTM( context, transform.parameters[0], 0 ); @@ -93,20 +112,24 @@ break; } - // scale + // scale case IJSVGTransformCommandScale: { - CGContextScaleCTM( context, transform.parameters[0], transform.parameters[1] ); + if( transform.parameterCount == 1 ) + CGContextScaleCTM( context, transform.parameters[0], transform.parameters[0] ); + else + CGContextScaleCTM( context, transform.parameters[0], transform.parameters[1] ); break; } - // rotate + // rotate case IJSVGTransformCommandRotate: { + // these are in radians, not degrees if( transform.parameterCount == 1 ) - CGContextRotateCTM( context, transform.parameters[0]); + CGContextRotateCTM( context, (transform.parameters[0] / 180) * M_PI); // need support for rotate around a point } - // do nothing + // do nothing case IJSVGTransformCommandNotImplemented: { } diff --git a/source/IJSVGUtils.h b/source/IJSVGUtils.h index 172c196..09183e9 100644 --- a/source/IJSVGUtils.h +++ b/source/IJSVGUtils.h @@ -26,5 +26,7 @@ + (CGFloat)angleBetweenPointA:(NSPoint)point pointb:(NSPoint)point; + (NSString *)defURL:(NSString *)string; ++ (CGFloat)floatValue:(NSString *)string + fallBackForPercent:(CGFloat)viewBox; @end diff --git a/source/IJSVGUtils.m b/source/IJSVGUtils.m index 2a627f5..71097c1 100644 --- a/source/IJSVGUtils.m +++ b/source/IJSVGUtils.m @@ -10,7 +10,7 @@ @implementation IJSVGUtils -#define FLOAT_EXP @"[-+]?[0-9]*\\.?[0-9]+" +#define FLOAT_EXP @"[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?" + (IJSVGCommandType)typeForCommandString:(NSString *)string { @@ -22,7 +22,7 @@ static NSRegularExpression *_commandRegex; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - _commandRegex = [[NSRegularExpression alloc] initWithPattern:@"[A-Za-z]" + _commandRegex = [[NSRegularExpression alloc] initWithPattern:@"[MmZzLlHhVvCcSsQqTtAa]{1}" options:0 error:nil]; }); @@ -51,9 +51,9 @@ error:nil]; }); return [_reg stringByReplacingMatchesInString:string - options:0 - range:NSMakeRange( 0, string.length ) - withTemplate:@""]; + options:0 + range:NSMakeRange( 0, string.length ) + withTemplate:@""]; } + (NSString *)defURL:(NSString *)string @@ -70,10 +70,10 @@ options:0 range:NSMakeRange( 0, string.length ) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) - { - if( ( foundID = [string substringWithRange:[result rangeAtIndex:1]] ) != nil ) - *stop = YES; - }]; + { + if( ( foundID = [string substringWithRange:[result rangeAtIndex:1]] ) != nil ) + *stop = YES; + }]; return foundID; } @@ -119,11 +119,19 @@ options:0 range:NSMakeRange( 0, command.length)]; CGFloat * ret = (CGFloat *)malloc(matches.count*sizeof(CGFloat)); + NSDictionary * dict = [NSDictionary dictionaryWithObject:@"." + forKey:NSLocaleDecimalSeparator]; for( NSInteger i = 0; i < matches.count; i++ ) { NSTextCheckingResult * match = [matches objectAtIndex:i]; NSString * paramString = [command substringWithRange:match.range]; - ret[i] = (CGFloat)[paramString floatValue]; + NSDecimalNumber * decimal = nil; + if( [paramString rangeOfString:@"."].location != NSNotFound ) + decimal = [NSDecimalNumber decimalNumberWithString:paramString + locale:dict]; + else + decimal = [NSDecimalNumber decimalNumberWithString:paramString]; + ret[i] = (CGFloat)[decimal floatValue]; *count += 1; } return ret; @@ -145,6 +153,15 @@ return ret; } ++ (CGFloat)floatValue:(NSString *)string + fallBackForPercent:(CGFloat)fallBack +{ + CGFloat val = [string floatValue]; + if( [string rangeOfString:@"%"].location != NSNotFound ) + val = (fallBack * val)/100; + return val; +} + + (void)logParameters:(CGFloat *)param count:(NSInteger)count {