Files
IJSVG/source/IJSVGExporter.m
T
Curtis Hard 1840c4eaa1 Squashed commit of the following:
commit 912bdd5029e2afab7736b36b694fb77e9b82fe7d
Author: Curtis Hard <curtishard@me.com>
Date:   Sat Jan 14 12:00:34 2017 +0000

    Various formatting changes

commit 3de9a8a00df4391c1be592cdda63fcd7cc49511b
Author: Curtis Hard <curtishard@me.com>
Date:   Sat Jan 14 12:00:20 2017 +0000

    Updated to use the backing sclae helper

commit b06b74b2b5d22c1b7d4cfad789d97cc1319e6961
Author: Curtis Hard <curtishard@me.com>
Date:   Sat Jan 14 11:48:15 2017 +0000

    Formatting fixes

commit e3fbca0f3b44f6a5c282e1e43b711addefc9b3fc
Author: Curtis Hard <curtishard@me.com>
Date:   Sat Jan 14 00:20:40 2017 +0000

    Fixes for SVG sprite rendereing / exporting with correct color

commit 0cc40c6e923865dc93e7912fd5a61dba88b3647a
Author: Curtis Hard <curtishard@me.com>
Date:   Sat Jan 14 00:10:46 2017 +0000

    Unit swapping to much better object way

commit 7a3d41d071ab510704ea0803f93804794c330467
Author: Curtis Hard <curtishard@me.com>
Date:   Fri Jan 13 14:58:11 2017 +0000

    Added more default values

commit 55b397b15deb0ffa39611f7ad2c840d9d1982adc
Author: Curtis Hard <curtishard@me.com>
Date:   Thu Jan 12 23:23:15 2017 +0000

    Made constant strings for all parsed attributes

commit 3e0423ba0ed6b8c223c0684c9abf87ee9e8264c7
Author: Curtis Hard <curtishard@me.com>
Date:   Thu Jan 12 21:47:00 2017 +0000

    More fixes to parsing attribuets

commit 2558f4e07feea1730df191f08cc110ecf1dbd963
Author: Curtis Hard <curtishard@me.com>
Date:   Thu Jan 12 21:42:42 2017 +0000

    Various changes

commit 64a5d8362209ae1ae53bae3ff65c48dca2c08324
Author: Curtis Hard <curtishard@me.com>
Date:   Thu Jan 12 16:36:45 2017 +0000

    Refactor of attribute parsing

commit 3b1274baa744aadb8b14a79f759dc11edd32f8fc
Author: Curtis Hard <curtishard@me.com>
Date:   Wed Jan 11 21:11:37 2017 +0000

    Fixes memory leaks

    - For the more interested, threading… wrap the dealloc method of any custom CALayers in a transaction lock or the sublayers get leaked… -_-

commit 059da0ce1965fa39b1ccd6b69f14aee296c67ecb
Author: Curtis Hard <curtishard@me.com>
Date:   Wed Jan 11 08:27:25 2017 +0000

    Changed backing scale implementation

commit 421a4afff25a0cb31aa2ff1574d4303911a65448
Author: Curtis Hard <curtishard@me.com>
Date:   Tue Jan 10 22:09:51 2017 +0000

    Fixes for coordinate and percent workings

commit cdbae16d634a4f510ea700925bfa4ba225eac6f6
Author: Curtis Hard <curtishard@me.com>
Date:   Tue Jan 10 21:57:25 2017 +0000

    Implemented a backingScaleHelper callback to help the sublayers that have custom content have the correct scale

commit d05bf062be5b8c5bc0577438d58e38f55f4c060e
Author: Curtis Hard <curtishard@me.com>
Date:   Tue Jan 10 15:25:59 2017 +0000

    Updated the colour space

commit c96571a11bd3ae30c004ab53c36a853a01d1784d
Author: Curtis Hard <curtishard@me.com>
Date:   Tue Jan 10 09:05:51 2017 +0000

    Fixes image not pulling through

commit 92f60485a0ecf48f26957daff0a528dac4601cc8
Author: Curtis Hard <curtishard@me.com>
Date:   Tue Jan 10 08:36:26 2017 +0000

    Varous fixes and improvements to masking

commit abc7573a52789033095cd86680fac480e91272d9
Author: Curtis Hard <curtishard@me.com>
Date:   Mon Jan 9 23:27:21 2017 +0000

    Much better pattern parsing and usage

    - Actually almost conforms to the spec….almost…

commit 4635de5af72fd0a4e94f9278eb37fd7b0d8dd02a
Author: Curtis Hard <curtishard@me.com>
Date:   Mon Jan 9 18:25:38 2017 +0000

    Added ability to init an SVG via raw IJSVGLayers (obviously need to know how the structure works)

    - Also added ability to initialize with an image (creates the group on the fly)

commit 94ab4be7a350accad53d653bad8f7a892a4791ce
Author: Curtis Hard <curtishard@me.com>
Date:   Mon Jan 9 14:27:18 2017 +0000

    Fixes object bounding box on masking

commit 618aba584de37d316c16ace12be768936529cc4d
Author: Curtis Hard <curtishard@me.com>
Date:   Mon Jan 9 13:08:50 2017 +0000

    Added collpasing of groups that are siblings and are the same

commit 027617129a39606600e595444c3aee057849ca17
Author: Curtis Hard <curtishard@me.com>
Date:   Sun Jan 8 23:04:48 2017 +0000

    Formatting and refactor

commit b2cf4ef67249346635aa8bba91894d622c137df7
Author: Curtis Hard <curtishard@me.com>
Date:   Sun Jan 8 22:55:59 2017 +0000

    Relative path transformations

commit 8d7cca01b9289079a5f0c5881cbb2403c74728eb
Author: Curtis Hard <curtishard@me.com>
Date:   Sun Jan 8 19:40:48 2017 +0000

    Added collapsing of groups

commit 9b742e641983ea6f33f0c7abd5cc17e78478e7b0
Author: Curtis Hard <curtishard@me.com>
Date:   Sun Jan 8 18:41:53 2017 +0000

    More things!

