// // IJSVGTransform.m // IconJar // // Created by Curtis Hard on 01/09/2014. // Copyright (c) 2014 Curtis Hard. All rights reserved. // #import "IJSVGMath.h" #import "IJSVGTransform.h" @implementation IJSVGTransform @synthesize command; @synthesize parameters; @synthesize parameterCount; @synthesize sort; - (void)dealloc { free(parameters); [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; } NSString* IJSVGDebugAffineTransform(CGAffineTransform transform) { NSMutableArray* strings = [[[NSMutableArray alloc] init] autorelease]; [strings addObjectsFromArray:[IJSVGTransform affineTransformToSVGTransformAttributeString:transform]]; return [strings componentsJoinedByString:@" "]; } NSString* IJSVGDebugTransforms(NSArray* transforms) { NSMutableArray* strings = [[[NSMutableArray alloc] init] autorelease]; IJSVGApplyTransform(transforms, ^(IJSVGTransform* transform) { [strings addObjectsFromArray:[IJSVGTransform affineTransformToSVGTransformAttributeString:transform.CGAffineTransform]]; }); return [strings componentsJoinedByString:@" "]; } CGAffineTransform IJSVGConcatTransforms(NSArray* transforms) { __block CGAffineTransform trans = CGAffineTransformIdentity; IJSVGApplyTransform(transforms, ^(IJSVGTransform* transform) { trans = CGAffineTransformConcat(trans, transform.CGAffineTransform); }); return trans; } void IJSVGApplyTransform(NSArray* transforms, IJSVGTransformApplyBlock block) { for (IJSVGTransform* transform in transforms) { block(transform); } }; + (IJSVGTransform*)transformByTranslatingX:(CGFloat)x y:(CGFloat)y { IJSVGTransform* transform = [[[self alloc] init] autorelease]; transform.command = IJSVGTransformCommandTranslate; transform.parameterCount = 2; CGFloat* params = (CGFloat*)malloc(sizeof(CGFloat) * 2); params[0] = x; params[1] = y; transform.parameters = params; return transform; } - (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 { str = str.lowercaseString; if ([str isEqualToString:@"matrix"]) return IJSVGTransformCommandMatrix; if ([str isEqualToString:@"translate"]) return IJSVGTransformCommandTranslate; if ([str isEqualToString:@"translatex"]) return IJSVGTransformCommandTranslateX; if ([str isEqualToString:@"translatey"]) return IJSVGTransformCommandTranslateY; if ([str isEqualToString:@"scale"]) return IJSVGTransformCommandScale; if ([str isEqualToString:@"skewx"]) return IJSVGTransformCommandSkewX; if ([str isEqualToString:@"skewy"]) return IJSVGTransformCommandSkewY; if ([str isEqualToString:@"rotate"]) return IJSVGTransformCommandRotate; return IJSVGTransformCommandNotImplemented; } + (NSInteger)sortForTransformCommand:(IJSVGTransformCommand)command { switch (command) { case IJSVGTransformCommandScale: return 0; case IJSVGTransformCommandRotate: return 1; case IJSVGTransformCommandMatrix: return 2; case IJSVGTransformCommandTranslateX: case IJSVGTransformCommandTranslateY: case IJSVGTransformCommandTranslate: return -1; default: return 10; } return 10; } + (NSArray*)transformsForString:(NSString*)string { static NSRegularExpression* _reg = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _reg = [[NSRegularExpression alloc] initWithPattern:@"([a-zA-Z]+)\\(([^\\)]+)\\)" options:0 error:nil]; }); NSMutableArray* transforms = [[[NSMutableArray alloc] init] autorelease]; @autoreleasepool { [_reg enumerateMatchesInString:string 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; transform.sort = [self.class sortForTransformCommand:commandType]; [transforms addObject:transform]; }]; } return transforms; } + (NSBezierPath*)transformedPath:(IJSVGPath*)path { if (path.transforms.count == 0) return path.path; NSBezierPath* cop = [[path.path copy] autorelease]; for (IJSVGTransform* transform in path.transforms) { NSAffineTransform* at = NSAffineTransform.transform; switch (transform.command) { // matrix case IJSVGTransformCommandMatrix: { at.transformStruct = (NSAffineTransformStruct){ .m11 = transform.parameters[0], .m12 = transform.parameters[1], .m21 = transform.parameters[2], .m22 = transform.parameters[3], .tX = transform.parameters[4], .tY = transform.parameters[5], }; break; } // skewX case IJSVGTransformCommandSkewX: { CGFloat degrees = transform.parameters[0]; CGFloat radians = degrees * M_PI / 180.f; at.transformStruct = (NSAffineTransformStruct){ .m11 = 1.f, .m12 = 0.f, .m21 = tan(radians), .m22 = 1.f, .tX = 0.f, .tY = 0.f }; break; } // skewX case IJSVGTransformCommandSkewY: { CGFloat degrees = transform.parameters[0]; CGFloat radians = degrees * M_PI / 180.f; at.transformStruct = (NSAffineTransformStruct){ .m11 = 1.f, .m12 = tan(radians), .m21 = 0.f, .m22 = 1.f, .tX = 0.f, .tY = 0.f }; break; } // translate case IJSVGTransformCommandTranslate: { if (transform.parameterCount == 1) [at translateXBy:transform.parameters[0] yBy:0]; else [at translateXBy:transform.parameters[0] yBy:transform.parameters[1]]; break; } // translateX case IJSVGTransformCommandTranslateX: { [at translateXBy:transform.parameters[0] yBy:0.f]; break; } // translateY case IJSVGTransformCommandTranslateY: { [at translateXBy:0.f yBy:transform.parameters[0]]; break; } // scale case IJSVGTransformCommandScale: { if (transform.parameterCount == 1) [at scaleBy:transform.parameters[0]]; else [at scaleXBy:transform.parameters[0] yBy:transform.parameters[1]]; break; } // rotate 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; } // do nothing case IJSVGTransformCommandNotImplemented: { } } [cop transformUsingAffineTransform:at]; } return cop; } - (CGAffineTransform)CGAffineTransform { return [self CGAffineTransformWithModifier:nil]; } - (CGAffineTransform)stackIdentity:(CGAffineTransform)identity { switch (self.command) { // translate case IJSVGTransformCommandTranslate: { if (self.parameterCount == 1) { return CGAffineTransformTranslate(identity, self.parameters[0], 0.f); } return CGAffineTransformTranslate(identity, self.parameters[0], self.parameters[1]); } // translateX case IJSVGTransformCommandTranslateX: { return CGAffineTransformTranslate(identity, self.parameters[0], 0.f); } // translateY case IJSVGTransformCommandTranslateY: { return CGAffineTransformTranslate(identity, 0.f, self.parameters[0]); } // rotate case IJSVGTransformCommandRotate: { if (self.parameterCount == 1) { return CGAffineTransformRotate(identity, (self.parameters[0] / 180) * M_PI); } CGFloat p0 = self.parameters[0]; CGFloat p1 = self.parameters[1]; CGFloat p2 = self.parameters[2]; CGFloat angle = p0 * (M_PI / 180.f); identity = CGAffineTransformTranslate(identity, p1, p2); identity = CGAffineTransformRotate(identity, angle); return CGAffineTransformTranslate(identity, -1.f * p1, -1.f * p2); } // scale case IJSVGTransformCommandScale: { CGFloat p0 = self.parameters[0]; CGFloat p1 = self.parameters[1]; if (self.parameterCount == 1) { return CGAffineTransformScale(identity, p0, p0); } return CGAffineTransformScale(identity, p0, p1); } // matrix case IJSVGTransformCommandMatrix: { CGFloat p0 = self.parameters[0]; CGFloat p1 = self.parameters[1]; CGFloat p2 = self.parameters[2]; CGFloat p3 = self.parameters[3]; CGFloat p4 = self.parameters[4]; CGFloat p5 = self.parameters[5]; return CGAffineTransformMake(p0, p1, p2, p3, p4, p5); } // skewX case IJSVGTransformCommandSkewX: { CGFloat degrees = self.parameters[0]; CGFloat radians = degrees * M_PI / 180.f; return CGAffineTransformMake(1.f, 0.f, tan(radians), 1.f, 0.f, 0.f); } // skewY case IJSVGTransformCommandSkewY: { CGFloat degrees = self.parameters[0]; CGFloat radians = degrees * M_PI / 180.f; return CGAffineTransformMake(1.f, tan(radians), 0.f, 1.f, 0.f, 0.f); } case IJSVGTransformCommandNotImplemented: { return CGAffineTransformIdentity; } } return CGAffineTransformIdentity; } - (CGAffineTransform)CGAffineTransformWithModifier:(IJSVGTransformParameterModifier)modifier { switch (self.command) { // matrix case IJSVGTransformCommandMatrix: { CGFloat p0 = self.parameters[0]; CGFloat p1 = self.parameters[1]; CGFloat p2 = self.parameters[2]; CGFloat p3 = self.parameters[3]; CGFloat p4 = self.parameters[4]; CGFloat p5 = self.parameters[5]; if (modifier != nil) { p0 = modifier(0, p0); p1 = modifier(1, p1); p2 = modifier(2, p2); p3 = modifier(3, p3); p4 = modifier(4, p4); p5 = modifier(5, p5); } return CGAffineTransformMake(p0, p1, p2, p3, p4, p5); } // translate case IJSVGTransformCommandTranslate: { CGFloat p0 = self.parameters[0]; CGFloat p1 = self.parameters[1]; if (modifier != nil) { p0 = modifier(0, p0); p1 = modifier(1, p1); } if (self.parameterCount == 1) return CGAffineTransformMakeTranslation(p0, 0); return CGAffineTransformMakeTranslation(p0, p1); } // translateX case IJSVGTransformCommandTranslateX: { CGFloat p0 = self.parameters[0]; if (modifier != nil) { p0 = modifier(0, p0); } return CGAffineTransformMakeTranslation(p0, 0.f); } // translateY case IJSVGTransformCommandTranslateY: { CGFloat p0 = self.parameters[0]; if (modifier != nil) { p0 = modifier(0, p0); } return CGAffineTransformMakeTranslation(0.f, p0); } // scale case IJSVGTransformCommandScale: { CGFloat p0 = self.parameters[0]; CGFloat p1 = self.parameters[1]; if (modifier != nil) { p0 = modifier(0, p0); p1 = modifier(1, p1); } if (self.parameterCount == 1) return CGAffineTransformMakeScale(p0, p0); return CGAffineTransformMakeScale(p0, p1); } // skewX case IJSVGTransformCommandSkewX: { CGFloat degrees = self.parameters[0]; if (modifier != nil) { degrees = modifier(0, degrees); } CGFloat radians = degrees * M_PI / 180.f; return CGAffineTransformMake(1.f, 0.f, tan(radians), 1.f, 0.f, 0.f); } // skewY case IJSVGTransformCommandSkewY: { CGFloat degrees = self.parameters[0]; if (modifier != nil) { degrees = modifier(0, degrees); } CGFloat radians = degrees * M_PI / 180.f; return CGAffineTransformMake(1.f, tan(radians), 0.f, 1.f, 0.f, 0.f); } // rotate case IJSVGTransformCommandRotate: { if (self.parameterCount == 1) return CGAffineTransformMakeRotation((self.parameters[0] / 180) * M_PI); else { CGFloat p0 = self.parameters[0]; CGFloat p1 = self.parameters[1]; CGFloat p2 = self.parameters[2]; if (modifier != nil) { p0 = modifier(0, p0); p1 = modifier(1, p1); p2 = modifier(2, p2); } CGFloat angle = p0 * (M_PI / 180.f); CGAffineTransform def = CGAffineTransformIdentity; def = CGAffineTransformTranslate(def, p1, p2); def = CGAffineTransformRotate(def, angle); def = CGAffineTransformTranslate(def, -1.f * p1, -1.f * p2); return def; } break; } // do nothing case IJSVGTransformCommandNotImplemented: { return CGAffineTransformIdentity; } } return CGAffineTransformIdentity; } + (NSArray*)transformsFromAffineTransform:(CGAffineTransform)affineTransform { NSArray* strings = [self affineTransformToSVGTransformAttributeString:affineTransform]; return [self transformsForString:[strings componentsJoinedByString:@" "]]; } + (NSString*)affineTransformToSVGMatrixString:(CGAffineTransform)transform { return [NSString stringWithFormat:@"matrix(%g,%g,%g,%g,%g,%g)", transform.a, transform.b, transform.c, transform.d, transform.tx, transform.ty]; } // this is an Object-C version of the matrixToTransform method from SVGO + (NSArray*)affineTransformToSVGTransformAttributeString:(CGAffineTransform)affineTransform { const CGFloat data[6] = { affineTransform.a, affineTransform.b, affineTransform.c, affineTransform.d, affineTransform.tx, affineTransform.ty }; CGFloat sx = sqrtf(data[0] * data[0] + data[1] * data[1]); CGFloat sy = (data[0] * data[3] - data[1] * data[2]) / sx; CGFloat colSum = data[0] * data[2] + data[1] * data[3]; CGFloat rowSum = data[0] * data[1] + data[2] * data[3]; BOOL scaleBefore = rowSum != 0.f || (sx == sy); NSMutableArray* trans = [[[NSMutableArray alloc] init] autorelease]; // translate if (data[4] != 0.f || data[5] != 0.f) { NSString* str = [NSString stringWithFormat:@"translate(%g, %g)", data[4], data[5]]; [trans addObject:str]; } // skewX if (data[1] == 0.f && data[2] != 0.f) { NSString* str = [NSString stringWithFormat:@"skewX(%g)", IJSVGMathAtan(data[2] / sy)]; [trans addObject:str]; // skewY } else if (data[1] != 0.f && data[2] == 0.f) { NSString* str = [NSString stringWithFormat:@"skewY(%g)", IJSVGMathAtan(data[1] / data[0])]; [trans addObject:str]; sx = data[0]; sy = data[3]; } else if (colSum == 0.f || (sx == 1.f && sy == 1.f) || scaleBefore == NO) { if (scaleBefore == NO) { sx = (data[0] < 0.f ? -1.f : 1.f) * sqrtf(data[0] * data[0] + data[2] * data[2]); sy = (data[3] < 0.f ? -1.f : 1.f) * sqrtf(data[1] * data[1] + data[3] * data[3]); NSString* str = nil; if (sx == sy) { str = [NSString stringWithFormat:@"scale(%g)", sx]; } else { str = [NSString stringWithFormat:@"scale(%g, %g)", sx, sy]; } [trans addObject:str]; } // rotate CGFloat rotate = IJSVGMathAcos(data[0] / sx) * (data[1] * sy < 0.f ? -1.f : 1.f); NSString* rotateString = nil; if (rotate != 0.f) { rotateString = [NSString stringWithFormat:@"rotate(%g)", rotate]; } // skewX if (rowSum != 0.f && colSum != 0.f) { NSString* str = [NSString stringWithFormat:@"skewX(%g)", IJSVGMathAtan(colSum / (sx * sx))]; [trans addObject:str]; } // rotate around center if (rotate != 0.f && (data[4] != 0.f || data[5] != 0.f)) { [trans removeObjectAtIndex:0]; CGFloat cos = data[0] / sx; CGFloat sin = data[1] / (scaleBefore ? sx : sy); CGFloat x = data[4] * (scaleBefore ? 1.f : sy); CGFloat y = data[5] * (scaleBefore ? 1.f : sx); CGFloat denom = (powf(1.f - cos, 2.f) + powf(sin, 2.f)) * (scaleBefore ? 1.f : sx * sy); CGFloat r1 = rotate; CGFloat r2 = ((1.f - cos) * x - sin * y) / denom; CGFloat r3 = ((1.f - cos) * y + sin * x) / denom; rotateString = [NSString stringWithFormat:@"rotate(%g, %g, %g)", r1, r2, r3]; } if (rotateString != nil) { [trans addObject:rotateString]; } } // scale if ((scaleBefore && (sx != 1.f || sy != 1.f)) || trans.count == 0.f) { NSString* str = nil; if (sx == sy) { str = [NSString stringWithFormat:@"scale(%g)", sx]; } else { str = [NSString stringWithFormat:@"scale(%g, %g)", sx, sy]; } [trans addObject:str]; } return trans; } - (NSString*)description { return [NSString stringWithFormat:@"%@ %@", [super description], [self.class affineTransformToSVGTransformAttributeString:self.CGAffineTransform]]; } @end