Categories
programming

SVGKit 2013 – Usage

SVG is an awesome image format thats widely used, works in all browsers. SVG graphics make better apps and better games – and automatically “upgrade” themselves for future devices.

This post explains how you can use SVGKit – the open-source SVG implementation for iOS/OS X in your own apps

(NB: basic installation is covered on the GitHub page – this is about how you *use* it)

SVGKit is like Apple’s UIKit

Originally, SVGKit was a bit tricky to use. I re-architected it to be an exact clone of Apple’s UIKit – same classes, same class structure, same method names.

So, we have:

UIKit SVGKit Notes
UI* SVGK* Apple’s standard: library names start with abbreviation of the library name
UIImage SVGKImage SVGKImage.h started as a copy/paste of UIImage.h
UIImageView SVGKImageView You cannot instantiate it directly, have to use a subclass
SVGKFastImageView Renders your SVG as a single layer, fast.
SVGKLayeredImageView Renders your SVG one CALayer per SVG element; you can hilight individual elements, tap them, animatet hem, etc

(a side note: We couldn’t use a classname prefix of “SVG” because the SVG spec reserves all classnames beginning “SVG”. Apple had a similar problem when they invented GLKit – the prefix “GL” was already used in the OpenGL library they were extending, so they named their classes “GLK” prefix. Hence … “SVGK”)

Loading an image

You already know how to load a UIImage, the “easy way”:
[objc]
UIImage* newImage = [UIImage imageNamed:@"myImage.png"];
[/objc]
Well, SVGKit is … the same:
[objc]
SVGKImage* newImage = [SVGKImage imageNamed:@"myImage.svg"];
[/objc]

Displaying an image: UIImageView

And how do you create a UIImageView?
[objc]
UIImageView* imageView = [[UIImageView alloc] initWithImage:newImage];
[/objc]
…so, for SVGKit:
[objc]
SVGKImageView* imageView = [[SVGKFastImageView alloc] initWithImage:newImage];
// ….. or:
SVGKImageView* imageView = [[SVGKLayeredImageView alloc] initWithImage:newImage];
[/objc]
Wait – why is this different?

The reference in both cases is an SVGKImageView – but you cannot alloc-init that class directly. Why?

Different people use the library in different ways. Some people need performance, others need detailed aniamtion. SVG’s contain a lot of bonus info, and it’s not possible to support every use case with a single class. So, you get to choose which one fits your needs

Advanced uses

So far, so easy. What about when you want more control? And how do you load an SVG over the internet (can you load an SVG directly from the web?)

Time to delve a little deeper…

Loading an SVG: in detail

There are four steps to loading an SVG (all of which are done automatically by SVGKImage above):

  1. SVGKSource: specify a location (a file, a filename – or an HTTP URL)
  2. SVGKParser: parse the file (all SVG’s are XML files, so your file can contain any custom XML you want)
  3. SVG classes: convert to SVG’s custom classes (from the SVG Spec); includes automatic support for CSS styling, cascades, etc
  4. SVGKImage: export the SVG to something Apple can render: e.g. a flat CALayer, or a hierarchy of CALayers, etc
  5. Experimental Exporter that saves a .svg file from your SVG file, including any changes you made to the in-memory DOM

Every stage is extensible, so you can either add features, or custommize it

Extend SVGKSource: adding new sources

Easy: create a new subclass of SVGKSource, and implement the two methods.

The method signatures are a little strange, because they have to support fast file-access (a C native library from Apple) as well as modern Objective-C methods.

One example of each: the supplied SVGKSource for loading from disk uses C methods, and the supplied one for loading from a URL uses Objective-C methods.

Extend SVGKParser: custom SVG files and formats

NOTE: do NOT subclass SVGKParser; parsing is very complex, and there’s a special mechanism that makes it easy to extend the parser (read on…)

Parsing is complex, so our parser is modular (based on ideas from a modular XML parser I wrote years ago). The SVGKParser class doesn’t parse SVG directly: instead, it parses raw XML, and converts it to a higher-level version that’s much faster to work with. It also manages error handling, loading bytes from an SVGKSource, etc.