commit 42b8d8eb0e96155717c606ebbf856c9c5ae790ae
Author: Curtis Hard <curtishard@me.com>
Date:   Sun Jan 8 17:16:32 2017 +0000

    Various unneeded stuff removed

commit b1fd970f81a491e4d153ecb0d9beb6394701d2cd
Author: Curtis Hard <curtishard@me.com>
Date:   Sun Jan 8 14:42:08 2017 +0000

    Added data method and extra options

commit 198f28a63cbe8cf40faf2261a2875c31ff59fcad
Author: Curtis Hard <curtishard@me.com>
Date:   Sun Jan 8 14:27:33 2017 +0000

    More export stuff, man im tired….

commit b352fb3fb7cb0b8512105cffc50d6f54632193a5
Author: Curtis Hard <curtishard@me.com>
Date:   Sat Jan 7 22:53:20 2017 +0000

    Absolutely stupid insane task…but somehow worked…

commit d826387279cf4280d88f740b09ba9099b4dc2e64
Author: Curtis Hard <curtishard@me.com>
Date:   Fri Jan 6 23:34:06 2017 +0000

    Initial steps for exporting a layer tree back to SVG

commit ffe296fa709cd73023a213cfd97d978cc642e85e
Author: Curtis Hard <curtishard@me.com>
Date:   Wed Jan 4 16:13:46 2017 +0000

    Multi thread issue fixes… seems to do the trick…

commit b7550f41b7fb6dd5738f43434089f0b98299300e
Author: Curtis Hard <curtishard@me.com>
Date:   Wed Jan 4 12:29:11 2017 +0000

    Various performance increases

commit f4bea2ff3c146b51ef5383a1ffa34d1690ec8e8f
Author: Curtis Hard <curtishard@me.com>
Date:   Tue Jan 3 23:41:48 2017 +0000

    Added mask and clip-path to be parsed from styles

commit 159d6bbb846ada5f8684697b0ea38bbc98180a8d
Author: Curtis Hard <curtishard@me.com>
Date:   Tue Jan 3 22:59:43 2017 +0000

    Added fillColor and strokeColor to IJSVG

commit a298d8ef71bfa50ca01eb7b895abc91a9b525817
Author: Curtis Hard <curtishard@me.com>
Date:   Mon Jan 2 22:11:34 2017 +0000

    Check notes….

    - Added discardDOM to IJSVG, this allows you to use the cache more effectly but clear out the DOM behind it, what this will do is actually generate the layer tree and then release the DOM from memory, once its gone, its gone. Have to clear the cache object the alloc init another SVG object with same URL to regenerate.

    - Added setEvictItemsAfter method to cache, allows you to tell the cache how many items you want to be able to cache

commit c2fe30945c1d238b88d6145461a1d4bc037fbe26
Author: Curtis Hard <curtishard@me.com>
Date:   Mon Jan 2 21:37:50 2017 +0000

    Fixes bug...

commit 58aa9c5db76f6d2b72aa4bc051ed82d8d6ce98fb
Author: Curtis Hard <curtishard@me.com>
Date:   Mon Jan 2 21:22:27 2017 +0000

    Fixes to sub SVG sprite sheets not needing to be corrected

commit 05d010ad9622090a17932b6f089603c8c99199c7
Author: Curtis Hard <curtishard@me.com>
Date:   Mon Jan 2 21:11:25 2017 +0000

    Addes drawsAsynchronously

    - Check the header of IJSVG to see what it actually does

commit ff4229fbe27b5e431676a1e2516cfa1488de39a7
Author: Curtis Hard <curtishard@me.com>
Date:   Mon Jan 2 21:10:49 2017 +0000

    Fixes memory leak with sprite sheets

commit 87fabd6475c5cf4d0bde73ec5c7f64df11b16cd8
Author: Curtis Hard <curtishard@me.com>
Date:   Mon Jan 2 20:57:34 2017 +0000

    Fixes retain cycle

commit ddce67f456d973e5d785e6daef482c5f50563f06
Author: Curtis Hard <curtishard@me.com>
Date:   Mon Jan 2 19:42:48 2017 +0000

    Various threading issues sorted

commit 79d7ac935e8e788609572d6580dda70385dacb2e
Author: Curtis Hard <curtishard@me.com>
Date:   Mon Jan 2 17:42:54 2017 +0000

    Few fixes and added beginnings of what will be text

commit ccc24ca92f989273dc8474788f22148415359a8a
Author: Curtis Hard <curtishard@me.com>
Date:   Sun Jan 1 14:20:37 2017 +0000

    Few tweaks

commit 21930ed7116f96db6dc08d8d71d55bea2d817e3b
Author: Curtis Hard <curtishard@me.com>
Date:   Sun Jan 1 11:52:42 2017 +0000

    Turned off caching by default

commit c1449c7edfc8d95219092d76495d0591c8f60599
Author: Curtis Hard <curtishard@me.com>
Date:   Sun Jan 1 11:49:28 2017 +0000

    Better frame manipulation

commit da19869b99808eb9ef1f4e42417267abdfd120b4
Author: Curtis Hard <curtishard@me.com>
Date:   Sun Jan 1 11:28:08 2017 +0000

    Back to basics

commit d05176fdc1f4913c195dd32c0ef8beb18f64122f
Author: Curtis Hard <curtishard@me.com>
Date:   Sun Jan 1 11:12:40 2017 +0000

    Fixes issue with closing of paths

    Basically we dont want to close if the original bezier path parsed from the file didnt too!

commit 79622ef1bacbe382c848b1359044a126cea7ec08
Author: Curtis Hard <curtishard@me.com>
Date:   Sat Dec 31 19:29:19 2016 +0000

    more fixes

commit 6a918f481b2257372c46eb0db81e50f8ddb220fa
Author: Curtis Hard <curtishard@me.com>
Date:   Sat Dec 31 18:14:38 2016 +0000

    Added skewX and skewY transforms

