commitffb55b0f31Author: Curtis Hard <curtishard@me.com> Date: Fri Mar 2 13:28:40 2018 +0000 Fixes viewBox origin translate commita4c032afa2Author: Curtis Hard <curtishard@me.com> Date: Wed Feb 28 21:29:11 2018 +0000 Added clip to viewport commitabe8f4cba5Author: Curtis Hard <curtishard@me.com> Date: Wed Feb 28 21:00:20 2018 +0000 Fixes drawInRect not obeying origin commita16842271bAuthor: Curtis Hard <curtishard@me.com> Date: Mon Feb 26 13:46:41 2018 +0000 Resolves namespaces correctly + added common HTML list to be parsed as groups commit58126e06e4Author: Curtis Hard <curtishard@me.com> Date: Sun Feb 25 21:58:49 2018 +0000 Fixes! commit5af5e054abAuthor: Curtis Hard <curtishard@me.com> Date: Fri Feb 23 22:37:53 2018 +0000 Fixed defNode being removed commitedd3aa1f33Author: Curtis Hard <curtishard@me.com> Date: Fri Feb 23 16:29:54 2018 +0000 excluded various elements from diff commit3366bc4fa5Author: Curtis Hard <curtishard@me.com> Date: Fri Feb 23 14:05:03 2018 +0000 Better optimaztion commit104002183bAuthor: Curtis Hard <curtishard@me.com> Date: Thu Feb 22 22:17:50 2018 +0000 Added rudimenatry inline styles -> stylesheet commit7010b7ea50Author: Curtis Hard <curtishard@me.com> Date: Thu Feb 22 13:33:32 2018 +0000 Correct order of cleanup commit5266a8c07aAuthor: Curtis Hard <curtishard@me.com> Date: Thu Feb 22 08:22:17 2018 +0000 corrent length of string commitcaf55e8bdfAuthor: Curtis Hard <curtishard@me.com> Date: Wed Feb 21 20:45:17 2018 +0000 removes useless def if required commit8160d05ebaAuthor: Curtis Hard <curtishard@me.com> Date: Tue Feb 20 14:12:02 2018 +0000 Refactor of a few methods commita6d6a06521Author: Curtis Hard <curtishard@me.com> Date: Tue Feb 20 09:40:21 2018 +0000 Added collpasing of gradients commit103a4d71f6Author: Curtis Hard <curtishard@me.com> Date: Mon Feb 19 19:07:30 2018 +0000 Reduced floats even more commit98874b1d2cAuthor: Curtis Hard <curtishard@me.com> Date: Mon Feb 19 18:53:02 2018 +0000 More compression goodness commite742db31e0Author: Curtis Hard <curtishard@me.com> Date: Mon Feb 19 14:02:15 2018 +0000 Added intermediateParent commit198fd09f07Author: Curtis Hard <curtishard@me.com> Date: Mon Feb 19 11:51:37 2018 +0000 Fixes! and performance increases commit3493194b1bAuthor: Curtis Hard <curtishard@me.com> Date: Mon Feb 19 08:20:02 2018 +0000 Scale computation commit0016775eafAuthor: Curtis Hard <curtishard@me.com> Date: Sun Feb 18 22:33:26 2018 +0000 More goodness commit304a04cc22Author: Curtis Hard <curtishard@me.com> Date: Sun Feb 18 22:32:04 2018 +0000 Vastly improved the exporter commite4fd0af582Author: Curtis Hard <curtishard@me.com> Date: Sun Feb 18 15:20:28 2018 +0000 This is insanely important! commit69a2a0c97eAuthor: Curtis Hard <curtishard@me.com> Date: Sun Feb 4 22:02:19 2018 +0000 Refactor commit5299bb0479Author: Curtis Hard <curtishard@me.com> Date: Sun Feb 4 21:58:21 2018 +0000 will continue to use CoreAnimation for the time being commit4f1943cad1Author: Curtis Hard <curtishard@me.com> Date: Sun Feb 4 21:57:46 2018 +0000 Fixes gradient strokes commitf02d186293Author: Curtis Hard <curtishard@me.com> Date: Sun Feb 4 11:15:04 2018 +0000 trying to get masks to work commit3291718cfbAuthor: Curtis Hard <curtishard@me.com> Date: Fri Feb 2 22:35:22 2018 +0000 Beginning of quartz renderer commit6fbaaf5884Author: Curtis Hard <curtishard@me.com> Date: Mon Jan 29 22:32:08 2018 +0000 Removed useless log commitabc65797eaAuthor: Curtis Hard <curtishard@me.com> Date: Mon Jan 29 21:37:45 2018 +0000 I think gradients work :D commitaf5a1c2718Author: Curtis Hard <curtishard@me.com> Date: Sun Jan 28 22:18:02 2018 +0000 Possible fx and fy things… commitfb9a5282b9Author: Curtis Hard <curtishard@me.com> Date: Sun Jan 28 19:22:40 2018 +0000 Even more gradient fixes commitd83933a103Author: Curtis Hard <curtishard@me.com> Date: Sun Jan 28 14:21:50 2018 +0000 Various improvements commitbd7a0d5021Author: Curtis Hard <curtishard@me.com> Date: Sat Jan 27 22:26:33 2018 +0000 Start to linear commita9a038568cAuthor: Curtis Hard <curtishard@me.com> Date: Sat Jan 27 21:14:25 2018 +0000 This kind of actually works... commit77fbb38b6fAuthor: Curtis Hard <curtishard@me.com> Date: Sat Jan 27 20:31:36 2018 +0000 I guess this could be a good start? Posssible start of fixes? commit12c3191569Author: Curtis Hard <curtishard@me.com> Date: Sat Jan 27 15:32:15 2018 +0000 Added method for findind absolute position commite3e9626ef7Author: Curtis Hard <curtishard@me.com> Date: Sat Jan 27 14:25:03 2018 +0000 Fixes crash due to parentNode on temp groups being released commit1160d89f16Author: Curtis Hard <curtishard@me.com> Date: Fri Jan 26 22:29:22 2018 +0000 Fixes use statements with transforms commit1575cbfde8Author: Curtis Hard <curtishard@me.com> Date: Fri Jan 26 18:16:03 2018 +0000 Moved color tree over to modern syntax commit5c4c2eee91Author: Curtis Hard <curtishard@me.com> Date: Thu Jan 25 18:23:53 2018 +0000 Added HSL/HSLA support commit087b13e58fAuthor: Curtis Hard <curtishard@me.com> Date: Wed Jan 24 22:24:43 2018 +0000 Various image loading issues resolved from base64 images commit1183e167aaAuthor: Curtis Hard <curtishard@me.com> Date: Wed Jan 24 21:21:41 2018 +0000 Rect issue fix commit4dbfc59437Author: Curtis Hard <curtishard@me.com> Date: Wed Jan 24 21:19:24 2018 +0000 Moved transforms over from being concatinated to seperate calls commit409bd509faAuthor: Curtis Hard <curtishard@me.com> Date: Wed Jan 24 20:30:38 2018 +0000 Fixes color issue commit8ae1d1b4e0Author: Curtis Hard <curtishard@me.com> Date: Wed Jan 24 18:41:08 2018 +0000 Removed check as its not needed here commit7243fbe5ffAuthor: Curtis Hard <curtishard@me.com> Date: Wed Jan 24 18:35:43 2018 +0000 Added excludeAttributes list to parseCommonAttributes added x and y to that list for rect commit51b9a5e85fAuthor: Curtis Hard <curtishard@me.com> Date: Tue Jan 23 19:53:04 2018 +0000 Added isSubcommand to IJSVGCommand - Fixes move command going awol when preceding move commands are not subcommands (woah) commit40098589deAuthor: Curtis Hard <curtishard@me.com> Date: Mon Jan 22 22:04:48 2018 +0000 removed reverseObjectEnumerator IJSVGTransform already deals with this at parse stage (was a test from earlier), spec states transforms are applied in reverse order (which parser already dealth with :-)) commit7cb96b21f2Author: Curtis Hard <curtishard@me.com> Date: Mon Jan 22 22:01:56 2018 +0000 Removed use of origin here as its computed in apply defaults commit1bff7c6970Author: Curtis Hard <curtishard@me.com> Date: Mon Jan 22 21:48:07 2018 +0000 Various fixes… still going…
781 lines
25 KiB
Objective-C
781 lines
25 KiB
Objective-C
//
|
|
// IJSVGLayerTree.m
|
|
// IJSVGExample
|
|
//
|
|
// Created by Curtis Hard on 29/12/2016.
|
|
// Copyright © 2016 Curtis Hard. All rights reserved.
|
|
//
|
|
|
|
#import "IJSVGLayerTree.h"
|
|
#import "IJSVGGroup.h"
|
|
#import "IJSVGTransform.h"
|
|
#import "IJSVGUtils.h"
|
|
#import "IJSVGPath.h"
|
|
#import "IJSVGImage.h"
|
|
#import "IJSVGGradient.h"
|
|
#import "IJSVGGradientLayer.h"
|
|
#import "IJSVGPatternLayer.h"
|
|
#import "IJSVGPattern.h"
|
|
#import "IJSVG.h"
|
|
#import "IJSVGText.h"
|
|
#import "IJSVGLayer.h"
|
|
#import "IJSVGShapeLayer.h"
|
|
#import "IJSVGImageLayer.h"
|
|
#import "IJSVGGroupLayer.h"
|
|
#import "IJSVGStrokeLayer.h"
|
|
|
|
@implementation IJSVGLayerTree
|
|
|
|
@synthesize viewBox;
|
|
@synthesize fillColor;
|
|
@synthesize strokeColor;
|
|
@synthesize strokeWidth;
|
|
@synthesize lineJoinStyle;
|
|
@synthesize lineCapStyle;
|
|
@synthesize replacementColors;
|
|
|
|
- (void)dealloc
|
|
{
|
|
[fillColor release], fillColor = nil;
|
|
[strokeColor release], strokeColor = nil;
|
|
[replacementColors release], replacementColors = nil;
|
|
[super dealloc];
|
|
}
|
|
|
|
- (id)init
|
|
{
|
|
if((self = [super init]) != nil) {
|
|
self.lineJoinStyle = IJSVGLineJoinStyleNone;
|
|
self.lineCapStyle = IJSVGLineCapStyleNone;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (IJSVGLayer *)layerForNode:(IJSVGNode *)node
|
|
{
|
|
IJSVGLayer * layer = nil;
|
|
|
|
// is there a sub SVG?
|
|
if([node isKindOfClass:[IJSVGPath class]]) {
|
|
// path
|
|
layer = [self layerForPath:(IJSVGPath *)node];
|
|
} else if([node isKindOfClass:[IJSVGGroup class]]) {
|
|
// group
|
|
layer = [self layerForGroup:(IJSVGGroup *)node];
|
|
} else if([node isKindOfClass:[IJSVGImage class]]) {
|
|
// image
|
|
layer = [self layerForImage:(IJSVGImage *)node];
|
|
}
|
|
|
|
[self applyDefaultsToLayer:layer fromNode:node];
|
|
|
|
// create the new layer
|
|
layer = [self applyTransforms:node.transforms
|
|
toLayer:layer];
|
|
|
|
return layer;
|
|
}
|
|
|
|
- (IJSVGLayer *)applyTransforms:(NSArray<IJSVGTransform *> *)transforms
|
|
toLayer:(IJSVGLayer *)layer
|
|
|
|
{
|
|
|
|
// do some magic transform
|
|
if(transforms.count == 0) {
|
|
return layer;
|
|
}
|
|
|
|
// add any transforms
|
|
IJSVGLayer * topLayer = nil;
|
|
IJSVGLayer * parentLayer = nil;
|
|
|
|
for(IJSVGTransform * transform in transforms) {
|
|
// make sure we apply the transform to the parent
|
|
// so they stack
|
|
IJSVGGroupLayer * childLayer = [[[IJSVGGroupLayer alloc] init] autorelease];
|
|
childLayer.affineTransform = transform.CGAffineTransform;
|
|
|
|
// add it to the parent layer
|
|
if(parentLayer != nil) {
|
|
[parentLayer addSublayer:childLayer];
|
|
} else {
|
|
// make sure we keep track of the top most layer
|
|
topLayer = childLayer;
|
|
}
|
|
|
|
// reset parent layer to the new child
|
|
parentLayer = childLayer;
|
|
}
|
|
|
|
// swap the layer around
|
|
[parentLayer addSublayer:layer];
|
|
layer = topLayer;
|
|
return layer;
|
|
}
|
|
|
|
- (void)applyDefaultsToLayer:(IJSVGLayer *)layer
|
|
fromNode:(IJSVGNode *)node
|
|
{
|
|
CGFloat opacity = node.opacity.value;
|
|
if(opacity == 0.f) {
|
|
opacity = 1.f;
|
|
}
|
|
layer.opacity = opacity;
|
|
|
|
// setup the blending mode
|
|
if(node.blendMode != IJSVGBlendModeNormal) {
|
|
layer.blendingMode = (CGBlendMode)node.blendMode;
|
|
}
|
|
|
|
// display?
|
|
if(node.shouldRender == NO) {
|
|
layer.hidden = YES;
|
|
}
|
|
|
|
CGRect frame = layer.frame;
|
|
frame.origin.x += [node.x computeValue:frame.size.width];
|
|
frame.origin.y += [node.y computeValue:frame.size.height];
|
|
layer.frame = frame;
|
|
}
|
|
|
|
- (IJSVGLayer *)layerForImage:(IJSVGImage *)image
|
|
{
|
|
IJSVGImageLayer * layer = [[[IJSVGImageLayer alloc] initWithCGImage:image.CGImage] autorelease];
|
|
layer.affineTransform = CGAffineTransformConcat(layer.affineTransform,
|
|
CGAffineTransformMakeScale( 1.f, -1.f));
|
|
|
|
// make sure we set the width and height correctly,
|
|
// as this may not be exactly the same as the size of the
|
|
// given image
|
|
CGRect frame = layer.frame;
|
|
frame.size.width = image.width.value;
|
|
frame.size.height = image.height.value;
|
|
layer.frame = frame;
|
|
return layer;
|
|
}
|
|
|
|
- (IJSVGLayer *)layerForGroup:(IJSVGGroup *)group
|
|
{
|
|
IJSVGGroupLayer * groupLayer = [[[IJSVGGroupLayer alloc] init] autorelease];
|
|
for(IJSVGNode * node in group.children) {
|
|
[groupLayer addSublayer:[self layerForNode:node]];
|
|
}
|
|
groupLayer.frame = (CGRect){
|
|
.origin = CGPointZero,
|
|
.size = (CGSize){
|
|
.width = group.width.value,
|
|
.height = group.height.value
|
|
}
|
|
};
|
|
|
|
// mask it - forgot groups can have masks too, doh! simple
|
|
// enough to apply though, recursion ftw!
|
|
[self maskLayer:groupLayer
|
|
fromNode:group];
|
|
return groupLayer;
|
|
}
|
|
|
|
- (CGRect)correctedBounds:(CGRect)bounds
|
|
{
|
|
if(bounds.origin.x >= INFINITY) {
|
|
bounds.origin.x = 0.f;
|
|
}
|
|
if(bounds.origin.y >= INFINITY) {
|
|
bounds.origin.y = 0.f;
|
|
}
|
|
if(bounds.size.width >= INFINITY) {
|
|
bounds.size.width = 0.f;
|
|
}
|
|
if(bounds.size.height >= INFINITY) {
|
|
bounds.size.height = 0.f;
|
|
}
|
|
return bounds;
|
|
}
|
|
|
|
- (IJSVGShapeLayer *)basicLayerForPath:(IJSVGPath *)path
|
|
originalBoundingBox:(CGRect *)originalBoundingBox
|
|
{
|
|
// setup path and layer
|
|
IJSVGShapeLayer * layer = [[[IJSVGShapeLayer alloc] init] autorelease];
|
|
CGPathRef introPath = [path newPathRefByAutoClosingPath:NO];
|
|
|
|
*originalBoundingBox = CGRectIntegral(CGPathGetBoundingBox(introPath));
|
|
layer.originalPathOrigin = (*originalBoundingBox).origin;
|
|
|
|
CGRect bounds = [self correctedBounds:*originalBoundingBox];
|
|
|
|
// zero back the path
|
|
CGAffineTransform trans = CGAffineTransformMakeTranslation(-bounds.origin.x,
|
|
-bounds.origin.y);
|
|
|
|
CGPathRef transformedPath = CGPathCreateCopyByTransformingPath(introPath, &trans);
|
|
layer.path = transformedPath;
|
|
|
|
// clean up path memory
|
|
CGPathRelease(transformedPath);
|
|
CGPathRelease(introPath);
|
|
|
|
// set the bounds
|
|
layer.frame = CGRectIntegral(bounds);
|
|
|
|
// basic fill color and rule
|
|
layer.fillColor = nil;
|
|
layer.fillRule = [self fillRule:path.windingRule];
|
|
return layer;
|
|
}
|
|
|
|
- (IJSVGShapeLayer *)layerMaskFromLayer:(CAShapeLayer *)layer
|
|
fromNode:(IJSVGNode *)node
|
|
{
|
|
IJSVGShapeLayer * mask = [[[IJSVGShapeLayer alloc] init] autorelease];
|
|
mask.fillColor = [NSColor blackColor].CGColor;
|
|
mask.path = layer.path;
|
|
return mask;
|
|
}
|
|
|
|
- (CGAffineTransform)absoluteTransform:(IJSVGNode *)node
|
|
{
|
|
CGAffineTransform parentAbsoluteTransform = CGAffineTransformIdentity;
|
|
IJSVGNode * intermediateNode = node.intermediateParentNode;
|
|
node = node.parentNode;
|
|
while(node != nil) {
|
|
// intermediateParent should be skipped as these are elements
|
|
// created by use statements and are technically fake elements, but the
|
|
// spec says use them for the transforms, yolo!
|
|
if(node == intermediateNode) {
|
|
node = node.parentNode;
|
|
continue;
|
|
}
|
|
CGAffineTransform trans = IJSVGConcatTransforms(node.transforms);
|
|
parentAbsoluteTransform = CGAffineTransformConcat(trans,parentAbsoluteTransform);
|
|
node = node.parentNode;
|
|
}
|
|
return parentAbsoluteTransform;
|
|
}
|
|
|
|
- (IJSVGLayer *)layerForPath:(IJSVGPath *)path
|
|
{
|
|
// is there a sub SVG?
|
|
if(path.svg != nil) {
|
|
// grab the sub layer tree from the SVG
|
|
return [path.svg layerWithTree:self];
|
|
}
|
|
|
|
// garb the basic shape layer
|
|
CGRect originalShapeBounds;
|
|
IJSVGShapeLayer * layer = [self basicLayerForPath:path
|
|
originalBoundingBox:&originalShapeBounds];
|
|
|
|
BOOL hasStroke = (path.strokeColor != nil ||
|
|
path.strokePattern != nil ||
|
|
path.strokeGradient != nil);
|
|
|
|
// any gradient?
|
|
if(self.fillColor == nil && path.fillGradient != nil) {
|
|
|
|
// create the gradient
|
|
IJSVGGradientLayer * gradLayer = [self gradientLayerForLayer:layer
|
|
gradient:path.fillGradient
|
|
fromNode:path
|
|
objectRect:originalShapeBounds
|
|
shouldMask:YES];
|
|
|
|
// add the gradient and set it against the layer
|
|
[layer addSublayer:gradLayer];
|
|
layer.gradientFillLayer = gradLayer;
|
|
|
|
} else if(self.fillColor == nil && path.fillPattern != nil) {
|
|
|
|
// create the pattern, this is actually not as easy as it may seem
|
|
IJSVGPatternLayer * patternLayer = [self patternLayerForLayer:layer
|
|
pattern:path.fillPattern
|
|
fromNode:path];
|
|
// add it
|
|
[layer addSublayer:patternLayer];
|
|
|
|
// apply offsets
|
|
[self applyOffsetsToLayer:patternLayer
|
|
fromNode:path.fillPattern];
|
|
|
|
layer.patternFillLayer = patternLayer;
|
|
|
|
} else {
|
|
// only use the global if its set and the current colors
|
|
// alpha channel is not 0.f, otherwise its a blank clear color,
|
|
// aka, not filled in
|
|
|
|
|
|
|
|
NSColor * fColor = path.fillColor;
|
|
BOOL hasColor = (fColor.alphaComponent == 0.f || fColor == nil) == NO;
|
|
BOOL hasFill = path.fillPattern != nil || path.fillGradient != nil;
|
|
if(self.fillColor && (hasFill || hasColor || fColor == nil)) {
|
|
fColor = self.fillColor;
|
|
}
|
|
|
|
// anything changed by user?
|
|
fColor = [self proposedColorForColor:fColor];
|
|
|
|
// just set the color
|
|
if(fColor != nil) {
|
|
layer.fillColor = fColor.CGColor;
|
|
} else {
|
|
// use default color
|
|
NSColor * defColor = [NSColor blackColor];
|
|
if(path.fillOpacity.value != 1.f) {
|
|
defColor = [IJSVGColor changeAlphaOnColor:defColor
|
|
to:path.fillOpacity.value];
|
|
}
|
|
|
|
// work out if anything was changed by user
|
|
NSColor * proposedColor = [self proposedColorForColor:defColor];
|
|
layer.fillColor = proposedColor.CGColor;
|
|
}
|
|
}
|
|
|
|
// stroke it
|
|
if(hasStroke == YES) {
|
|
|
|
// load the stroke layer
|
|
IJSVGStrokeLayer * strokeLayer = [self strokeLayer:layer
|
|
fromNode:path];
|
|
|
|
// reset the node
|
|
BOOL moveStrokeLayer = NO;
|
|
if(self.strokeColor == nil && path.strokeGradient != nil) {
|
|
|
|
// force reset of the mask colour as we need to use the stroke layer
|
|
// as the mask for the stroke gradient
|
|
strokeLayer.strokeColor = [NSColor blackColor].CGColor;
|
|
|
|
// create the gradient
|
|
IJSVGGradientLayer * gradLayer = [self gradientStrokeLayerForLayer:layer
|
|
gradient:path.strokeGradient
|
|
fromNode:path
|
|
objectRect:originalShapeBounds];
|
|
|
|
moveStrokeLayer = YES;
|
|
gradLayer.mask = strokeLayer;
|
|
gradLayer.opacity = strokeLayer.opacity;
|
|
|
|
// add it
|
|
[layer addSublayer:gradLayer];
|
|
layer.strokeLayer = strokeLayer;
|
|
layer.gradientStrokeLayer = gradLayer;
|
|
|
|
} else if(self.strokeColor == nil && path.strokePattern != nil) {
|
|
|
|
// force reset of the mask
|
|
strokeLayer.strokeColor = [NSColor blackColor].CGColor;
|
|
|
|
// create the pattern
|
|
IJSVGPatternLayer * patternLayer = [self patternStrokeLayerForLayer:layer
|
|
pattern:path.strokePattern
|
|
fromNode:path];
|
|
|
|
// set the mask for it
|
|
moveStrokeLayer = YES;
|
|
patternLayer.mask = strokeLayer;
|
|
patternLayer.opacity = strokeLayer.opacity;
|
|
|
|
// add it
|
|
[layer addSublayer:patternLayer];
|
|
layer.strokeLayer = strokeLayer;
|
|
layer.patternStrokeLayer = (IJSVGPatternLayer *)patternLayer;
|
|
|
|
} else {
|
|
// just add the coloured layer
|
|
[layer addSublayer:strokeLayer];
|
|
layer.strokeLayer = strokeLayer;
|
|
}
|
|
|
|
// if we required to move the stroke layer
|
|
// then move it in based on half of what the stroke
|
|
// width is, as strokes are draw on the center
|
|
if(moveStrokeLayer) {
|
|
CGFloat layerStrokeWidth = strokeLayer.lineWidth;
|
|
CGRect rect = strokeLayer.frame;
|
|
rect.origin.x += (layerStrokeWidth*.5f);
|
|
rect.origin.y += (layerStrokeWidth*.5f);
|
|
strokeLayer.frame = rect;
|
|
}
|
|
|
|
}
|
|
|
|
// apply masking
|
|
[self maskLayer:(IJSVGLayer *)layer
|
|
fromNode:path];
|
|
|
|
return (IJSVGLayer *)layer;
|
|
}
|
|
|
|
- (CGRect)correctBounds:(CGRect)bounds
|
|
forStrokedPath:(IJSVGNode *)path
|
|
{
|
|
// minus half the stroke width from x and y
|
|
// plus the stroke width to width and height
|
|
CGFloat val = path.strokeWidth.value;
|
|
bounds.origin.x -= (val*.5f);
|
|
bounds.origin.y -= (val*.5f);
|
|
bounds.size.width += val;
|
|
bounds.size.height += val;
|
|
return bounds;
|
|
}
|
|
|
|
- (NSColor *)proposedColorForColor:(NSColor *)color
|
|
{
|
|
// nothing found, just return color
|
|
if(replacementColors == nil || replacementColors.count == 0) {
|
|
return color;
|
|
}
|
|
|
|
// check the mappings
|
|
NSColor * found = nil;
|
|
color = [IJSVGColor computeColorSpace:color];
|
|
if((found = replacementColors[color]) != nil) {
|
|
return found;
|
|
}
|
|
|
|
// nothing :(
|
|
return color;
|
|
}
|
|
|
|
- (IJSVGGradientLayer *)gradientStrokeLayerForLayer:(IJSVGShapeLayer *)layer
|
|
gradient:(IJSVGGradient *)gradient
|
|
fromNode:(IJSVGNode *)path
|
|
objectRect:(CGRect)objectRect
|
|
{
|
|
// the gradient drawing layer
|
|
IJSVGGradientLayer * gradLayer = [self gradientLayerForLayer:layer
|
|
gradient:gradient
|
|
fromNode:path
|
|
objectRect:objectRect
|
|
shouldMask:NO];
|
|
|
|
// set the bounds
|
|
CGRect bounds = CGPathGetBoundingBox(layer.path);
|
|
bounds = [self correctBounds:bounds forStrokedPath:path];
|
|
gradLayer.frame = bounds;
|
|
return gradLayer;
|
|
}
|
|
|
|
|
|
- (IJSVGGradientLayer *)gradientLayerForLayer:(IJSVGShapeLayer *)layer
|
|
gradient:(IJSVGGradient *)gradient
|
|
fromNode:(IJSVGNode *)path
|
|
objectRect:(CGRect)objectRect
|
|
shouldMask:(BOOL)shouldMask
|
|
{
|
|
// the gradient drawing layer
|
|
IJSVGGradientLayer * gradLayer = [[[IJSVGGradientLayer alloc] init] autorelease];
|
|
gradLayer.viewBox = self.viewBox;
|
|
gradLayer.frame = layer.bounds;
|
|
gradLayer.gradient = gradient;
|
|
gradLayer.absoluteTransform = [self absoluteTransform:path];
|
|
gradLayer.objectRect = CGRectApplyAffineTransform(objectRect, gradLayer.absoluteTransform);
|
|
|
|
if(shouldMask == YES) {
|
|
// add the mask
|
|
IJSVGShapeLayer * mask = [self layerMaskFromLayer:layer
|
|
fromNode:path];
|
|
gradLayer.mask = mask;
|
|
}
|
|
|
|
// is there a fill opacity?
|
|
if(path.fillOpacity.value != 0.f) {
|
|
gradLayer.opacity = path.fillOpacity.value;
|
|
}
|
|
return gradLayer;
|
|
}
|
|
|
|
- (IJSVGPatternLayer *)patternStrokeLayerForLayer:(IJSVGShapeLayer *)layer
|
|
pattern:(IJSVGPattern *)pattern
|
|
fromNode:(IJSVGNode *)path
|
|
{
|
|
// create the pattern, this is actually not as easy as it may seem
|
|
IJSVGPatternLayer * patternLayer = [[[IJSVGPatternLayer alloc] init] autorelease];
|
|
patternLayer.patternNode = pattern;
|
|
patternLayer.pattern = [self layerForNode:pattern];
|
|
|
|
// is there a fill opacity?
|
|
if(path.fillOpacity.value != 0.f) {
|
|
patternLayer.opacity = path.fillOpacity.value;
|
|
}
|
|
|
|
// set the bounds
|
|
CGRect bounds = CGPathGetBoundingBox(layer.path);
|
|
bounds = [self correctBounds:bounds forStrokedPath:path];
|
|
patternLayer.frame = bounds;
|
|
|
|
// display
|
|
[patternLayer setNeedsDisplay];
|
|
|
|
return patternLayer;
|
|
}
|
|
|
|
- (IJSVGPatternLayer *)patternLayerForLayer:(IJSVGShapeLayer *)layer
|
|
pattern:(IJSVGPattern *)pattern
|
|
fromNode:(IJSVGNode *)path
|
|
{
|
|
// create the pattern, this is actually not as easy as it may seem
|
|
IJSVGPatternLayer * patternLayer = [[[IJSVGPatternLayer alloc] init] autorelease];
|
|
patternLayer.patternNode = pattern;
|
|
patternLayer.pattern = [self layerForNode:pattern];
|
|
patternLayer.frame = CGPathGetBoundingBox(layer.path);
|
|
|
|
// is there a fill opacity?
|
|
if(path.fillOpacity.value != 0.f) {
|
|
patternLayer.opacity = path.fillOpacity.value;
|
|
}
|
|
|
|
// add the mask
|
|
patternLayer.mask = [self layerMaskFromLayer:layer
|
|
fromNode:path];
|
|
|
|
// display
|
|
[patternLayer setNeedsDisplay];
|
|
|
|
return patternLayer;
|
|
}
|
|
|
|
- (void)applyOffsetsToLayer:(IJSVGLayer *)layer
|
|
fromNode:(IJSVGNode *)node
|
|
{
|
|
// make sure it has a superlayer
|
|
if(layer.superlayer == nil) {
|
|
return;
|
|
}
|
|
|
|
// grab the x and y
|
|
IJSVGUnitLength * x = nil;
|
|
IJSVGUnitLength * y = nil;
|
|
|
|
// sort out the rect
|
|
CGRect rect = layer.superlayer.frame;
|
|
CGRect frame = layer.frame;
|
|
|
|
// x
|
|
if((x = node.x) != nil) {
|
|
frame.origin.x = [x computeValue:rect.size.width];
|
|
}
|
|
|
|
// y
|
|
if((y = node.y) != nil) {
|
|
frame.origin.y = [y computeValue:rect.size.height];
|
|
}
|
|
|
|
// update the frame
|
|
if(CGRectEqualToRect(frame, layer.frame) == NO) {
|
|
layer.frame = frame;
|
|
}
|
|
|
|
}
|
|
|
|
- (IJSVGStrokeLayer *)strokeLayer:(IJSVGShapeLayer *)layer
|
|
fromNode:(IJSVGPath *)path
|
|
{
|
|
// same as fill, dont use global if the alpha is 0.f, but do use it
|
|
// if there is a pattern or gradient
|
|
NSColor * sColor = path.strokeColor;
|
|
if(self.strokeColor != nil &&
|
|
((sColor != nil && sColor.alphaComponent != 0.f) ||
|
|
path.strokePattern != nil || path.strokeGradient != nil )) {
|
|
sColor = self.strokeColor;
|
|
}
|
|
|
|
sColor = [self proposedColorForColor:sColor];
|
|
|
|
// stroke layer
|
|
IJSVGStrokeLayer * strokeLayer = [[[IJSVGStrokeLayer alloc] init] autorelease];
|
|
strokeLayer.path = layer.path;
|
|
strokeLayer.fillColor = nil;
|
|
strokeLayer.strokeColor = sColor.CGColor;
|
|
|
|
CGFloat lineWidth = 1.f;
|
|
if(self.strokeWidth > 0.f) {
|
|
lineWidth = self.strokeWidth;
|
|
} else if(path.strokeWidth.value > 0.f) {
|
|
lineWidth = path.strokeWidth.value;
|
|
}
|
|
|
|
// work out line styles
|
|
IJSVGLineCapStyle lCapStyle;
|
|
IJSVGLineJoinStyle lJoinStyle;
|
|
|
|
// forced cap style
|
|
if(self.lineCapStyle != IJSVGLineCapStyleNone) {
|
|
lCapStyle = self.lineCapStyle;
|
|
} else {
|
|
lCapStyle = path.lineCapStyle;
|
|
}
|
|
|
|
// forced join style
|
|
if(self.lineJoinStyle != IJSVGLineJoinStyleNone) {
|
|
lJoinStyle = self.lineJoinStyle;
|
|
} else {
|
|
lJoinStyle = path.lineJoinStyle;
|
|
}
|
|
|
|
// line styles
|
|
strokeLayer.lineWidth = lineWidth;
|
|
strokeLayer.lineCap = [self lineCap:lCapStyle];
|
|
strokeLayer.lineJoin = [self lineJoin:lJoinStyle];
|
|
|
|
CGFloat strokeOpacity = 1.f;
|
|
if(path.strokeOpacity.value != 0.f) {
|
|
strokeOpacity = path.strokeOpacity.value;
|
|
}
|
|
strokeLayer.opacity = strokeOpacity;
|
|
|
|
// dashing
|
|
strokeLayer.lineDashPhase = path.strokeDashOffset.value;
|
|
strokeLayer.lineDashPattern = [self lineDashPattern:path];
|
|
|
|
return strokeLayer;
|
|
}
|
|
|
|
- (void)maskLayer:(IJSVGLayer *)layer
|
|
fromNode:(IJSVGNode *)node
|
|
{
|
|
// any clippath?
|
|
if(node.clipPath != nil || node.mask != nil) {
|
|
IJSVGGroupLayer * maskLayer = [[[IJSVGGroupLayer alloc] init] autorelease];
|
|
|
|
// add clip mask
|
|
if(node.clipPath != nil) {
|
|
IJSVGLayer * clip = [self layerForNode:node.clipPath];
|
|
|
|
// adjust the frame
|
|
[self adjustLayer:clip
|
|
toParentLayerFrame:layer];
|
|
|
|
// add the layer
|
|
[maskLayer addSublayer:clip];
|
|
}
|
|
|
|
// add the actual mask
|
|
if(node.mask != nil) {
|
|
IJSVGLayer * mask = [self layerForNode:node.mask];
|
|
|
|
// only move if bounding box
|
|
if(node.mask.units == IJSVGUnitObjectBoundingBox) {
|
|
[self adjustLayer:mask
|
|
toParentLayerFrame:layer];
|
|
}
|
|
|
|
// add the layer
|
|
[maskLayer addSublayer:mask];
|
|
}
|
|
|
|
// recursive colourize for each item
|
|
[self _recursiveColorLayersFromLayer:maskLayer
|
|
withColor:[NSColor whiteColor].CGColor];
|
|
|
|
// add the mask
|
|
layer.mask = maskLayer;
|
|
}
|
|
}
|
|
|
|
- (void)_recursiveColorLayersFromLayer:(IJSVGLayer *)layer
|
|
withColor:(CGColorRef)color
|
|
{
|
|
if([layer isKindOfClass:[IJSVGShapeLayer class]]) {
|
|
// has a proper fill method
|
|
for(IJSVGLayer * c in layer.sublayers) {
|
|
if([c isKindOfClass:[IJSVGGradientLayer class]] ||
|
|
[c isKindOfClass:[IJSVGPatternLayer class]]) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// set the fill
|
|
IJSVGShapeLayer * l = (IJSVGShapeLayer *)layer;
|
|
l.fillColor = color;
|
|
} else if([layer isKindOfClass:[IJSVGGroupLayer class]]) {
|
|
// go through its children, assume its a group
|
|
for(IJSVGLayer * child in layer.sublayers) {
|
|
[self _recursiveColorLayersFromLayer:child
|
|
withColor:color];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)adjustLayer:(IJSVGLayer *)childLayer
|
|
toParentLayerFrame:(IJSVGLayer *)parent
|
|
{
|
|
childLayer.frame = (CGRect){
|
|
.size = childLayer.frame.size,
|
|
.origin = CGPointMake((childLayer.frame.origin.x - parent.frame.origin.x),
|
|
(childLayer.frame.origin.y - parent.frame.origin.y))
|
|
};
|
|
}
|
|
|
|
- (NSArray<NSNumber *> *)lineDashPattern:(IJSVGNode *)node
|
|
{
|
|
NSMutableArray * arr = [[[NSMutableArray alloc] init] autorelease];
|
|
for(NSInteger i = 0; i < node.strokeDashArrayCount; i++) {
|
|
[arr addObject:@((CGFloat)node.strokeDashArray[i])];
|
|
}
|
|
return [[arr copy] autorelease];
|
|
}
|
|
|
|
- (NSString *)lineJoin:(IJSVGLineJoinStyle)joinStyle
|
|
{
|
|
switch (joinStyle) {
|
|
default:
|
|
case IJSVGLineJoinStyleMiter: {
|
|
return kCALineJoinMiter;
|
|
}
|
|
case IJSVGLineJoinStyleBevel: {
|
|
return kCALineJoinBevel;
|
|
}
|
|
case IJSVGLineJoinStyleRound: {
|
|
return kCALineJoinRound;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (NSString *)lineCap:(IJSVGLineCapStyle)capStyle
|
|
{
|
|
switch (capStyle) {
|
|
default:
|
|
case IJSVGLineCapStyleButt: {
|
|
return kCALineCapButt;
|
|
}
|
|
case IJSVGLineCapStyleRound: {
|
|
return kCALineCapRound;
|
|
}
|
|
case IJSVGLineCapStyleSquare: {
|
|
return kCALineCapSquare;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (NSString *)fillRule:(IJSVGWindingRule)rule
|
|
{
|
|
switch (rule) {
|
|
case IJSVGWindingRuleEvenOdd: {
|
|
return kCAFillRuleEvenOdd;
|
|
}
|
|
default: {
|
|
return kCAFillRuleNonZero;
|
|
}
|
|
}
|
|
}
|
|
|
|
+ (void)log:(IJSVGLayer *)layer
|
|
depth:(NSInteger)depth {
|
|
NSLog(@"%@%@: %@, Transforms: %@",[@"" stringByPaddingToLength:depth
|
|
withString:@"\t"
|
|
startingAtIndex:0],layer,
|
|
NSStringFromRect(layer.frame),
|
|
[IJSVGTransform affineTransformToSVGTransformAttributeString:layer.affineTransform]);
|
|
for(IJSVGLayer * sublayer in layer.sublayers) {
|
|
[self log:(IJSVGLayer *)sublayer
|
|
depth:depth++];
|
|
}
|
|
}
|
|
|
|
@end
|