You create a new parser instance with a helper method that includes all the modules you need by default:
[objc]
SVGKParser* parser = [SVGKParser parserWithDefaultParseExtensions];
[/objc]

SVGKParserExtension: adding your own custom parsing

Our SVG implementation is already split into sub-modules. This makes the code easier to maintain – and it makes it much easier to add support for SVG features one-by-one, in parallel with other developers.

Example classes:

  • SVGKParserSVG: parses “Basic” SVG – obvious things that require no special handling. NB: this is the most complex parser extension we have. Most are much simpler
  • SVGKParserGradients: parses SVG Gradients – because these were added later by a different developer (Stich) … likewise, if you’re adding a msising SVG feature, feel free to make a new parser-extension for it, it’s easier!
  • SVGKParserDefsAndUse: the SVG <defs> and <use> tags are much harder to parse than 99% of SVG, since they require complex cross-referencing and instancing. So, we use a separate parser extension to isolate this code.

For one of my games, I wanted to store gameplay data inside the SVG – attach info to particular SVG tags (e.g. give each SVG path a “bonusPoints” attribute).

To do this in a spec-compliant way, you should:

  1. Create a new XML namespace (requires no code: you simply invent a URL on a domain that you own)
  2. Put the namespace in your SVG file, using an <xmlns> tag, and give it a “prefix”, e.g. “my-game”
  3. Everywhere you want your custom attributes, or custom tags, prefix them, e.g.: “Write an SVGKParserExtension, and in the “supportedTags” and “supportedNamespaces” methods, return ONLY your namespace, and either nil, or the set of tags you’ve invented

Once the file is parsed, use the DOM to fetch your data (by definition, SVG parsers are required to be DOM-compliant parsers too)