commit f9fb22d5cb072ce0b00df7c7748c3faf17ab3ab5
Author: Curtis Hard <curtishard@me.com>
Date:   Fri Dec 30 23:55:34 2016 +0000

    fixes memory leak for paths

commit 5ddd8dca37d59a3bc31ce43a2c97c7cbffcd5670
Author: Curtis Hard <curtishard@me.com>
Date:   Fri Dec 30 23:47:59 2016 +0000

    Sub SVG rendering

commit adcbb5f2ce9466a55711448c80a0963967c43e23
Author: Curtis Hard <curtishard@me.com>
Date:   Fri Dec 30 23:25:48 2016 +0000

    Thread fixes

commit 92a71e31c6e450fa4b75f110601e8d7fb21bda2c
Author: Curtis Hard <curtishard@me.com>
Date:   Fri Dec 30 22:17:54 2016 +0000

    More fixes to renderer

commit 44670b546218f07a343440fa5418444433149c08
Author: Curtis Hard <curtishard@me.com>
Date:   Fri Dec 30 15:42:35 2016 +0000

    testing

commit 0b6de6278eff4d9906d016e8a88af31319410db3
Author: Curtis Hard <curtishard@me.com>
Date:   Fri Dec 30 12:44:03 2016 +0000

    More fixes to rendering

commit 1273960ab247fd9219c2fb2aa24289b10fab1767
Author: Curtis Hard <curtishard@me.com>
Date:   Fri Dec 30 09:01:02 2016 +0000

    Fixes gradient drawing issues within CALayers

commit 84e884e7a1e7a2ee2258f7bd9ca3d376b17edbd7
Author: Curtis Hard <curtishard@me.com>
Date:   Fri Dec 30 08:28:01 2016 +0000

    Fixes to image rendering

commit 782c936f1562c71f3c37fe5ffb9c5f835d3f61a4
Author: Curtis Hard <curtishard@me.com>
Date:   Thu Dec 29 23:39:58 2016 +0000

    More things towards patterns

commit 58d3a1ca405d55c6ea78f6d914e96dbd3358404c
Author: Curtis Hard <curtishard@me.com>
Date:   Thu Dec 29 16:44:38 2016 +0000

    More calayer rendering stuff

commit d3ff49d6cdcc73cb8b80c4a3cd3c852d8c8931d2
Author: Curtis Hard <curtishard@me.com>
Date:   Thu Dec 29 15:10:04 2016 +0000

    Intro to calayer based rendering
2017-01-15 12:02:21 +00:00

1087 lines
37 KiB
Objective-C

