Files
IJSVG/source/IJSVGLayerTree.m
T
Curtis Hard 6d4eb76803 Squashed commit of the following:
commit ffb55b0f31
Author: Curtis Hard <curtishard@me.com>
Date:   Fri Mar 2 13:28:40 2018 +0000

    Fixes viewBox origin translate

commit a4c032afa2
Author: Curtis Hard <curtishard@me.com>
Date:   Wed Feb 28 21:29:11 2018 +0000

    Added clip to viewport

commit abe8f4cba5
Author: Curtis Hard <curtishard@me.com>
Date:   Wed Feb 28 21:00:20 2018 +0000

    Fixes drawInRect not obeying origin

commit a16842271b
Author: 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

commit 58126e06e4
Author: Curtis Hard <curtishard@me.com>
Date:   Sun Feb 25 21:58:49 2018 +0000

    Fixes!

commit 5af5e054ab
Author: Curtis Hard <curtishard@me.com>
Date:   Fri Feb 23 22:37:53 2018 +0000

    Fixed defNode being removed

commit edd3aa1f33
Author: Curtis Hard <curtishard@me.com>
Date:   Fri Feb 23 16:29:54 2018 +0000

    excluded various elements from diff

commit 3366bc4fa5
Author: Curtis Hard <curtishard@me.com>
Date:   Fri Feb 23 14:05:03 2018 +0000

    Better optimaztion

commit 104002183b
Author: Curtis Hard <curtishard@me.com>
Date:   Thu Feb 22 22:17:50 2018 +0000

    Added rudimenatry inline styles -> stylesheet

commit 7010b7ea50
Author: Curtis Hard <curtishard@me.com>
Date:   Thu Feb 22 13:33:32 2018 +0000

    Correct order of cleanup

commit 5266a8c07a
Author: Curtis Hard <curtishard@me.com>
Date:   Thu Feb 22 08:22:17 2018 +0000

    corrent length of string

commit caf55e8bdf
Author: Curtis Hard <curtishard@me.com>
Date:   Wed Feb 21 20:45:17 2018 +0000

    removes useless def if required

commit 8160d05eba
Author: Curtis Hard <curtishard@me.com>
Date:   Tue Feb 20 14:12:02 2018 +0000

    Refactor of a few methods

commit a6d6a06521
Author: Curtis Hard <curtishard@me.com>
Date:   Tue Feb 20 09:40:21 2018 +0000

    Added collpasing of gradients

commit 103a4d71f6
Author: Curtis Hard <curtishard@me.com>
Date:   Mon Feb 19 19:07:30 2018 +0000

    Reduced floats even more

commit 98874b1d2c
Author: Curtis Hard <curtishard@me.com>
Date:   Mon Feb 19 18:53:02 2018 +0000

    More compression goodness

commit e742db31e0
Author: Curtis Hard <curtishard@me.com>
Date:   Mon Feb 19 14:02:15 2018 +0000

    Added intermediateParent

commit 198fd09f07
Author: Curtis Hard <curtishard@me.com>
Date:   Mon Feb 19 11:51:37 2018 +0000

    Fixes! and performance increases

commit 3493194b1b
Author: Curtis Hard <curtishard@me.com>
Date:   Mon Feb 19 08:20:02 2018 +0000

    Scale computation

commit 0016775eaf
Author: Curtis Hard <curtishard@me.com>
Date:   Sun Feb 18 22:33:26 2018 +0000

    More goodness

commit 304a04cc22
Author: Curtis Hard <curtishard@me.com>
Date:   Sun Feb 18 22:32:04 2018 +0000

    Vastly improved the exporter

commit e4fd0af582
Author: Curtis Hard <curtishard@me.com>
Date:   Sun Feb 18 15:20:28 2018 +0000

    This is insanely important!

commit 69a2a0c97e
Author: Curtis Hard <curtishard@me.com>
Date:   Sun Feb 4 22:02:19 2018 +0000

    Refactor

commit 5299bb0479
Author: Curtis Hard <curtishard@me.com>
Date:   Sun Feb 4 21:58:21 2018 +0000

    will continue to use CoreAnimation for the time being

