Commit 71cec955 authored by Yury Popov's avatar Yury Popov

Most SVG tags parse support

parent 1dc3293f
......@@ -90,6 +90,9 @@
D07DB4FE1B026FC1004733DE /* no_sympathies.svg in Resources */ = {isa = PBXBuildFile; fileRef = D07DB4F31B026FC1004733DE /* no_sympathies.svg */; };
D07DB4FF1B026FC1004733DE /* share.svg in Resources */ = {isa = PBXBuildFile; fileRef = D07DB4F41B026FC1004733DE /* share.svg */; };
D07DB5001B026FC1004733DE /* support.svg in Resources */ = {isa = PBXBuildFile; fileRef = D07DB4F51B026FC1004733DE /* support.svg */; };
D0DCC4D11B0281F700586BF3 /* pxSVGObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D0DCC4D01B0281F700586BF3 /* pxSVGObject.m */; };
D0DCC4D41B02827E00586BF3 /* pxSVGGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = D0DCC4D31B02827E00586BF3 /* pxSVGGroup.m */; };
D0DCC4D71B0284B200586BF3 /* pxSVGPath.m in Sources */ = {isa = PBXBuildFile; fileRef = D0DCC4D61B0284B200586BF3 /* pxSVGPath.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
......@@ -208,6 +211,12 @@
D07DB4F31B026FC1004733DE /* no_sympathies.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = no_sympathies.svg; sourceTree = "<group>"; };
D07DB4F41B026FC1004733DE /* share.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = share.svg; sourceTree = "<group>"; };
D07DB4F51B026FC1004733DE /* support.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = support.svg; sourceTree = "<group>"; };
D0DCC4CF1B0281F700586BF3 /* pxSVGObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pxSVGObject.h; sourceTree = "<group>"; };
D0DCC4D01B0281F700586BF3 /* pxSVGObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = pxSVGObject.m; sourceTree = "<group>"; };
D0DCC4D21B02827E00586BF3 /* pxSVGGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pxSVGGroup.h; sourceTree = "<group>"; };
D0DCC4D31B02827E00586BF3 /* pxSVGGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = pxSVGGroup.m; sourceTree = "<group>"; };
D0DCC4D51B0284B200586BF3 /* pxSVGPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pxSVGPath.h; sourceTree = "<group>"; };
D0DCC4D61B0284B200586BF3 /* pxSVGPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = pxSVGPath.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
......@@ -342,6 +351,7 @@
D07DB4BA1B024A45004733DE /* pxSVG */ = {
isa = PBXGroup;
children = (
D0DCC4CE1B0281DA00586BF3 /* SVG */,
D07DB4DC1B025350004733DE /* Parsers */,
D07DB4D31B024A91004733DE /* Views */,
D07DB4BB1B024A45004733DE /* pxSVG.h */,
......@@ -373,6 +383,19 @@
name = Parsers;
sourceTree = "<group>";
};
D0DCC4CE1B0281DA00586BF3 /* SVG */ = {
isa = PBXGroup;
children = (
D0DCC4CF1B0281F700586BF3 /* pxSVGObject.h */,
D0DCC4D01B0281F700586BF3 /* pxSVGObject.m */,
D0DCC4D21B02827E00586BF3 /* pxSVGGroup.h */,
D0DCC4D31B02827E00586BF3 /* pxSVGGroup.m */,
D0DCC4D51B0284B200586BF3 /* pxSVGPath.h */,
D0DCC4D61B0284B200586BF3 /* pxSVGPath.m */,
);
name = SVG;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
......@@ -546,6 +569,9 @@
D07DB4D61B024AAA004733DE /* pxSVGLayer.m in Sources */,
D07DB4EA1B026BF8004733DE /* pxSVGRenderPath.m in Sources */,
D07DB4E41B026959004733DE /* pxXMLNode.m in Sources */,
D0DCC4D71B0284B200586BF3 /* pxSVGPath.m in Sources */,
D0DCC4D11B0281F700586BF3 /* pxSVGObject.m in Sources */,
D0DCC4D41B02827E00586BF3 /* pxSVGGroup.m in Sources */,
D07DB4D91B024AC2004733DE /* pxSVGView.m in Sources */,
D07DB4DF1B025366004733DE /* pxSVGImage.m in Sources */,
);
......@@ -707,6 +733,7 @@
D07DB4381B024475004733DE /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
D07DB4CA1B024A45004733DE /* Build configuration list for PBXNativeTarget "pxSVG" */ = {
isa = XCConfigurationList;
......@@ -715,6 +742,7 @@
D07DB4CC1B024A45004733DE /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
......
//
// pxSVGGroup.h
// pxSVG
//
// Created by Yury Popov on 12.05.15.
// Copyright (c) 2015 PhoeniX. All rights reserved.
//
#import "pxSVGObject.h"
@interface pxSVGGroup : pxSVGObject
@property NSArray *subnodes;
@end
//
// pxSVGGroup.m
// pxSVG
//
// Created by Yury Popov on 12.05.15.
// Copyright (c) 2015 PhoeniX. All rights reserved.
//
#import "pxSVGGroup.h"
@implementation pxSVGGroup
@end
//
// pxSVGObject.h
// pxSVG
//
// Created by Yury Popov on 12.05.15.
// Copyright (c) 2015 PhoeniX. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface pxSVGObject : NSObject
- (void) loadAttributes:(NSDictionary*)attributes;
- (void) setSubnodes:(NSArray*)subnodes;
@property NSString *id;
@property NSArray *animations;
@property UIColor *fillColor;
@property UIColor *strokeColor;
@property CGFloat strokeWidth;
@property CGFloat opacity;
@property CATransform3D transform;
@end
//
// pxSVGObject.m
// pxSVG
//
// Created by Yury Popov on 12.05.15.
// Copyright (c) 2015 PhoeniX. All rights reserved.
//
#import "pxSVGObject.h"
@implementation pxSVGObject
+ (CATransform3D) transformFromString:(NSString*)string
{
CATransform3D tr = CATransform3DIdentity;
if (!string.length) return tr;
NSScanner *sc = [NSScanner scannerWithString:string];
sc.charactersToBeSkipped = [NSCharacterSet whitespaceAndNewlineCharacterSet];
NSString *op; double p[6]; int i;
while (!sc.atEnd) {
[sc scanUpToString:@"(" intoString:&op];
[sc scanString:@"(" intoString:nil];
i=0;
while ([sc scanDouble:&p[i++]]) [sc scanCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@" ,"] intoString:nil];
i--;
[sc scanString:@")" intoString:nil];
if ([op isEqualToString:@"scale"]) {
switch (i) {
case 1:
tr = CATransform3DScale(tr, p[0], p[0], 1);
break;
case 2:
tr = CATransform3DScale(tr, p[0], p[1], 1);
break;
default: NSLog(@"Invalid number of operands for %@: %@",op,@(i)); break;
}
} else if ([op isEqualToString:@"translate"]) {
switch (i) {
case 1:
tr = CATransform3DTranslate(tr, p[0], 0, 0);
break;
case 2:
tr = CATransform3DTranslate(tr, p[0], p[1], 0);
break;
default: NSLog(@"Invalid number of operands for %@: %@",op,@(i)); break;
}
} else if ([op isEqualToString:@"matrix"]) {
switch (i) {
case 6:
tr = CATransform3DConcat(tr, CATransform3DMakeAffineTransform(CGAffineTransformMake(p[0], p[1], p[2], p[3], p[4], p[5])));
break;
default: NSLog(@"Invalid number of operands for %@: %@",op,@(i)); break;
}
} else {
NSLog(@"Unknown transform: %@",op);
}
}
return tr;
}
- (UIColor*) colorWithSVGColor:(NSString*)string
{
if (!string) return nil;
string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if ([string isEqualToString:@"none"]) return nil;
if ([string isEqualToString:@"black"]) return [UIColor blackColor];
if ([string isEqualToString:@"white"]) return [UIColor whiteColor];
NSScanner *sc = [NSScanner scannerWithString:[string lowercaseString]];
if ([sc scanString:@"#" intoString:nil]) {
unsigned int cl;
[sc scanHexInt:&cl];
float r,g,b;
switch (string.length) {
case 4:
b = (cl & 0xF)<<4; cl >>= 4;
g = (cl & 0xF)<<4; cl >>= 4;
r = (cl & 0xF)<<4; cl >>= 4;
break;
case 7:
b = cl & 0xFF; cl >>= 8;
g = cl & 0xFF; cl >>= 8;
r = cl & 0xFF; cl >>= 8;
break;
default:
NSLog(@"Invalid hex color: %@",string);
return nil;
}
r /= 255.f;
g /= 255.f;
b /= 255.f;
return [UIColor colorWithRed:r green:g blue:b alpha:1];
}
NSLog(@"Unknown color: %@",string);
return nil;
}
- (void)loadAttributes:(NSDictionary *)attributes
{
self.id = [attributes objectForKey:@"id"];
self.fillColor = [self colorWithSVGColor:[attributes objectForKey:@"fill"]];
self.strokeColor = [self colorWithSVGColor:[attributes objectForKey:@"stroke"]];
self.strokeWidth = [[attributes objectForKey:@"stroke-width"] doubleValue];
self.transform = [self.class transformFromString:[attributes objectForKey:@"transform"]];
self.opacity = [[attributes objectForKey:@"opacity"] doubleValue];
}
- (void)setSubnodes:(NSArray *)subnodes { }
@end
//
// pxSVGPath.h
// pxSVG
//
// Created by Yury Popov on 12.05.15.
// Copyright (c) 2015 PhoeniX. All rights reserved.
//
#import "pxSVGObject.h"
@interface pxSVGPath : pxSVGObject
@property UIBezierPath *d;
@end
//
// pxSVGPath.m
// pxSVG
//
// Created by Yury Popov on 12.05.15.
// Copyright (c) 2015 PhoeniX. All rights reserved.
//
#import "pxSVGPath.h"
@interface NSScanner (CGPoint)
- (CGPoint) scanCGPoint;
- (CGPoint) scanCGPointWithOffset:(CGPoint)off;
@end
@implementation NSScanner (CGPoint)
- (CGPoint) scanCGPoint
{
double x,y;
[self scanString:@"," intoString:nil];
[self scanDouble:&x];
[self scanString:@"," intoString:nil];
[self scanDouble:&y];
return (CGPoint){x,y};
}
- (CGPoint)scanCGPointWithOffset:(CGPoint)off
{
CGPoint p = [self scanCGPoint];
return (CGPoint){p.x+off.x,p.y+off.y};
}
@end
@implementation pxSVGPath
- (UIBezierPath*)bezierPathWithString:(NSString*)string
{
static NSCharacterSet *cmds; if (!cmds) cmds = [NSCharacterSet characterSetWithCharactersInString:@"MmCcSsLlHhVvAaZz"];
NSScanner *scanner = [NSScanner scannerWithString:string];
[scanner setCharactersToBeSkipped:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *cmd; unichar lastCmd = 0;
UIBezierPath *bp = [UIBezierPath new];
CGPoint p = CGPointZero, c1, c2;
double d, r, s;
while (!scanner.atEnd) {
if (cmd.length>1) cmd=[cmd substringFromIndex:1];
else if (![scanner scanCharactersFromSet:cmds intoString:&cmd]) {
if ([cmds characterIsMember:lastCmd]) {
cmd = [NSString stringWithCharacters:&lastCmd length:1];
} else {
NSLog(@"Unknown character: %@",[string substringWithRange:(NSRange){scanner.scanLocation,1}]);
}
break;
}
lastCmd = [cmd characterAtIndex:0];
switch ([cmd characterAtIndex:0]) {
case 'M':
[bp moveToPoint:p = [scanner scanCGPoint]];
break;
case 'm':
[bp moveToPoint:p = [scanner scanCGPointWithOffset:p]];
break;
case 'C':
c1 = [scanner scanCGPoint];
c2 = [scanner scanCGPoint];
[bp addCurveToPoint:p=[scanner scanCGPoint] controlPoint1:c1 controlPoint2:c2];
break;
case 'c':
c1 = [scanner scanCGPointWithOffset:p];
c2 = [scanner scanCGPointWithOffset:p];
[bp addCurveToPoint:p = [scanner scanCGPointWithOffset:p] controlPoint1:c1 controlPoint2:c2];
break;
case 'S':
c1 = (CGPoint){p.x*2.f-c2.x,p.y*2.f-c2.y};
c2 = [scanner scanCGPoint];
p = [scanner scanCGPoint];
[bp addCurveToPoint:p controlPoint1:c1 controlPoint2:c2];
break;
case 's':
c1 = (CGPoint){p.x*2.f-c2.x,p.y*2.f-c2.y};
c2 = [scanner scanCGPointWithOffset:p];
p = [scanner scanCGPointWithOffset:p];
[bp addCurveToPoint:p controlPoint1:c1 controlPoint2:c2];
break;
case 'A':
c2 = [scanner scanCGPoint]; // r
[scanner scanString:@"," intoString:nil];
[scanner scanDouble:&d]; d *= M_PI; d /= 180.f; // a
[scanner scanString:@"," intoString:nil];
[scanner scanDouble:nil];
[scanner scanString:@"," intoString:nil];
[scanner scanDouble:nil];
c1 = p;
p = [scanner scanCGPoint];
c1 = (CGPoint){(c1.x+p.x)/2.f,(c1.y+p.y)/2.f};
r = sqrt((c2.x-c1.x)*(c2.x-c1.x)+(c2.y-c1.y)*(c2.y-c1.y));
s = atan2(p.x-c1.x, p.y-c1.y)+M_PI_2;
[bp addArcWithCenter:c1 radius:r startAngle:s endAngle:s+d clockwise:d>0];
break;
case 'a':
c2 = [scanner scanCGPointWithOffset:p]; // r
[scanner scanString:@"," intoString:nil];
[scanner scanDouble:&d]; d *= M_PI; d /= 180.f; // a
[scanner scanString:@"," intoString:nil];
[scanner scanDouble:nil];
[scanner scanString:@"," intoString:nil];
[scanner scanDouble:nil];
c1 = p;
p = [scanner scanCGPointWithOffset:p];
c1 = (CGPoint){(c1.x+p.x)/2.f,(c1.y+p.y)/2.f};
r = sqrt((c2.x-c1.x)*(c2.x-c1.x)+(c2.y-c1.y)*(c2.y-c1.y));
s = atan2(p.x-c1.x, p.y-c1.y)+M_PI_2;
[bp addArcWithCenter:c1 radius:r startAngle:s endAngle:s+d clockwise:d>0];
break;
case 'V':
[scanner scanDouble:&d]; p.y = d;
[bp addLineToPoint:p];
break;
case 'v':
[scanner scanDouble:&d]; p.y+= d;
[bp addLineToPoint:p];
break;
case 'H':
[scanner scanDouble:&d]; p.x = d;
[bp addLineToPoint:p];
break;
case 'h':
[scanner scanDouble:&d]; p.x+= d;
[bp addLineToPoint:p];
break;
case 'L':
[bp addLineToPoint:p=[scanner scanCGPoint]];
break;
case 'l':
[bp addLineToPoint:p=[scanner scanCGPointWithOffset:p]];
break;
case 'z':
case 'Z':
[bp closePath];
break;
default:
NSLog(@"Unknown command: %@",cmd);
return nil;
}
}
return bp;
}
- (UIBezierPath*)bezierPolygonWithString:(NSString*)string
{
static NSCharacterSet *cmds; if (!cmds) cmds = [NSCharacterSet characterSetWithCharactersInString:@"MmCcSsLlHhVvAaZz"];
NSScanner *scanner = [NSScanner scannerWithString:string];
[scanner setCharactersToBeSkipped:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
UIBezierPath *bp = [UIBezierPath new];
[bp moveToPoint:[scanner scanCGPoint]];
while (!scanner.atEnd) {
[bp addLineToPoint:[scanner scanCGPoint]];
}
[bp closePath];
return bp;
}
- (void)loadAttributes:(NSDictionary *)attributes
{
[super loadAttributes:attributes];
if ([attributes objectForKey:@"d"]) self.d = [self bezierPathWithString:[attributes objectForKey:@"d"]];
else if ([attributes objectForKey:@"points"]) self.d = [self bezierPolygonWithString:[attributes objectForKey:@"points"]];
else if ([attributes objectForKey:@"cx"] &&
[attributes objectForKey:@"cy"] &&
[attributes objectForKey:@"rx"] &&
[attributes objectForKey:@"ry"]) {
CGRect r;
r.size.width = [[attributes objectForKey:@"rx"] doubleValue];
r.size.height = [[attributes objectForKey:@"rx"] doubleValue];
r.origin.x = [[attributes objectForKey:@"cx"] doubleValue] - r.size.width;
r.origin.y = [[attributes objectForKey:@"cy"] doubleValue] - r.size.height;
r.size.width *= 2; r.size.height *= 2;
self.d = [UIBezierPath bezierPathWithOvalInRect:r];
}
else if ([attributes objectForKey:@"cx"] &&
[attributes objectForKey:@"cy"] &&
[attributes objectForKey:@"r"]) {
CGRect r;
r.size.width = [[attributes objectForKey:@"r"] doubleValue];
r.size.height = [[attributes objectForKey:@"r"] doubleValue];
r.origin.x = [[attributes objectForKey:@"cx"] doubleValue] - r.size.width;
r.origin.y = [[attributes objectForKey:@"cy"] doubleValue] - r.size.height;
r.size.width *= 2; r.size.height *= 2;
self.d = [UIBezierPath bezierPathWithOvalInRect:r];
}
else if ([attributes objectForKey:@"width"] &&
[attributes objectForKey:@"height"]) {
CGRect r;
r.size.width = [[attributes objectForKey:@"width"] doubleValue];
r.size.height = [[attributes objectForKey:@"height"] doubleValue];
r.origin.x = [[attributes objectForKey:@"x"] doubleValue];
r.origin.y = [[attributes objectForKey:@"y"] doubleValue];
r.size.width *= 2; r.size.height *= 2;
self.d = [UIBezierPath bezierPathWithRect:r];
} else NSLog(@"%@",attributes);
}
@end
......@@ -7,10 +7,12 @@
//
#import "pxSVGRenderPath.h"
#import "pxSVGGroup.h"
#import "pxSVGPath.h"
@interface pxSVGRenderPath ()
@property NSDictionary *defs;
//@property pxSVGGroup *root;
@property pxSVGObject *root;
@property CGRect bounds;
@end
......@@ -52,6 +54,39 @@
} else {
self.bounds = CGRectNull;
}
self.root = [self parseObject:xmlNode];
return self;
}
- (pxSVGObject*)parseObject:(pxXMLNode*)node
{
if ([node.tagName rangeOfString:@":"].location != NSNotFound) return nil;
if ([node.tagName isEqualToString:@"metadata"]) return nil;
Class objClass = pxSVGObject.class;
if ([node.tagName isEqualToString:@"g"])
objClass = pxSVGGroup.class;
else if ([node.tagName isEqualToString:@"svg"])
objClass = pxSVGGroup.class;
else if ([node.tagName isEqualToString:@"path"])
objClass = pxSVGPath.class;
else if ([node.tagName isEqualToString:@"polygon"])
objClass = pxSVGPath.class;
else if ([node.tagName isEqualToString:@"ellipse"])
objClass = pxSVGPath.class;
else if ([node.tagName isEqualToString:@"circle"])
objClass = pxSVGPath.class;
else if ([node.tagName isEqualToString:@"rect"])
objClass = pxSVGPath.class;
else NSLog(@"Unknown tag: %@",node.tagName);
pxSVGObject *obj = [objClass new];
[obj loadAttributes:node.attributes];
if (node.childNodes.count) {
NSMutableArray *subnodes = [NSMutableArray new];
for (pxXMLNode *n in node.childNodes) {
pxSVGObject *o = [self parseObject:n];
if (o) [subnodes addObject:o];
}
[obj setSubnodes:[NSArray arrayWithArray:subnodes]];
}
return obj;
}
@end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment