Converting SVG Paths to Objective-C Paths – Updated Again!

Further update:

Ariel over at arivibes.com has turned this into a GitHub project and, I believe, greatly improved it: HERE

 

Update: Bob Monaghan from Glue Tools LLC was kind enough to clean up some memory leaks, make the thing compatible with OS X as well as iOS and put in some positioning of the paths in a Rect. The download here reflects this new much improved version.


OS X and iOS have a wonderful library of great drawing and animation tools, many of which are built on Bezier paths. If you’ve created these paths in some other program, such as Photoshop or Illustrator, however, you’ll be left with the task of importing these and converting them to NSBezierPath (on OS X) or UIBezierPath (on iOS) objects. The best way I found to do this was via the SVG format. It’s XML and the paths are represented by «path» tags containing a “d” attribute which is a string containing a series of move, curve and line commands and coordinates. That’s convenient because Illustrator can export as SVG and OS X is good at XML.

What’s missing in this is a parser for the path creation commands. I’ve written one which takes the “d” attribute string and converts it to a UIBezierPath. It’s written for iOS but it would be easy to make it OS X. I left the decomposition of the d attribute string from the SVG text file as a separate piece of code because I’m cutting and pasting the SVG paths into a larger XML document which contains all sorts of program information. It’s perfectly possible, at the simplest, to just search for the string between each ‘d=”‘ and the following ‘”‘ and feed the contents into this code. So far it’s tested against a number of quite complex paths created in Photoshop from the outlines of custom text, imported to Illustrator, then saved as SVG. Be aware, if you do something like this, that for something with an interior space – like an “O” – the two separate lines, the inner and outer circle, must be part of the same path. There will be a ‘z’ command in the middle of that path to close the first line, then commands ending in a ‘z’ to close the second line. Without this a fill command, for example, will fill the entire circle.

To make a bezier path, instantiate an SvgToBezier converter with the “d” attribute string passed to the constructor, then the “bezier” property of that object is the path you can use elsewhere. The code is a single Objective-C class file, although internally it uses a private class called Token. (And, have mercy, I’m new to Objective-C – however critiques of my code are much appreciated.) Here’s the code for that constructor:

[sourcecode language=”cpp”]
– (id) initFromSVGPathNodeDAttr:(NSString *)attr inViewBoxSize:(CGSize)size {
[super init];
if (self) {
pathScale = 0;
viewBox = size;
[self reset];
separatorSet = [NSCharacterSet characterSetWithCharactersInString:separatorCharString];
commandSet = [NSCharacterSet characterSetWithCharactersInString:commandCharString];
NSArray* tokens = [self parsePath:attr];
bezier = [self generateBezier:tokens];
for (Token *token in tokens) {
[token release];
}
[tokens release];
}
return self;
}
[/sourcecode]

Pretty simple, it sets up the valid character sets from constant strings, parses the d attribute as tokens, then generates a bezier from the tokens. “generateBezier” takes each token in turn and draws the command that token embodies into the current path. There are 5 different command families, “move”, “line”, “curve”, “smoothcurve”, and “close path”. Smoothcurve uses the immediately previous curve’s last control handle as its own first control handle. Here’s the code for smoothcurve as an example of how that works:

[sourcecode language=”cpp”]
– (void) appendSVGSCommand: (Token *) token {
if (!validLastControlPoint) {
NSLog(@"Invalid last control point in S command", nil);
}
int index = 0;
while ((index + 3) < [token valence]) { // we must have 4 floats here (x2, y2, x, y).
float x1 = lastControlPoint.x;
float y1 = lastControlPoint.y;
float x2 = [token parameter:index++] + ([token command] == ‘s’ ? lastPoint.x : 0);
float y2 = [token parameter:index++] + ([token command] == ‘s’ ? lastPoint.y : 0);
float x = [token parameter:index++] + ([token command] == ‘s’ ? lastPoint.x : 0);
float y = [token parameter:index++] + ([token command] == ‘s’ ? lastPoint.y : 0);
lastPoint = CGPointMake(x, y);
[bezier addCurveToPoint:[self bezierPoint:lastPoint]
controlPoint1:[self bezierPoint:CGPointMake(x1,y1)]
controlPoint2:[self bezierPoint:CGPointMake(x2, y2)]];
lastControlPoint = CGPointMake(x2, y2);
validLastControlPoint = YES;
}
if (index == 0) {
NSLog(@"Insufficient parameters for S command", nil);
}
}
[/sourcecode]

Full code is here, as a zip. You’re free to use it under the Creative Commons license below so long as you keep the attribution: leave the attribution comments in the headers.

Creative Commons License
SvgToBezier by Ponderwell, llc is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.