commit 4f1943cad1
Author: Curtis Hard <curtishard@me.com>
Date:   Sun Feb 4 21:57:46 2018 +0000

    Fixes gradient strokes

commit f02d186293
Author: Curtis Hard <curtishard@me.com>
Date:   Sun Feb 4 11:15:04 2018 +0000

    trying to get masks to work

commit 3291718cfb
Author: Curtis Hard <curtishard@me.com>
Date:   Fri Feb 2 22:35:22 2018 +0000

    Beginning of quartz renderer

commit 6fbaaf5884
Author: Curtis Hard <curtishard@me.com>
Date:   Mon Jan 29 22:32:08 2018 +0000

    Removed useless log

commit abc65797ea
Author: Curtis Hard <curtishard@me.com>
Date:   Mon Jan 29 21:37:45 2018 +0000

    I think gradients work :D

commit af5a1c2718
Author: Curtis Hard <curtishard@me.com>
Date:   Sun Jan 28 22:18:02 2018 +0000

    Possible fx and fy things…

commit fb9a5282b9
Author: Curtis Hard <curtishard@me.com>
Date:   Sun Jan 28 19:22:40 2018 +0000

    Even more gradient fixes

commit d83933a103
Author: Curtis Hard <curtishard@me.com>
Date:   Sun Jan 28 14:21:50 2018 +0000

    Various improvements

commit bd7a0d5021
Author: Curtis Hard <curtishard@me.com>
Date:   Sat Jan 27 22:26:33 2018 +0000

    Start to linear

commit a9a038568c
Author: Curtis Hard <curtishard@me.com>
Date:   Sat Jan 27 21:14:25 2018 +0000

    This kind of actually works...

commit 77fbb38b6f
Author: 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?

commit 12c3191569
Author: Curtis Hard <curtishard@me.com>
Date:   Sat Jan 27 15:32:15 2018 +0000

    Added method for findind absolute position

commit e3e9626ef7
Author: Curtis Hard <curtishard@me.com>
Date:   Sat Jan 27 14:25:03 2018 +0000

    Fixes crash due to parentNode on temp groups being released

commit 1160d89f16
Author: Curtis Hard <curtishard@me.com>
Date:   Fri Jan 26 22:29:22 2018 +0000

    Fixes use statements with transforms

commit 1575cbfde8
Author: Curtis Hard <curtishard@me.com>
Date:   Fri Jan 26 18:16:03 2018 +0000

    Moved color tree over to modern syntax

commit 5c4c2eee91
Author: Curtis Hard <curtishard@me.com>
Date:   Thu Jan 25 18:23:53 2018 +0000

    Added HSL/HSLA support

commit 087b13e58f
Author: Curtis Hard <curtishard@me.com>
Date:   Wed Jan 24 22:24:43 2018 +0000

    Various image loading issues resolved from base64 images

commit 1183e167aa
Author: Curtis Hard <curtishard@me.com>
Date:   Wed Jan 24 21:21:41 2018 +0000

    Rect issue fix

commit 4dbfc59437
Author: Curtis Hard <curtishard@me.com>
Date:   Wed Jan 24 21:19:24 2018 +0000

    Moved transforms over from being concatinated to seperate calls

commit 409bd509fa
Author: Curtis Hard <curtishard@me.com>
Date:   Wed Jan 24 20:30:38 2018 +0000

    Fixes color issue

commit 8ae1d1b4e0
Author: Curtis Hard <curtishard@me.com>
Date:   Wed Jan 24 18:41:08 2018 +0000

    Removed check as its not needed here

commit 7243fbe5ff
Author: 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

commit 51b9a5e85f
Author: 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)

commit 40098589de
Author: 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 :-))

commit 7cb96b21f2
Author: 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

commit 1bff7c6970
Author: Curtis Hard <curtishard@me.com>
Date:   Mon Jan 22 21:48:07 2018 +0000

    Various fixes… still going…
2018-03-02 13:51:49 +00:00

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