//
// IJSVGExporter.m
// IJSVGExample
//
// Created by Curtis Hard on 06/01/2017.
// Copyright © 2017 Curtis Hard. All rights reserved.
//
#import "IJSVGExporter.h"
#import "IJSVG.h"
#import "IJSVGGradientLayer.h"
#import "IJSVGRadialGradient.h"
#import "IJSVGLinearGradient.h"
#import "IJSVGPatternLayer.h"
#import "IJSVGImageLayer.h"
#import "IJSVGShapeLayer.h"
#import "IJSVGGroupLayer.h"
#import "IJSVGStrokeLayer.h"
#import "IJSVGMath.h"
#import "IJSVGExporterPathInstruction.h"
@implementation IJSVGExporter
#define XML_DOC_VERSION 1.1f
#define XML_DOC_NS @"http://www.w3.org/2000/svg"
#define XML_DOC_NSXLINK @"http://www.w3.org/1999/xlink"
#define XML_DOCTYPE_VERSION @"1.0"
#define XML_DOC_CHARSET @"UTF-8"
#define XML_DOC_GENERATOR @"Generated by IJSVG (https://github.com/curthard89/IJSVG)"
@synthesize title;
@synthesize description;
const NSArray * IJSVGInheritableAttributes()
{
static NSArray * _attributes;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_attributes = [@[
@"clip-rule",
@"color",
@"color-interpolation",
@"color-interpolation-filters",
@"color-profile",
@"color-rendering",
@"cursor",
@"direction",
@"fill",
@"fill-opacity",
@"fill-rule",
@"font",
@"font-family",
@"font-size",
@"font-size-adjust",
@"font-stretch",
@"font-style",
@"font-variant",
@"font-weight",
@"glyph-orientation-horizontal",
@"glyph-orientation-vertical",
@"image-rendering",
@"kerning",
@"letter-spacing",
@"marker",
@"marker-end",
@"marker-mid",
@"marker-start",
@"pointer-events",
@"shape-rendering",
@"stroke",
@"stroke-dasharray",
@"stroke-dashoffset",
@"stroke-linecap",
@"stroke-linejoin",
@"stroke-miterlimit",
@"stroke-opacity",
@"stroke-width",
@"text-anchor",
@"text-rendering",
@"visibility",
@"white-space",
@"word-spacing",
@"writing-mode"] retain];
});
return _attributes;
}
void IJSVGApplyAttributesToElement(NSDictionary *attributes, NSXMLElement *element) {
[element setAttributesAsDictionary:attributes];
};
NSDictionary * IJSVGElementAttributeDictionary(NSXMLElement * element) {
NSMutableDictionary * dict = [[[NSMutableDictionary alloc] init] autorelease];
for(NSXMLNode * attribute in element.attributes) {
dict[attribute.name] = attribute.stringValue;
}
return dict;
};
NSString * IJSVGShortFloatString(CGFloat f)
{
return [NSString stringWithFormat:@"%g",f];
};
NSString * IJSVGHashURL(NSString * key) {
return [NSString stringWithFormat:@"url(#%@)",key];
};
NSString * IJSVGHash(NSString * key) {
return [@"#" stringByAppendingString:key];
}
- (void)dealloc
{
[_svg release], _svg = nil;
[_dom release], _dom = nil;
[title release], title = nil;
[description release], description = nil;
[super dealloc];
}
- (id)initWithSVG:(IJSVG *)svg
options:(IJSVGExporterOptions)options
{
if((self = [super init]) != nil) {
_options = options;
_svg = [svg retain];
// clear memory as soon as possible
@autoreleasepool {
[self _prepare];
}
}
return self;
}
- (NSXMLElement *)defElement
{
if(_defElement != nil) {
return _defElement;
}
return _defElement = [[NSXMLElement alloc] initWithName:@"defs"];
}
- (NSXMLElement *)rootNode
{
// generates the root document
NSXMLElement * root = [[[NSXMLElement alloc] initWithName:@"svg"] autorelease];
// sort out viewbox
NSRect viewBox = _svg.viewBox;
NSDictionary * attributes = @{
@"viewBox":[NSString stringWithFormat:@"%g %g %g %g",
viewBox.origin.x, viewBox.origin.y,
viewBox.size.width, viewBox.size.height],
@"version": [NSString stringWithFormat:@"%g",XML_DOC_VERSION],
@"xmlns": XML_DOC_NS,
@"xmlns:xlink": XML_DOC_NSXLINK
};
// apply the attributes
IJSVGApplyAttributesToElement(attributes, root);
return root;
}
- (void)_prepare
{
// create the stand alone DOM
_dom = [[NSXMLDocument alloc] initWithRootElement:[self rootNode]];
_dom.version = XML_DOCTYPE_VERSION;
_dom.characterEncoding = XML_DOC_CHARSET;
// add generator
NSXMLNode * generatorNode = [[[NSXMLNode alloc] initWithKind:NSXMLCommentKind] autorelease];
generatorNode.stringValue = XML_DOC_GENERATOR;
[_dom.rootElement addChild:generatorNode];
// add defs in
[_dom.rootElement addChild:[self defElement]];
// sort out header
// sort out stuff, so here we go...
[self _recursiveParseFromLayer:_svg.layer
intoElement:_dom.rootElement];
// cleanup
[self _cleanup];
}
- (void)_cleanup
{
// convert any duplicate paths into use
if((_options & IJSVGExporterOptionCreateUseForPaths) != 0) {
[self _convertUseElements];
}
// cleanup def
if((_options & IJSVGExporterOptionRemoveUselessDef) != 0) {
[self _cleanDef];
}
// move any possible attributes to its parent group
if((_options & IJSVGExporterOptionMoveAttributesToGroup) != 0) {
[self _moveAttributesToGroup];
}
// collapse groups
if((_options & IJSVGExporterOptionCollapseGroups) != 0) {
[self _collapseGroups];
}
// clean any blank groups
if((_options & IJSVGExporterOptionRemoveUselessGroups) != 0) {
[self _cleanEmptyGroups];
}
// sort attributes
if((_options & IJSVGExporterOptionSortAttributes) != 0) {
[self _sortAttributesOnElement:_dom.rootElement];
}
// compress groups together
if((_options & IJSVGExporterOptionCollapseGroups) != 0) {
[self _compressGroups];
}
}
- (void)_sortAttributesOnElement:(NSXMLElement *)element
{
// only apply to XML elements, not XMLNodes
if([element isKindOfClass:[NSXMLElement class]] == NO) {
return;
}
[self sortAttributesOnElement:element];
for(NSXMLElement * child in element.children) {
[self _sortAttributesOnElement:child];
}
}
- (void)_moveAttributesToGroup
{
@autoreleasepool {
NSArray<NSXMLElement *> * groups = [_dom nodesForXPath:@"//g"
error:nil];
const NSArray * inheritableAttributes = IJSVGInheritableAttributes();
for(NSXMLElement * group in groups) {
@autoreleasepool {
NSDictionary * intersection = @{};
// does it have a mask/clip?
BOOL hasClip = [group attributeForName:@"clip-path"] != nil ||
[group attributeForName:@"mask"] != nil;
NSMutableArray * intersected = [[[NSMutableArray alloc] init] autorelease];
// loop around each child
for(NSXMLElement * child in group.children) {
if(child.attributes.count == 0) {
continue;
}
// straight add on
NSDictionary * atts = [self intersectableAttributes:IJSVGElementAttributeDictionary(child)
inheritableAttributes:inheritableAttributes];
if(intersection.count == 0) {
intersection = atts;
} else {
// work out the intersection
NSDictionary * dict = nil;
dict = [self intersectionInheritableAttributes:intersection
currentAttributes:atts
inheritableAttributes:inheritableAttributes];
// break if nothing
if(dict == nil) {
continue;
}
// add them
intersection = dict;
}
// add the child to array
[intersected addObject:child];
}
if(intersected.count != 0 && hasClip == NO) {
for(NSXMLElement * child in intersected) {
for(NSString * attributeName in intersection.allKeys) {
// remove attribute
[child removeAttributeForName:attributeName];
// add the
NSDictionary * atts = nil;
atts = @{attributeName:intersection[attributeName]};
IJSVGApplyAttributesToElement(atts, group);
}
}
}
}
}
}
}
- (NSDictionary *)intersectableAttributes:(NSDictionary *)atts
inheritableAttributes:(const NSArray *)inheritable
{
NSMutableDictionary * dict = [[[NSMutableDictionary alloc] init] autorelease];
for(NSString * key in atts.allKeys) {
if([inheritable containsObject:key]) {
dict[key] = atts[key];
}
}
return dict;
}
- (NSDictionary *)intersectionInheritableAttributes:(NSDictionary *)newAttributes
currentAttributes:(NSDictionary *)currentAttributes
inheritableAttributes:(const NSArray *)inheritableAtts
{
NSMutableDictionary * dict = [[[NSMutableDictionary alloc] init] autorelease];
for(NSString * key in newAttributes.allKeys) {
// make sure they are the same and
// they are inheritable
if([currentAttributes objectForKey:key] != nil &&
[inheritableAtts containsObject:key] &&
[newAttributes[key] isEqualToString:currentAttributes[key]]) {
dict[key] = currentAttributes[key];
}
}
// nothing to return, kill it
if(dict.count == 0) {
return nil;
}
return dict;
}
- (void)_cleanDef
{
NSXMLElement * defNode = [self defElement];
if(defNode.children == 0) {
NSXMLElement * parent = (NSXMLElement *)defNode.parent;
[parent removeChildAtIndex:defNode.index];
}
}
- (void)_cleanEmptyGroups
{
@autoreleasepool {
// cleanup any groups that are completely useless
NSArray * groups = [_dom nodesForXPath:@"//g" error:nil];
for(NSXMLElement * element in groups) {
NSXMLElement * parent = (NSXMLElement *)element.parent;
if(element.childCount == 0) {
// empty group
[(NSXMLElement *)element.parent removeChildAtIndex:element.index];
} else if(element.attributes.count == 0) {
// no useful data on the group
NSInteger index = element.index;
for(NSXMLElement * child in element.children) {
[(NSXMLElement *)child.parent removeChildAtIndex:child.index];
[parent insertChild:child
atIndex:index++];
}
[parent removeChildAtIndex:element.index];
}
}
}
}
- (void)_compressGroups
{
NSArray * groups = [_dom nodesForXPath:@"//g" error:nil];
for(NSXMLElement * group in groups) {
// whats the next group?
if(group.parent == nil) {
continue;
}
// compare each group with its next sibling
NSXMLElement * nextGroup = (NSXMLElement *)group.nextSibling;
while([self compareElement:group withElement:nextGroup]) {
// move each child into the older group
for(NSXMLElement * child in nextGroup.children) {
[nextGroup removeChildAtIndex:child.index];
[group addChild:child];
}
// remove the newer
NSXMLElement * n = nextGroup;
nextGroup = (NSXMLElement *)nextGroup.nextSibling;
[(NSXMLElement *)n.parent removeChildAtIndex:n.index];
}
}
}
- (void)_collapseGroups
{
NSArray * groups = [_dom nodesForXPath:@"//g" error:nil];
const NSArray * inheritable = IJSVGInheritableAttributes();
for(NSXMLElement * group in groups) {
// dont do anything due to it being referenced
if([group attributeForName:@"id"] != nil) {
continue;
}
if(group.attributes.count != 0 && group.children.count == 1) {
// grab the first child as its a loner
NSXMLElement * child = (NSXMLElement *)group.children[0];
for(NSXMLNode * gAttribute in group.attributes) {
// if it just doesnt have the attriute, just add it
if([child attributeForName:gAttribute.name] == NO) {
// remove first, or throws a wobbly
[group removeAttributeForName:gAttribute.name];
[child addAttribute:gAttribute];
} else if([gAttribute.name isEqualToString:@"transform"]) {
// transform requires concatination
NSXMLNode * childTransform = [child attributeForName:@"transform"];
childTransform.stringValue = [NSString stringWithFormat:@"%@ %@",
gAttribute.stringValue, childTransform.stringValue];
} else if([inheritable containsObject:gAttribute.name] == NO) {
// if its not inheritable, only remove it if its not equal
NSXMLNode * aAtt = [child attributeForName:gAttribute.name];
if(aAtt == nil || (aAtt != nil && [aAtt.stringValue isEqualToString:gAttribute.stringValue] == NO)) {
continue;
}
}
[group removeAttributeForName:gAttribute.name];
}
}
}
}
- (BOOL)compareElement:(NSXMLElement *)element
withElement:(NSXMLElement *)anotherElement
{
// not a matching element
if([element.name isEqualToString:anotherElement.name] == NO ||
element.attributes.count != anotherElement.attributes.count) {
return NO;
}
// compare attributes
for(NSXMLNode * attribute in element.attributes) {
NSString * compareString = [anotherElement attributeForName:attribute.name].stringValue;
if([attribute.stringValue isEqualToString:compareString] == NO) {
return NO;
}
}
return YES;
}
- (void)_convertUseElements
{
@autoreleasepool {
NSArray * paths = [_dom nodesForXPath:@"//path"
error:nil];
NSCountedSet * set = [[[NSCountedSet alloc] init] autorelease];
for(NSXMLElement * element in paths) {
[set addObject:[element attributeForName:@"d"].stringValue];
}
NSMutableDictionary * defs = [[[NSMutableDictionary alloc] init] autorelease];
// now actually compute them
for(NSXMLElement * element in paths) {
NSString * data = [element attributeForName:@"d"].stringValue;
if([set countForObject:data] == 1) {
continue;
}
// at this point, we know the path is being used more then once
NSXMLElement * defParentElement = nil;
if((defParentElement = [defs objectForKey:data]) == nil) {
// create the def
NSXMLElement * element = [[[NSXMLElement alloc] init] autorelease];
element.name = @"path";
NSDictionary * atts = @{@"d":data,
@"id":[NSString stringWithFormat:@"path-%ld",(++_pathCount)]};
IJSVGApplyAttributesToElement(atts, element);
// store it against the def
defs[data] = element;
defParentElement = element;
}
// we know at this point, we need to swap out the path to a use
NSXMLElement * use = [[[NSXMLElement alloc] init] autorelease];
use.name = @"use";
// grab the id
NSString * pathId = [defParentElement attributeForName:@"id"].stringValue;
NSXMLNode * useAttribute = [[[NSXMLNode alloc] initWithKind:NSXMLAttributeKind] autorelease];
useAttribute.name = @"xlink:href";
useAttribute.stringValue = IJSVGHash(pathId);
[use addAttribute:useAttribute];
// remove the d attribute
for(NSXMLNode * attribute in element.attributes) {
if([attribute.name isEqualToString:@"d"]) {
continue;
}
[element removeAttributeForName:attribute.name];
[use addAttribute:attribute];
}
// swap it out
[(NSXMLElement *)element.parent replaceChildAtIndex:element.index
withNode:use];
}
// add the defs back in
NSXMLElement * def = [self defElement];
for(NSXMLElement * defElement in defs.allValues) {
[def addChild:defElement];
}
}
}
- (void)_recursiveParseFromLayer:(IJSVGLayer *)layer
intoElement:(NSXMLElement *)element
{
// is a shape
if([layer class] == [IJSVGShapeLayer class]) {
NSXMLElement * child = [self elementForShape:(IJSVGShapeLayer *)layer
fromParent:element];
if(child != nil) {
[element addChild:child];
}
} else if([layer isKindOfClass:[IJSVGImageLayer class]]) {
NSXMLElement * child = [self elementForImage:(IJSVGImageLayer *)layer
fromParent:element];
if(child != nil) {
[element addChild:child];
}
} else if([layer isKindOfClass:[IJSVGGroupLayer class]]) {
// assume its probably a group?
NSXMLElement * child = [self elementForGroup:layer
fromParent:element];
if(child != nil) {
[element addChild:child];
}
}
}
- (void)applyTransformToElement:(NSXMLElement *)element
fromLayer:(IJSVGLayer *)layer
{
// dont do anything, they are the same
CGFloat x = layer.frame.origin.x;
CGFloat y = layer.frame.origin.y;
// check for x or y..they must be included
if(CGAffineTransformEqualToTransform(layer.affineTransform, CGAffineTransformIdentity) &&
x == 0.f && y == 0.f) {
return;
}
// construct the matrix
CGAffineTransform transform = layer.affineTransform;
// was there already x and y transform?
BOOL hasXTransform = transform.tx != 0.f;
BOOL hasYTransform = transform.ty != 0.f;
// move the x and y position
if(x != 0.f || y != 0.f) {
transform = CGAffineTransformConcat( transform, CGAffineTransformMakeTranslation( x, y ));
}
// x and y were not given so just transform
if(hasXTransform) {
transform.tx /= 2.f;
}
if(hasYTransform) {
transform.ty /= 2.f;
}
// append the string
NSArray * transformArray = [IJSVGTransform affineTransformToSVGTransformAttributeString:transform];
NSString * transformStr = [transformArray componentsJoinedByString:@" "];
// apply it to the node
IJSVGApplyAttributesToElement(@{@"transform":transformStr},element);
}
- (NSXMLElement *)elementForGroup:(IJSVGLayer *)layer
fromParent:(NSXMLElement *)parent
{
// create the element
NSXMLElement * e = [[[NSXMLElement alloc] init] autorelease];
e.name = @"g";
// stick defaults
[self applyDefaultsToElement:e
fromLayer:layer];
// add group children
for(IJSVGLayer * childLayer in layer.sublayers) {
[self _recursiveParseFromLayer:childLayer
intoElement:e];
}
return e;
}
- (NSString *)base64EncodedStringFromCGImage:(CGImageRef)image
{
// convert the CGImage into an NSImage
NSBitmapImageRep * rep = [[[NSBitmapImageRep alloc] initWithCGImage:image] autorelease];
// work out the data
NSData * data = [rep representationUsingType:NSBitmapImageFileTypePNG
properties:@{}];
NSString * base64String = [data base64EncodedStringWithOptions:0];
return [@"data:image/png;base64," stringByAppendingString:base64String];
}
- (void)applyPatternFromLayer:(IJSVGPatternLayer *)layer
parentLayer:(IJSVGLayer *)parentLayer
toElement:(NSXMLElement *)element
{
// now we need the pattern
IJSVGGroupLayer * patternLayer = (IJSVGGroupLayer *)layer.pattern;
NSXMLElement * patternElement = [self elementForGroup:patternLayer
fromParent:nil];
patternElement.name = @"pattern";
NSDictionary * dict = @{@"id":[NSString stringWithFormat:@"pattern-%ld",(++_patternCount)],
@"width":IJSVGShortFloatString(layer.patternNode.width.value),
@"height":IJSVGShortFloatString(layer.patternNode.height.value)};
IJSVGApplyAttributesToElement(dict, patternElement);
[[self defElement] addChild:patternElement];
// now the use statement
NSXMLElement * useElement = [[[NSXMLElement alloc] init] autorelease];
useElement.name = @"use";
// now add the fill
dict = @{@"fill":IJSVGHashURL([patternElement attributeForName:@"id"].stringValue)};
IJSVGApplyAttributesToElement(dict, element);
// fill opacity
if(patternLayer.opacity != 1.f) {
IJSVGApplyAttributesToElement(@{@"fill-opacity":IJSVGShortFloatString(patternLayer.opacity)}, element);
}
}
- (void)applyGradientFromLayer:(IJSVGGradientLayer *)layer
parentLayer:(IJSVGLayer *)parentLayer
toElement:(NSXMLElement *)element
{
IJSVGGradient * gradient = layer.gradient;
NSString * gradKey = [NSString stringWithFormat:@"gradient-%ld",(++_gradCount)];
NSXMLElement * gradientElement = [[[NSXMLElement alloc] init] autorelease];
// work out linear gradient
if([gradient isKindOfClass:[IJSVGLinearGradient class]]) {
IJSVGLinearGradient * lGradient = (IJSVGLinearGradient *)gradient;
gradientElement.name = @"linearGradient";
NSDictionary * dict = @{@"id":gradKey,
@"x1":lGradient.x1.stringValue,
@"y1":lGradient.y1.stringValue,
@"x2":lGradient.x2.stringValue,
@"y2":lGradient.y2.stringValue};
// give it the attibutes
IJSVGApplyAttributesToElement(dict, gradientElement);
} else {
// assume radial
IJSVGRadialGradient * rGradient = (IJSVGRadialGradient *)gradient;
gradientElement.name = @"radialGradient";
NSDictionary * dict = @{@"id":gradKey,
@"cx":rGradient.cx.stringValue,
@"cy":rGradient.cy.stringValue,
@"fx":rGradient.fx.stringValue,
@"fy":rGradient.fy.stringValue,
@"r":rGradient.radius.stringValue};
// give it the attributes
IJSVGApplyAttributesToElement(dict, gradientElement);
}
// add the stops
NSGradient * grad = layer.gradient.gradient;
NSInteger noStops = grad.numberOfColorStops;
for(NSInteger i = 0; i < noStops; i++) {
// grab each color from the gradient
NSColor * aColor = nil;
CGFloat location;
[grad getColor:&aColor
location:&location
atIndex:i];
// create the stop element
NSXMLElement * stop = [[[NSXMLElement alloc] init] autorelease];
stop.name = @"stop";
NSMutableDictionary * atts = [[[NSMutableDictionary alloc] init] autorelease];
atts[@"offset"] = [NSString stringWithFormat:@"%g%%",(location*100)];
// add the color
atts[@"stop-color"] = [IJSVGColor colorStringFromColor:aColor
forceHex:YES];
// we need to work out the color at this point, annoyingly...
CGFloat opacity = aColor.alphaComponent;
atts[@"stop-opacity"] = [NSString stringWithFormat:@"%g",opacity];
// att the attributes
IJSVGApplyAttributesToElement(atts, stop);
// append the stop the gradient
[gradientElement addChild:stop];
}
// append it to the defs
[[self defElement] addChild:gradientElement];
// work out the transform
NSArray * transforms = layer.gradient.transforms;
if(transforms.count != 0.f) {
CGAffineTransform transform = [self affineTransformFromTransforms:transforms];
// work out if there is x and y translate
BOOL hasXTransform = transform.tx != 0.f;
BOOL hasYTransform = transform.ty != 0.f;
// x and y were not given so just transform
if(hasXTransform) {
transform.tx /= 2.f;
}
if(hasYTransform) {
transform.ty /= 2.f;
}
// apply the attributes
NSArray * transforms = [IJSVGTransform affineTransformToSVGTransformAttributeString:transform];
NSString * tString = [transforms componentsJoinedByString:@" "];
IJSVGApplyAttributesToElement(@{@"gradientTransform":tString}, gradientElement);
}
// add it to the element passed in
IJSVGApplyAttributesToElement(@{@"fill":IJSVGHashURL(gradKey)}, element);
// fill opacity
if(layer.opacity != 1.f) {
IJSVGApplyAttributesToElement(@{@"fill-opacity":IJSVGShortFloatString(layer.opacity)}, element);
}
}
- (CGAffineTransform)affineTransformFromTransforms:(NSArray<IJSVGTransform *> *)transforms
{
CGAffineTransform t = CGAffineTransformIdentity;
for(IJSVGTransform * transform in transforms) {
t = CGAffineTransformConcat( t, [transform CGAffineTransform]);
}
return t;
}
- (NSXMLElement *)elementForImage:(IJSVGImageLayer *)layer
fromParent:(NSXMLElement *)parent
{
NSString * base64String = [self base64EncodedStringFromCGImage:(CGImageRef)layer.contents];
// image element for the SVG
NSXMLElement * imageElement = [[[NSXMLElement alloc] init] autorelease];
imageElement.name = @"image";
NSMutableDictionary * dict = [[[NSMutableDictionary alloc] init] autorelease];
dict[@"id"] = [NSString stringWithFormat:@"image-%ld",(++_imageCount)];
dict[@"width"] = IJSVGShortFloatString(layer.frame.size.width);
dict[@"height"] = IJSVGShortFloatString(layer.frame.size.height);
dict[@"xlink:href"] = base64String;
// work out any position
if(layer.frame.origin.x != 0.f) {
dict[@"x"] = IJSVGShortFloatString(layer.frame.origin.x);
}
if(layer.frame.origin.y != 0.f) {
dict[@"y"] = IJSVGShortFloatString(layer.frame.origin.y);
}
// add the attributes
IJSVGApplyAttributesToElement(dict, imageElement);
return imageElement;
}
- (NSXMLElement *)elementForShape:(IJSVGShapeLayer *)layer
fromParent:(NSXMLElement *)parent
{
NSXMLElement * e = [[[NSXMLElement alloc] init] autorelease];
e.name = @"path";
CGPathRef path = layer.path;
NSMutableDictionary * dict = [[[NSMutableDictionary alloc] init] autorelease];
// path
dict[@"d"] = [self pathFromCGPath:path];
// work out even odd rule
if([layer.fillRule isEqualToString:kCAFillRuleNonZero] == NO) {
dict[@"fill-rule"] = @"evenodd";
}
// fill color
if(layer.fillColor != nil) {
NSColor * fillColor = [NSColor colorWithCGColor:layer.fillColor];
NSString * colorString = [IJSVGColor colorStringFromColor:fillColor];
// could be none
if(colorString != nil) {
dict[@"fill"] = colorString;
}
}
// is there a gradient fill?
if(layer.gradientFillLayer != nil) {
[self applyGradientFromLayer:layer.gradientFillLayer
parentLayer:(IJSVGLayer *)layer
toElement:e];
}
// is there a pattern?
if(layer.patternFillLayer != nil) {
[self applyPatternFromLayer:layer.patternFillLayer
parentLayer:(IJSVGLayer *)layer
toElement:e];
}
// is there a stroke layer?
if(layer.strokeLayer != nil) {
// check the type
IJSVGStrokeLayer * strokeLayer = layer.strokeLayer;
if([strokeLayer isKindOfClass:[IJSVGShapeLayer class]]) {
// stroke
if(strokeLayer.lineWidth != 0.f) {
dict[@"stroke-width"] = IJSVGShortFloatString(strokeLayer.lineWidth);
}
// stroke color
if(strokeLayer.strokeColor != nil) {
NSColor * strokeColor = [NSColor colorWithCGColor:strokeLayer.strokeColor];
NSString * strokeColorString = [IJSVGColor colorStringFromColor:strokeColor];
// could be none
if(strokeColorString != nil) {
dict[@"stroke"] = strokeColorString;
}
}
// work out line cap
if([strokeLayer.lineCap isEqualToString:kCALineCapButt] == NO) {
NSString * capStyle = nil;
if([strokeLayer.lineCap isEqualToString:kCALineCapRound]) {
capStyle = @"round";
} else if([strokeLayer.lineCap isEqualToString:kCALineCapSquare]) {
capStyle = @"square";
}
if(capStyle != nil) {
dict[@"stroke-linecap"] = capStyle;
}
}
// work out line join
if([strokeLayer.lineJoin isEqualToString:kCALineJoinMiter] == NO) {
NSString * joinStyle = nil;
if([strokeLayer.lineJoin isEqualToString:kCALineJoinBevel]) {
joinStyle = @"bevel";
} else if([strokeLayer.lineJoin isEqualToString:kCALineJoinRound]) {
joinStyle = @"round";
}
if(joinStyle != nil) {
dict[@"stroke-linejoin"] = joinStyle;
}
}
// work out dash offset...
if(strokeLayer.lineDashPhase != 0.f) {
dict[@"stroke-dashoffset"] = IJSVGShortFloatString(strokeLayer.lineDashPhase);
}
// work out dash array
if(strokeLayer.lineDashPattern.count != 0) {
dict[@"stroke-dasharray"] = [strokeLayer.lineDashPattern componentsJoinedByString:@" "];
}
}
}
// // stroke is done by a child layer
// if(layer.sublayers.count != 0) {
//
// // add the pattern sublayer
// if([sublayer isKindOfClass:[IJSVGPatternLayer class]]) {
// [self applyPatternFromLayer:(IJSVGPatternLayer *)sublayer
// parentLayer:(IJSVGLayer *)layer
// toElement:e];
// break;
// }
// }
// apply the attributes
IJSVGApplyAttributesToElement(dict, e);
// apple defaults
[self applyDefaultsToElement:e
fromLayer:(IJSVGLayer *)layer];
return e;
}
- (void)applyDefaultsToElement:(NSXMLElement *)element
fromLayer:(IJSVGLayer *)layer
{
NSMutableDictionary * dict = [[[NSMutableDictionary alloc] init] autorelease];
// opacity
if(layer.opacity != 1.f) {
dict[@"opacity"] = IJSVGShortFloatString(layer.opacity);
}
// add atttributes
IJSVGApplyAttributesToElement(dict, element);
// apply transforms
[self applyTransformToElement:element
fromLayer:layer];
// add any masks...
if(layer.mask != nil) {
[self applyMaskToElement:element
fromLayer:layer];
}
}
- (void)applyMaskToElement:(NSXMLElement *)element
fromLayer:(IJSVGLayer *)layer
{
// create the element
NSXMLElement * mask = [[[NSXMLElement alloc] init] autorelease];
mask.name = @"mask";
// create the key
NSString * maskKey = [NSString stringWithFormat:@"mask-%ld",(++_maskCount)];
NSMutableDictionary * dict = [[[NSMutableDictionary alloc] init] autorelease];
dict[@"id"] = maskKey;
dict[@"maskContentUnits"] = @"userSpaceOnUse";
dict[@"maskUnits"] = @"objectBoundingBox";
if(layer.mask.frame.origin.x != 0.f) {
dict[@"x"] = IJSVGShortFloatString(layer.mask.frame.origin.x);
}
if(layer.mask.frame.origin.y != 0.f) {
dict[@"y"] = IJSVGShortFloatString(layer.mask.frame.origin.y);
}
IJSVGApplyAttributesToElement(dict, mask);
// add the cool stuff
[self _recursiveParseFromLayer:(IJSVGLayer *)layer.mask
intoElement:mask];
// add mask id to element
IJSVGApplyAttributesToElement(@{@"mask":IJSVGHashURL(maskKey)}, element);
// add it defs
[[self defElement] addChild:mask];
}
- (NSString *)SVGString
{
return [_dom XMLStringWithOptions:NSXMLNodePrettyPrint];
}
- (NSData *)SVGData
{
return [[self SVGString] dataUsingEncoding:NSUTF8StringEncoding];
}
#pragma mark CGPath stuff
- (NSString *)pathFromCGPath:(CGPathRef)path
{
// string to store the path in
NSArray * instructions = [IJSVGExporterPathInstruction instructionsFromPath:path];
// work out what to do...
if((_options & IJSVGExporteroptionCleanupPaths) != 0) {
[IJSVGExporterPathInstruction convertInstructionsToRelativeCoordinates:instructions];
}
return [IJSVGExporterPathInstruction pathStringFromInstructions:instructions];
}
void IJSVGExporterPathCaller(void * info, const CGPathElement * pathElement) {
IJSVGCGPathHandler handler = (IJSVGCGPathHandler)info;
handler(pathElement);
};
- (void)sortAttributesOnElement:(NSXMLElement *)element
{
const NSArray * order = @[@"id",@"width",@"height",@"x",@"x1",@"x2",
@"y",@"y1",@"y2",@"cx",@"cy",@"r",@"fill",
@"stroke",@"marker",@"d",@"points",@"transform",
@"gradientTransform", @"xlink:href"];
// grab the attributes
NSArray<NSXMLNode *>* attributes = element.attributes;
NSInteger count = attributes.count;
// sort the attributes using a custom sort
NSArray * sorted = [attributes sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
// tell compiler we are nodes
NSXMLNode * attribute1 = (NSXMLNode *)obj1;
NSXMLNode * attribute2 = (NSXMLNode *)obj2;
// base index
float aIndex = count;
float bIndex = count;
// loop around each order string
for(NSInteger i = 0; i < order.count; i++) {
if([attribute1.name isEqualToString:order[i]]) {
aIndex = i;
} else if([attribute1.name rangeOfString:[order[i] stringByAppendingString:@"-"]].location == 0) {
aIndex = i + .5;
}
if([attribute2.name isEqualToString:order[i]]) {
bIndex = i;
} else if([attribute2.name rangeOfString:[order[i] stringByAppendingString:@"-"]].location == 0) {
bIndex = i + .5;
}
}
// return the comparison set
if(aIndex != bIndex) {
if(aIndex > bIndex) {
return NSOrderedDescending;
} else {
return NSOrderedAscending;
}
}
return [attribute1.name compare:attribute2.name];
}];
// remove all attributes
for(NSXMLNode * node in attributes) {
[element removeAttributeForName:node.name];
}
// add them back on in order
for(NSXMLNode * attribute in sorted) {
[element addAttribute:attribute];
}
}
@end