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
1087 lines
37 KiB
Objective-C
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
|