e.g.:
[objc]
NodeList* myCustomNodes = [svgImage.DOMDocument getElementsByTagNameNS:@"http://my.custom.namespace" localName:@"my-custom-tag"];
[/objc]
(NB: NodeList is defined by the DOM, and is just a wrapper for an NSArray. If you #import “NodeList+Mutable.h”, you can access the NSArray directly, and use ObjectiveC fast enumeration. This is cheating, it’s not in the core SVG spec, which is why it’s hidden inside a separate header file)

Extend SVG Classes: the core SVG Spec

Don’t.

(the only valid reason for doing this is if you find features in the spec that are missing or broken in SVGKit. All other usages, you should be able to do in some other way, more easily, with less risk of your changes being broken when we upgrade SVGKit)

Extend SVGKImage export: export your SVG into custom rendering or to a new file on disk

All the parsed data for the SVGKImage is available to you directly, for exporting:

  • (SVGKImage* image).DOMTree : this property contains the entire parsed SVG-DOM (all the SVG* classes)
  • (SVGKImage* image).CALayerTree : this property contains the SVG converted into Apple’s CALayer classes, renderable directly in OS X and iOS
  • Additionally: “SVGKImage+CGContext.h” has methods to render the SVG into any CGContextRef you supply (or will autocreate one for you)
  • SVGKExporterNSData : creates NSData raw bytes, efficient and fast (good for OpenGL)
  • SVGKExporterUIImage : creates a UIImage instance, which you can use anywhere in UIKit

To save memory and CPU, the “CALayerTree” property isn’t created until you request it; if you don’t want the CALayer’s, they’ll never get created.

Improving SVGKit, fixing bugs, and ultra-advanced development

But SVGKit isn’t perfect: there are still missing features, bugs, etc.\

If you find a bug but can’t understand / fix it, please create a simple-as-possible SVG file and send it to us (create an Issue on the GitHub page, and include a link to the file). If you give us permission, we’ll add it to the suite of “test” images we use to verify each version of SVGKit, and that way it will always work, even with future versions.

If you fix bugs, or have bits you want to fix, please have a look at the SVGKit Development guide.

If you want support or help with SVGKit, the very best thing to do is create an Issue on the SVGKit page. That way, any of the contributors can see your problem and help you out. If you email me directly for help, you’re less likely to get a response (I’m busy, and I work on SVGKit entirely unpaid).

Categories
programming

SVGKit 2013 – Development

SVG is an awesome image format thats widely used, works in all browsers. SVG graphics make better apps and better games – and automatically “upgrade” themselves for future devices.

This post explains the underlying code architecture of SVGKit – the open-source SVG implementation for iOS/OS X; the target audience is developers who want to help improve SVGKit (adding missing features, fixing bugs, or making it more compliant with the SVG Specification)

Goals

Primary goals of the SVGKit project:

  1. 100% compliance with the SVG Specification
  2. Seamless integration with iOS (iPad/iPhone) and OS X
  3. Performance better than PNG/JPG/bitmap graphics
  4. …a library good enough that Apple would have liked to have included it in iOS

NB: the license terms for SVGKit are, without prejudice: “you can do anything you want with this, so long as you give credit to the SVGKit authors for their work”. Many of us are using it in commercial projects.

Core structure

The SVG Specification forces us to split the library into two parts, from the very start:

  1. SVG Spec – 100% defined by the W3 Consortium
  2. Native rendering – approx 10% defined by the W3 Consortium

The SVG Spec does have *some* requirements on the native rendering, and it has a lot of “guidelines” – but on the whole, it’s undefined, so that we can provide an implementation that makes sense on our platform (iOS/OS X).

I’ve divided this up into independent sections:

  1. SVG Spec
    1. Locating an input stream (e.g. a file, or an HTTP URL)
    2. XML parsing (low-level)
    3. DOM parsing from XML
    4. SVG parsing from DOM
  2. Native rendering – approx 10% defined by the W3 Consortium
    1. Conversion from SVG + DOM to SVG data (including: cascading, as per CSS (required by SVG Spec!))
    2. Dynamic changes to render data, to support Vector Graphics (Apple’s runtime support for vectors is – ironically – weak)
    3. Export to disk, using the latest copy of your modified DOM
    4. Export from SVG data to OpenGL (via raw bytes), to Apple’s (CAlayer/UIView), and to arbitrary CGContextRef instances

“Apple’s support for vector graphics is weak”

This was the biggest surprise to me: Apple has spent a decade marketing their OS (Mac / OS X) as “vector based”, etc.

In practice … OS X libraries were usually sparsely documented by Apple, and until iPhone came along, they were messy, buggy, poorly designed, and full of “out of date” methods. With iPhone OS (now renamed “iOS”), Apple cleaned their house out, and made some very lean, clear, logical APIs (with many fewer bugs!). They also – finally – documented it all.

That’s an amazing achievement, it’s very impressive. But along the way (probably to save time) they ignored some parts. The original iPhone’s CPU and GPU were very weak (compared to today), so it’s no surprise that Apple didn’t update their vector graphics libraries. iOS (as of 2013) is still using the under-documented and flawed OS X classes.

(NB: the lack of documentation also means that very few people know how to use Core Animation/Quartz/CALayer for high performance – you have to “experiment” and deduce what Apple *might* be doing, and test extensively. [Incidentally, there’s a lot of misinformation around – rumour and theory, in the absence of official docs from Apple])

Find the link for CALayer and bookmark it. This core class is where Apple’s vector libraries and main rendering intersect. It’s powerful – but it’s ugly and bloated too.

Simple bits, see elsewhere

“Locating an input stream (e.g. a file, or an HTTP URL)”

c.f. the SVGKit Usage post. This stuff is very simple, but it’s lacking features. Would be great for you to add some new SVGKSource subclases, with better features.

“XML parsing (low-level)”

Currently uses libxml (because that is built-in to iOS, OS X, and Xcode).

This wraps libxml, and adds three features:

  1. Captures every parse-error, and provides a list + line numbers when parsing is finished (libxml doesn’t have this feature by default)
  2. Converts low-level libxml C library to high-level ObjectiveC calls
  3. Provides a “modular” parsing system, where parsing code is very simple to write

On the whole, we have NO INTENTION of changing the parser – it works, and its intended to be as simple as possible. It’s really just an upgrade to libxml.

But there’s one thing it’s missing that we’d love to add:

  • Streaming / interrupt-based parsing

This is potentially more efficient in CPU and memory usage (not much, since we HAVE to use DOM – it’s required by the SVG Spec), but requires making the SVGKParser.m class a bit cleverer.

“Export from SVG data to OpenGL/NSData/CALayer/CGContextRef/etc”

Check out the “Exporters” sub-folder. It contains simple example classes – one per exporter – showing how to efficiently use SVGKImage to help you export stuff.

Note that approximately half of all SVG files have NO SIZE! – they are “infinite” – and you want to re-use SVGKImage’s code for calculating “correct”, or “best guess” sizes.

Since UIView uses CALayer’s internally, you can take any CALayer and add it to a UIView ([UIView.layer addSublayer:(CALayer*)myCALayer]).

Complex parts

“DOM parsing from XML”

The way this works is very rigidly defined by the SVG Spec, and you absolutely must stick to the Spec.

DOM is a major web standard, and the SVG authors thought it would save everyone a lot of time to re-use it.

Unfortunately – tragically! – iOS has no DOM implementation:

  1. Apple has a private implementation available in Safari. It’s not entirely private (we had to rename one of our classes because of a careless name from Apple), but Apples policy is “if it’s not explicitly public, we can reject your app for using it”. In theory, we could get access to this via WebKit source, or via an embedded WebView. But it would probably be much slower, and use a lot more memory, than our current native implementation
  2. There are a couple of open-source implementations, most of which have sadly been abandoned by their authors. Also, most of those I looked at are incomplete, and non-compliant; we can’t afford to rely on them.

The process for adding / modifying DOM classes goes like this:

  1. Copy/paste the DOM official class name (including the capitalization)
  2. In the header, paste the HTTP link to the *paragraph* of the DOM specification that defines that DOM class
  3. …then copy/paste the DOM’s interface/class declaration (usually 5-10 lines of code beginning “interface”, and blockquoted)
  4. Copy/paste that a second time, this time as the ObjectiveC Interface
  5. Convert every “variable” to an ObjectiveC @property
    • Note: by definition, you are supposed to replace DOMString with NSString*
  6. Convert every “method” to an ObjectiveC “-(something) methodSomething:(something);” method – NB: do *not* implement as C-methods
  7. Fill the .m file with @synthesize directives
  8. Create a blank method in the .m file for each method, and put an “NSAssert( FALSE, “Not implemented yet” );” in there (or implement it yourself)
  9. Any other DOM classes that are used as variables or method parameters … do all the above again
Hiding NSArray and NSDictionary behind SVG Spec methods

The SVG Spec is designed to work in ANY programming language – so it doesn’t support some core features of ObjectiveC, such as fast enumeration (i.e. the “for( NSObject* o in array)” syntax).

A much bigger problem for you is that you can’t include “init” methods, which are necessary for good ObjectiveC code.

Our DOM and SVG classes *must* be spec compliant, so we cannot expose the raw array – and we can’t add methods to provide fast enumeration, nor custom init methods.

Instead … when you have a situation like that, and you want users to be able to (optionally) access them … go ahead and do it, but put the “bonus” methods into a separate header file.

In Xcode, this is called a “class extension”, and it’s a special feature of ObjectiveC. Select “Class Extension” when creating the new file.

e.g. look at the source for Nodelist.h and NodeList.m – and notice that some of the methods are missing from the header file, but appear in NodeList+Mutable.h

In general, you should use the following naming strategies:

  • If the bonus features are needed to modify properties that SVG Spec says are “read only”, name the extension “Mutable” to make it clear that’s what it’s for
  • If the bonus features are ONLY a convenience, e.g. to enable fast enumeration, name the extension “NotInSpec” or similar

“SVG parsing from DOM”

Again, the SVG spec rigorously defines the name of every “SVG” class, and its methods, and its variables. You must follow these exactly.

The process is identical as for the DOM Spec notes above.

NB: SVG was designed and intended to be implemented on-top-of DOM; many of the SVG Spec methods are trivial to implement if you use the DOM methods that already exist. You are not supposed to re-invent the wheel!

For instance, have a look at DOMDocument, and Node, and Element – they have some very useful methods built-in to them.

Remmember: if you call SVGElement’s init method, then every SVG tag has already been parsed into a DOM Element (which extends DOM Node). It already has all the XML attributes pre-parsed and available to you!

Gotcha 1: SVG attributes are NOT nil

SVG Spec defines that “empty” or “missing” attributes have to be returned NOT AS NULL but as an empty string (“”).

This means you must NEVER write:
[objc]
Attr* fillAttribute = [self getAttribute:@"fill"];
if( fillAttribute ) // DO NOT DO THIS!!!

[/objc]
…because according to the spec fillAttribute can be non-null even though in the SVG it’s blank. Instead, you must (according to spec) do:
[objc]
Attr* fillAttribute = [self getAttribute:@"fill"];
if( fillAttribute.length > 0 ) // This is correct, according to SVG Spec

[/objc]

Gotcha 2: XML Namespaces

You can parse a lot of SVG’s and ignore namespaces; most SVG’s use the same “convention” for naming the XML tags.

It’s a convention; it’s a default; it IS NOT GUARANTEED.

But XML-namespaes are guaranteed. All SVGKit code should be using namespaces explicitly.

As a convenience for users, the DOM spec allows us to provide methods that do NOT need an explicit namespace – but you should not be using them! They will occasionally fail when used on some input SVG files

So, for instance, you should NOT do this:
[objc]
Attr* fillAttribute = [self getAttribute:@"fill"]; // DON’T DO THIS (it’ll work 99% of the time, but … best not to)
[/objc]
instead do this:
[objc]
Attr* fillAttribute = [self getAttributeNS:svgNamespace localName:@"fill"]; // CORRECT. (svgNamespace is the HTTP URL of the official SVG Spec)
[/objc]
At the moment, we don’t have a convenience method for “get the namespace that means SVG” – this really should be part of the SVGKparserSVG extension.

NB: if you’re afraid this namespace stuff won’t work, note that SVGKparser already has full namespace support, and will automatically create the SVG namespace if needed when parsing incoming SVG files

Gotcha 3: Cascading (as in: “Cascading Style Sheets” i.e. CSS)

The SVG spec officially is based on DOM; but it’s also (officially) based on CSS.

Fortunately, we only have to support a subset of CSS – the two parts that SVG uses are:

  1. Embedding stylesheets, or referencing them with an external “link” tag
  2. Cascading

But cascading is tricky. There are approximately 50 XML attributes that – officially – must be “cascaded” when using SVG. There’s a table of them in the spec – http://www.w3.org/TR/SVG/propidx.html

Cascading is tricky, and it’s potentially quite slow – you have to look-up the property in many different places, and check each one “in correct order” until you find the first match.

So, we have a method in SVGElement that does all this for you:
[objc]
-(NSString*) cascadedValueForStylableProperty:(NSString*) stylableProperty
[/objc]
…but that is not part of the SVG Spec, and it’s possible it *is* part of the CSS spec, but located in a different class (I haven’t found it yet). We’ll leave that method there as a convenience, but you might need to import a special header to access it (since it’s not part of the SVG Spec).

To use cascading (which you MUST do), instead of this:
[objc]
// DON’T DO THIS (it ignores cascading and styles and CSS-classes)
Attr* fillAttribute = [self getAttribute:@"fill"];
[/objc]
…do this:
[objc]
// Automatically does all the CSS stuff for you
NSString* fillAttributeValue = [self cascadedValueForStylableProperty:@"fill"];
[/objc]
…eventually, we’ll add an error / NSAssert for cases where you pass in a property that is not one of the cascadeable ones – for now, just use the table as a reference.

Class names and method names

We couldn’t use a classname prefix of “SVG” because the SVG spec reserves all classnames beginning “SVG”. Inside the project, you’ll find all of these in the “SVG DOM” folder – please note: these are MANDATED by the SVG Spec, we did NOT come up with the names.

Apple had a similar problem when they invented GLKit – the prefix “GL” was already used in the OpenGL library they were extending, so they named their classes “GLK” prefix. Hence … “SVGK”)

Whenever you create a new class that is not part of the SVG Spec – for any reason – you must prefix the name with “SVGK”.

Some of our classes – for historic reasons – don’t follow this convention. Yet. Feel free to refactor any you encounter.