604 lines
20 KiB
Objective-C
604 lines
20 KiB
Objective-C
//
|
|
// 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<IJSVGTransform*>* transforms)
|
|
{
|
|
NSMutableArray* strings = [[[NSMutableArray alloc] init] autorelease];
|
|
IJSVGApplyTransform(transforms, ^(IJSVGTransform* transform) {
|
|
[strings addObjectsFromArray:[IJSVGTransform affineTransformToSVGTransformAttributeString:transform.CGAffineTransform]];
|
|
});
|
|
return [strings componentsJoinedByString:@" "];
|
|
}
|
|
|
|
CGAffineTransform IJSVGConcatTransforms(NSArray<IJSVGTransform*>* transforms)
|
|
{
|
|
__block CGAffineTransform trans = CGAffineTransformIdentity;
|
|
IJSVGApplyTransform(transforms, ^(IJSVGTransform* transform) {
|
|
trans = CGAffineTransformConcat(trans, transform.CGAffineTransform);
|
|
});
|
|
return trans;
|
|
}
|
|
|
|
void IJSVGApplyTransform(NSArray<IJSVGTransform*>* 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<IJSVGTransform*>*)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<NSString*>*)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
|