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 42d2389..da8f5d3 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 45dbd38..b012a0f 100644 --- a/source/IJSVG.m +++ b/source/IJSVG.m @@ -420,6 +420,10 @@ static NSColor * _baseColor = nil; { CGContextSaveGState(ref); { + // 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 ) { @@ -441,7 +445,8 @@ static NSColor * _baseColor = nil; [radGrad.gradient drawInBezierPath:path.path relativeCenterPosition:NSZeroPoint]; } - } CGContextRestoreGState(ref); + } + CGContextRestoreGState(ref); } else { // no gradient specified // just use the color instead diff --git a/source/IJSVGCommand.h b/source/IJSVGCommand.h index 54fb30a..df943ee 100644 --- a/source/IJSVGCommand.h +++ b/source/IJSVGCommand.h @@ -21,6 +21,7 @@ typedef NS_ENUM( NSInteger, IJSVGCommandType ) { @required + (void)runWithParams:(CGFloat *)params paramCount:(NSInteger)count + command:(IJSVGCommand *)currentCommand previousCommand:(IJSVGCommand *)command type:(IJSVGCommandType)type path:(IJSVGPath *)path; @@ -39,6 +40,7 @@ typedef NS_ENUM( NSInteger, IJSVGCommandType ) { NSInteger requiredParameters; IJSVGCommandType type; IJSVGCommand * previousCommand; + NSInteger _currentIndex; Class commandClass; } @@ -63,4 +65,9 @@ typedef NS_ENUM( NSInteger, IJSVGCommandType ) { + (NSDictionary *)registeredCommandClasses; + (Class)commandClassForCommandLetter:(NSString *)str; +- (CGFloat)readFloat; +- (NSPoint)readPoint; +- (BOOL)readBOOL; +- (void)resetRead; + @end diff --git a/source/IJSVGCommand.m b/source/IJSVGCommand.m index 60eaafa..289baf2 100644 --- a/source/IJSVGCommand.m +++ b/source/IJSVGCommand.m @@ -39,6 +39,7 @@ static NSMutableDictionary * _classes = nil; if( ( self = [super init] ) != nil ) { // work out the basics + _currentIndex = 0; subCommands = [[NSMutableArray alloc] init]; command = [[str substringToIndex:1] copy]; type = [IJSVGUtils typeForCommandString:self.command]; @@ -125,4 +126,29 @@ static NSMutableDictionary * _classes = nil; return NSClassFromString(command); } +- (CGFloat)readFloat +{ + CGFloat f = parameters[_currentIndex]; + _currentIndex++; + return f; +} + +- (NSPoint)readPoint +{ + CGFloat x = parameters[_currentIndex]; + CGFloat y = parameters[_currentIndex+1]; + _currentIndex+=2; + return NSMakePoint( x, y ); +} + +- (BOOL)readBOOL +{ + return [self readFloat] == 1; +} + +- (void)resetRead +{ + _currentIndex = 0; +} + @end diff --git a/source/IJSVGCommandArc.m b/source/IJSVGCommandArc.m index 1ed1c9e..4e01404 100644 --- a/source/IJSVGCommandArc.m +++ b/source/IJSVGCommandArc.m @@ -7,6 +7,7 @@ // #import "IJSVGCommandArc.h" +#import "IJSVGUtils.h" @implementation IJSVGCommandArc @@ -23,31 +24,79 @@ + (void)runWithParams:(CGFloat *)params paramCount:(NSInteger)count + command:(IJSVGCommand *)currentCommand previousCommand:(IJSVGCommand *)command type:(IJSVGCommandType)type path:(IJSVGPath *)path { -// NSPoint point = [path currentPoint]; -// -// CGFloat x1 = point.x; -// CGFloat y1 = point.y; -// -// NSPoint radii = [[self class] readCoordinatePair:params -// index:0]; -// -// CGFloat rX = fabs(radii.x); -// CGFloat rY = fabs(radii.y); -// -// CGFloat phi = params[2]; -// phi *= M_PI/180; -// -// BOOL largeArcFlag = params[3] != 0.f; -// BOOL sweepFlag = params[4] != 0.f; -// -// NSPoint endPoint = NSMakePoint( params[5], params[6]); -// -// CGFloat x2 = point.x + endPoint.x; -// CGFloat y2 = point.y + endPoint.y; + + // command was taken from: https://github.com/jmenter/JAMSVGImage/blob/89b375c0c3203355a0c693e3b805458415bf4e29/Classes/JAMSVGImage/Path%20and%20Parser/JAMStyledBezierPathFactory.m + // and modified to purpose inside this to be converted into degrees and not radians + + CGPoint radii = CGPointZero; + CGPoint arcEndPoint = CGPointZero; + CGPoint arcStartPoint = path.currentPoint; + CGFloat xAxisRotation = 0; + BOOL largeArcFlag = 0; + BOOL sweepFlag = 0; + + radii = [currentCommand readPoint]; + xAxisRotation = [currentCommand readFloat]; + largeArcFlag = [currentCommand readBOOL]; + sweepFlag = [currentCommand readBOOL]; + arcEndPoint = [currentCommand readPoint]; + + if ( type == IJSVGCommandTypeRelative ) { + arcEndPoint.x += path.currentPoint.x; + arcEndPoint.y += path.currentPoint.y; + } + + xAxisRotation *= M_PI / 180.f; + CGPoint currentPoint = CGPointMake(cos(xAxisRotation) * (arcStartPoint.x - arcEndPoint.x) / 2.0 + sin(xAxisRotation) * (arcStartPoint.y - arcEndPoint.y) / 2.0, -sin(xAxisRotation) * (arcStartPoint.x - arcEndPoint.x) / 2.0 + cos(xAxisRotation) * (arcStartPoint.y - arcEndPoint.y) / 2.0); + + CGFloat radiiAdjustment = pow(currentPoint.x, 2) / pow(radii.x, 2) + pow(currentPoint.y, 2) / pow(radii.y, 2); + radii.x *= (radiiAdjustment > 1) ? sqrt(radiiAdjustment) : 1; + radii.y *= (radiiAdjustment > 1) ? sqrt(radiiAdjustment) : 1; + + CGFloat sweep = (largeArcFlag == sweepFlag ? -1 : 1) * sqrt(((pow(radii.x, 2) * pow(radii.y, 2)) - (pow(radii.x, 2) * pow(currentPoint.y, 2)) - (pow(radii.y, 2) * pow(currentPoint.x, 2))) / (pow(radii.x, 2) * pow(currentPoint.y, 2) + pow(radii.y, 2) * pow(currentPoint.x, 2))); + sweep = (sweep != sweep) ? 0 : sweep; + CGPoint preCenterPoint = CGPointMake(sweep * radii.x * currentPoint.y / radii.y, sweep * -radii.y * currentPoint.x / radii.x); + + CGPoint centerPoint = CGPointMake((arcStartPoint.x + arcEndPoint.x) / 2.0 + cos(xAxisRotation) * preCenterPoint.x - sin(xAxisRotation) * preCenterPoint.y, (arcStartPoint.y + arcEndPoint.y) / 2.0 + sin(xAxisRotation) * preCenterPoint.x + cos(xAxisRotation) * preCenterPoint.y); + + CGFloat startAngle = angle(CGPointMake(1, 0), CGPointMake((currentPoint.x-preCenterPoint.x)/radii.x, + (currentPoint.y-preCenterPoint.y)/radii.y)); + + CGPoint deltaU = CGPointMake((currentPoint.x - preCenterPoint.x) / radii.x, + (currentPoint.y - preCenterPoint.y) / radii.y); + CGPoint deltaV = CGPointMake((-currentPoint.x - preCenterPoint.x) / radii.x, + (-currentPoint.y - preCenterPoint.y) / radii.y); + CGFloat angleDelta = (deltaU.x * deltaV.y < deltaU.y * deltaV.x ? -1 : 1) * acos(ratio(deltaU, deltaV)); + + angleDelta = (ratio(deltaU, deltaV) <= -1) ? M_PI : (ratio(deltaU, deltaV) >= 1) ? 0 : angleDelta; + + CGFloat radius = MAX(radii.x, radii.y); + CGPoint scale = (radii.x > radii.y) ? CGPointMake(1, radii.y / radii.x) : CGPointMake(radii.x / radii.y, 1); + + NSAffineTransform * trans = [NSAffineTransform transform]; + [trans translateXBy:-centerPoint.x yBy:-centerPoint.y]; + [trans rotateByRadians:-xAxisRotation]; + [trans scaleXBy:(1/scale.x) yBy:(1/scale.y)]; + + [path.currentSubpath transformUsingAffineTransform:trans]; + [path.currentSubpath appendBezierPathWithArcWithCenter:NSZeroPoint + radius:radius + startAngle:radians_to_degrees(startAngle) + endAngle:radians_to_degrees(startAngle + angleDelta) + clockwise:!sweepFlag]; + + trans = [NSAffineTransform transform]; + [trans translateXBy:centerPoint.x yBy:centerPoint.y]; + [trans rotateByRadians:xAxisRotation]; + [trans scaleXBy:scale.x yBy:scale.y]; + [path.currentSubpath transformUsingAffineTransform:trans]; + + } @end diff --git a/source/IJSVGCommandClose.m b/source/IJSVGCommandClose.m index a0ac856..7483443 100644 --- a/source/IJSVGCommandClose.m +++ b/source/IJSVGCommandClose.m @@ -23,6 +23,7 @@ + (void)runWithParams:(CGFloat *)params paramCount:(NSInteger)count + command:(IJSVGCommand *)currentCommand previousCommand:(IJSVGCommand *)command type:(IJSVGCommandType)type path:(IJSVGPath *)path diff --git a/source/IJSVGCommandCommandSmoothQuadraticCurve.m b/source/IJSVGCommandCommandSmoothQuadraticCurve.m index 9278779..f15d768 100644 --- a/source/IJSVGCommandCommandSmoothQuadraticCurve.m +++ b/source/IJSVGCommandCommandSmoothQuadraticCurve.m @@ -24,6 +24,7 @@ + (void)runWithParams:(CGFloat *)params paramCount:(NSInteger)count + command:(IJSVGCommand *)currentCommand previousCommand:(IJSVGCommand *)command type:(IJSVGCommandType)type path:(IJSVGPath *)path diff --git a/source/IJSVGCommandCurve.m b/source/IJSVGCommandCurve.m index cf8076b..faf840d 100644 --- a/source/IJSVGCommandCurve.m +++ b/source/IJSVGCommandCurve.m @@ -23,6 +23,7 @@ + (void)runWithParams:(CGFloat *)params paramCount:(NSInteger)count + command:(IJSVGCommand *)currentCommand previousCommand:(IJSVGCommand *)command type:(IJSVGCommandType)type path:(IJSVGPath *)path diff --git a/source/IJSVGCommandHorizontalLine.m b/source/IJSVGCommandHorizontalLine.m index 3f4aa4b..d1c6f6c 100644 --- a/source/IJSVGCommandHorizontalLine.m +++ b/source/IJSVGCommandHorizontalLine.m @@ -23,6 +23,7 @@ + (void)runWithParams:(CGFloat *)params paramCount:(NSInteger)count + command:(IJSVGCommand *)currentCommand previousCommand:(IJSVGCommand *)command type:(IJSVGCommandType)type path:(IJSVGPath *)path diff --git a/source/IJSVGCommandLineTo.m b/source/IJSVGCommandLineTo.m index 1650e62..f15bb06 100644 --- a/source/IJSVGCommandLineTo.m +++ b/source/IJSVGCommandLineTo.m @@ -23,6 +23,7 @@ + (void)runWithParams:(CGFloat *)params paramCount:(NSInteger)count + command:(IJSVGCommand *)currentCommand previousCommand:(IJSVGCommand *)command type:(IJSVGCommandType)type path:(IJSVGPath *)path diff --git a/source/IJSVGCommandMove.m b/source/IJSVGCommandMove.m index 3b1eac0..4970fac 100644 --- a/source/IJSVGCommandMove.m +++ b/source/IJSVGCommandMove.m @@ -25,6 +25,7 @@ + (void)runWithParams:(CGFloat *)params paramCount:(NSInteger)count + command:(IJSVGCommand *)currentCommand previousCommand:(IJSVGCommand *)command type:(IJSVGCommandType)type path:(IJSVGPath *)path @@ -35,6 +36,7 @@ { [IJSVGCommandLineTo runWithParams:params paramCount:count + command:currentCommand previousCommand:command type:type path:path]; diff --git a/source/IJSVGCommandQuadraticCurve.m b/source/IJSVGCommandQuadraticCurve.m index e8d4115..0c9a972 100644 --- a/source/IJSVGCommandQuadraticCurve.m +++ b/source/IJSVGCommandQuadraticCurve.m @@ -23,6 +23,7 @@ + (void)runWithParams:(CGFloat *)params paramCount:(NSInteger)count + command:(IJSVGCommand *)currentCommand previousCommand:(IJSVGCommand *)command type:(IJSVGCommandType)type path:(IJSVGPath *)path diff --git a/source/IJSVGCommandSmoothCurve.m b/source/IJSVGCommandSmoothCurve.m index de46beb..1c3aff1 100644 --- a/source/IJSVGCommandSmoothCurve.m +++ b/source/IJSVGCommandSmoothCurve.m @@ -25,6 +25,7 @@ + (void)runWithParams:(CGFloat *)params paramCount:(NSInteger)count + command:(IJSVGCommand *)currentCommand previousCommand:(IJSVGCommand *)command type:(IJSVGCommandType)type path:(IJSVGPath *)path diff --git a/source/IJSVGCommandVerticalLine.m b/source/IJSVGCommandVerticalLine.m index 24ac369..c507654 100644 --- a/source/IJSVGCommandVerticalLine.m +++ b/source/IJSVGCommandVerticalLine.m @@ -23,6 +23,7 @@ + (void)runWithParams:(CGFloat *)params paramCount:(NSInteger)count + command:(IJSVGCommand *)currentCommand previousCommand:(IJSVGCommand *)command type:(IJSVGCommandType)type path:(IJSVGPath *)path diff --git a/source/IJSVGParser.m b/source/IJSVGParser.m index 0e803aa..b31a972 100644 --- a/source/IJSVGParser.m +++ b/source/IJSVGParser.m @@ -238,9 +238,11 @@ if( fillAttribute != nil ) { NSString * defID = [IJSVGUtils defURL:[fillAttribute stringValue]]; - if( defID != nil ) - node.fillGradient = (IJSVGGradient *)[node defForID:defID]; - else { + if( defID != nil ) { + IJSVGGradient * grad = (IJSVGGradient *)[node defForID:defID]; + node.fillGradient = grad; + node.gradientTransforms = grad.gradientTransforms; + } else { // change the fill color over if its allowed node.fillColor = [IJSVGColor colorFromString:[fillAttribute stringValue]]; if( node.fillOpacity != 1.f ) @@ -324,8 +326,11 @@ if( [fill isKindOfClass:[NSString class]] ) { NSString * defID = [IJSVGUtils defURL:(NSString *)fill]; - if( defID != nil ) - node.fillGradient = (IJSVGGradient *)[node defForID:defID]; + if( defID != nil ) { + IJSVGGradient * grad = (IJSVGGradient *)[node defForID:defID]; + node.fillGradient = grad; + node.gradientTransforms = grad.gradientTransforms; + } } else { if( [IJSVGColor computeColor:fill] != nil ) node.fillColor = fill; @@ -775,6 +780,7 @@ { [subCommand.commandClass runWithParams:subCommand.parameters paramCount:subCommand.parameterCount + command:subCommand previousCommand:preCommand type:subCommand.type path:path]; diff --git a/source/IJSVGUtils.h b/source/IJSVGUtils.h index 2d4efe5..f829d08 100644 --- a/source/IJSVGUtils.h +++ b/source/IJSVGUtils.h @@ -11,6 +11,12 @@ @interface IJSVGUtils : NSObject +CGFloat magnitude(CGPoint point); +CGFloat ratio( CGPoint a, CGPoint b ); +CGFloat angle( CGPoint a, CGPoint b ); +CGFloat radians_to_degrees( CGFloat radians); +CGFloat degrees_to_radians( CGFloat degrees ); + + (IJSVGCommandType)typeForCommandString:(NSString *)string; + (NSRegularExpression *)commandNameRegex; + (NSRegularExpression *)commandRegex; diff --git a/source/IJSVGUtils.m b/source/IJSVGUtils.m index e82a908..96af5ee 100644 --- a/source/IJSVGUtils.m +++ b/source/IJSVGUtils.m @@ -12,6 +12,30 @@ #define FLOAT_EXP @"[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?" +CGFloat angle( CGPoint a, CGPoint b ) { + return [IJSVGUtils angleBetweenPointA:a + pointb:b]; +} + +CGFloat ratio( CGPoint a, CGPoint b ) { + return (a.x * b.x + a.y * b.y) / (magnitude(a) * magnitude(b)); +} + +CGFloat magnitude(CGPoint point) +{ + return sqrtf(powf(point.x, 2) + powf(point.y, 2)); +} + +CGFloat radians_to_degrees(CGFloat radians) +{ + return ((radians) * (180.0 / M_PI)); +} + +CGFloat degrees_to_radians( CGFloat degrees ) +{ + return ( ( degrees ) / 180.0 * M_PI ); +} + + (IJSVGCommandType)typeForCommandString:(NSString *)string { return [string isEqualToString:[string uppercaseString]] ? IJSVGCommandTypeAbsolute : IJSVGCommandTypeRelative; @@ -200,13 +224,10 @@ return [string floatValue]; } -+ (CGFloat)angleBetweenPointA:(NSPoint)startingPoint - pointb:(NSPoint)endingPoint ++ (CGFloat)angleBetweenPointA:(NSPoint)point1 + pointb:(NSPoint)point2 { - CGPoint originPoint = CGPointMake(endingPoint.x - startingPoint.x, endingPoint.y - startingPoint.y); - CGFloat bearingRadians = atan2f(originPoint.y, originPoint.x); - CGFloat bearingDegrees = bearingRadians * (180.0 / M_PI); - return (bearingDegrees > 0.0 ? bearingDegrees : (360.0 + bearingDegrees)); + return (point1.x * point2.y < point1.y * point2.x ? -1 : 1) * acosf(ratio(point1, point2)); } @end