diff --git a/Framework/IJSVG/IJSVG.xcodeproj/project.pbxproj b/Framework/IJSVG/IJSVG.xcodeproj/project.pbxproj index 219502f..c80647e 100644 --- a/Framework/IJSVG/IJSVG.xcodeproj/project.pbxproj +++ b/Framework/IJSVG/IJSVG.xcodeproj/project.pbxproj @@ -17,6 +17,8 @@ 590EB7A527F637EF0047CECF /* IJSVGTransformLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 590EB7A327F637EF0047CECF /* IJSVGTransformLayer.m */; }; 5910A9A628427AD600BD1F03 /* IJSVGMask.h in Headers */ = {isa = PBXBuildFile; fileRef = 5910A9A428427AD600BD1F03 /* IJSVGMask.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5910A9A728427AD600BD1F03 /* IJSVGMask.m in Sources */ = {isa = PBXBuildFile; fileRef = 5910A9A528427AD600BD1F03 /* IJSVGMask.m */; }; + 5910A9AA284377D600BD1F03 /* IJSVGClipPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 5910A9A8284377D600BD1F03 /* IJSVGClipPath.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5910A9AB284377D600BD1F03 /* IJSVGClipPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 5910A9A9284377D600BD1F03 /* IJSVGClipPath.m */; }; 5919E65723F47FF60051873A /* IJSVGUnitRect.h in Headers */ = {isa = PBXBuildFile; fileRef = 5919E65523F47FF60051873A /* IJSVGUnitRect.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5919E65823F47FF60051873A /* IJSVGUnitRect.m in Sources */ = {isa = PBXBuildFile; fileRef = 5919E65623F47FF60051873A /* IJSVGUnitRect.m */; }; 5919E65B23F480330051873A /* IJSVGUnitPoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 5919E65923F480330051873A /* IJSVGUnitPoint.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -167,6 +169,8 @@ 590EB7A327F637EF0047CECF /* IJSVGTransformLayer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IJSVGTransformLayer.m; sourceTree = ""; }; 5910A9A428427AD600BD1F03 /* IJSVGMask.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IJSVGMask.h; sourceTree = ""; }; 5910A9A528427AD600BD1F03 /* IJSVGMask.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IJSVGMask.m; sourceTree = ""; }; + 5910A9A8284377D600BD1F03 /* IJSVGClipPath.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IJSVGClipPath.h; sourceTree = ""; }; + 5910A9A9284377D600BD1F03 /* IJSVGClipPath.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IJSVGClipPath.m; sourceTree = ""; }; 5919E65523F47FF60051873A /* IJSVGUnitRect.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IJSVGUnitRect.h; sourceTree = ""; }; 5919E65623F47FF60051873A /* IJSVGUnitRect.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IJSVGUnitRect.m; sourceTree = ""; }; 5919E65923F480330051873A /* IJSVGUnitPoint.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IJSVGUnitPoint.h; sourceTree = ""; }; @@ -465,6 +469,8 @@ 590AA090280DDDA3002BBE12 /* IJSVGFilterEffect.m */, 5910A9A428427AD600BD1F03 /* IJSVGMask.h */, 5910A9A528427AD600BD1F03 /* IJSVGMask.m */, + 5910A9A8284377D600BD1F03 /* IJSVGClipPath.h */, + 5910A9A9284377D600BD1F03 /* IJSVGClipPath.m */, ); path = Nodes; sourceTree = ""; @@ -683,6 +689,7 @@ 59AC865A2810156B00CD5685 /* IJSVGThreadManager.h in Headers */, 5978C46A280A241200D25296 /* IJSVGRootLayer.h in Headers */, 59F9EAF82808BB5F00188ACB /* IJSVGViewBox.h in Headers */, + 5910A9AA284377D600BD1F03 /* IJSVGClipPath.h in Headers */, 5910A9A628427AD600BD1F03 /* IJSVGMask.h in Headers */, 59EB760B23905F7300F5AE63 /* IJSVGStyleSheetSelector.h in Headers */, ); @@ -782,6 +789,7 @@ 59EB763523905F7300F5AE63 /* IJSVGStyleSheetSelector.m in Sources */, 590AA08E280DDD90002BBE12 /* IJSVGFilter.m in Sources */, 59E7CFB023B148600077D599 /* IJSVGCommandParser.m in Sources */, + 5910A9AB284377D600BD1F03 /* IJSVGClipPath.m in Sources */, 59EB760723905F7300F5AE63 /* IJSVGNode.m in Sources */, 5919E65C23F480330051873A /* IJSVGUnitPoint.m in Sources */, 59EB75E923905F7300F5AE63 /* IJSVGStringAdditions.m in Sources */, diff --git a/Framework/IJSVG/IJSVG/Source/Layers/IJSVGLayer.h b/Framework/IJSVG/IJSVG/Source/Layers/IJSVGLayer.h index 59c64fb..08b45d8 100644 --- a/Framework/IJSVG/IJSVG/Source/Layers/IJSVGLayer.h +++ b/Framework/IJSVG/IJSVG/Source/Layers/IJSVGLayer.h @@ -18,6 +18,11 @@ @class IJSVGLayer; @class IJSVGFilter; +typedef NS_OPTIONS(NSUInteger, IJSVGLayerDrawingOptions) { + IJSVGLayerDrawingOptionNone = 0, + IJSVGLayerDrawingOptionIgnoreClipping = 1 << 1 +}; + typedef NS_ENUM(NSUInteger, IJSVGLayerFillType) { IJSVGLayerFillTypeColor, IJSVGLayerFillTypePattern, @@ -26,7 +31,7 @@ typedef NS_ENUM(NSUInteger, IJSVGLayerFillType) { }; typedef NS_OPTIONS(NSUInteger, IJSVGLayerTraits) { - IJSVGLayerTraitGroup + IJSVGLayerTraitGroup = 1 << 1 }; @protocol IJSVGPathableLayer @@ -54,11 +59,20 @@ typedef NS_OPTIONS(NSUInteger, IJSVGLayerTraits) { @end -@protocol IJSVGDrawableLayer +@protocol IJSVGClippingLayer + +@required +@property (nonatomic, assign) CGRect clippingBoundingBox; +@property (nonatomic, assign) CGAffineTransform clippingTransform; + +@end + +@protocol IJSVGDrawableLayer @required @property (nonatomic, assign) CGBlendMode blendingMode; @property (nonatomic, strong) CALayer* clipLayer; +@property (nonatomic, strong) NSArray*>* clipLayers; @property (nonatomic, strong) CALayer* maskLayer; @property (nonatomic, copy) CAShapeLayerFillRule clipRule; @property (nonatomic, copy) CAShapeLayerFillRule fillRule; @@ -95,6 +109,7 @@ typedef NS_OPTIONS(NSUInteger, IJSVGLayerTraits) { @property (nonatomic, assign) CGBlendMode blendingMode; @property (nonatomic, assign) CGPoint absoluteOrigin; @property (nonatomic, strong) CALayer* clipLayer; +@property (nonatomic, strong) NSArray*>* clipLayers; @property (nonatomic, copy) CAShapeLayerFillRule clipRule; @property (nonatomic, copy) CAShapeLayerFillRule fillRule; @property (nonatomic, strong) CALayer* maskLayer; @@ -107,6 +122,8 @@ typedef NS_OPTIONS(NSUInteger, IJSVGLayerTraits) { @property (nonatomic, assign) CALayer* referencingLayer; @property (nonatomic, assign) CGRect maskingBoundingBox; @property (nonatomic, assign) CGRect maskingClippingRect; +@property (nonatomic, assign) CGRect clippingBoundingBox; +@property (nonatomic, assign) CGAffineTransform clippingTransform; + (IJSVGLayerFillType)fillTypeForFill:(id)fill; @@ -123,24 +140,33 @@ typedef NS_OPTIONS(NSUInteger, IJSVGLayerTraits) { withOffset:(CGPoint)offset; + (CGImageRef)newMaskImageForLayer:(CALayer*)layer + options:(IJSVGLayerDrawingOptions)options scale:(CGFloat)scale; + (CGImageRef)newImageForLayer:(CALayer*)layer + options:(IJSVGLayerDrawingOptions)options + colorSpace:(CGColorSpaceRef)colorSpace + bitmapInfo:(uint32_t)bitmapInfo + scale:(CGFloat)scale; + ++ (CGImageRef)newImageWithSize:(CGSize)size + drawBlock:(void (^)(CGContextRef context))drawBlock colorSpace:(CGColorSpaceRef)colorSpace bitmapInfo:(uint32_t)bitmapInfo scale:(CGFloat)scale; + (void)renderLayer:(CALayer*)layer - inContext:(CGContextRef)ctx; + inContext:(CGContextRef)ctx + options:(IJSVGLayerDrawingOptions)options; + (void)applyBlendingMode:(CGBlendMode)blendMode toContext:(CGContextRef)ctx drawingBlock:(dispatch_block_t)drawingBlock; -+ (void)clipContextWithClip:(CALayer*)clipLayer - toLayer:(CALayer*)layer - inContext:(CGContextRef)ctx - drawingBlock:(dispatch_block_t)drawingBlock; ++ (void)clipContextWithClipLayers:(NSArray*>*)clipLayers + toLayer:(CALayer*)layer + inContext:(CGContextRef)ctx + drawingBlock:(dispatch_block_t)drawingBlock; + (void)clipContextWithMask:(CALayer*)maskLayer toLayer:(CALayer*)layer diff --git a/Framework/IJSVG/IJSVG/Source/Layers/IJSVGLayer.m b/Framework/IJSVG/IJSVG/Source/Layers/IJSVGLayer.m index cb4b247..9e3b201 100644 --- a/Framework/IJSVG/IJSVG/Source/Layers/IJSVGLayer.m +++ b/Framework/IJSVG/IJSVG/Source/Layers/IJSVGLayer.m @@ -83,7 +83,7 @@ // we can stop looking if([parentLayer isKindOfClass:IJSVGRootLayer.class] == YES) { break; - } + } } return CGAffineTransformConcat(identity, layer.affineTransform); } @@ -116,6 +116,7 @@ intoUserSpaceUnitsFrom:(CALayer*)fromLayer + (void)performBasicRenderOfLayer:(CALayer*)layer inContext:(CGContextRef)ctx + options:(IJSVGLayerDrawingOptions)options { dispatch_block_t drawingBlock = ^{ [layer performRenderInContext:ctx]; @@ -124,22 +125,26 @@ intoUserSpaceUnitsFrom:(CALayer*)fromLayer toContext:ctx drawingBlock:^{ // we need to clip first - if(layer.clipLayer == nil) { + IJSVGLayerDrawingOptions opt = IJSVGLayerDrawingOptionIgnoreClipping; + BOOL ignoreClipping = (options & opt) == opt; + if(ignoreClipping == YES || layer.clipLayers == nil) { drawingBlock(); return; } - [self clipContextWithClip:layer.clipLayer - toLayer:layer - inContext:ctx - drawingBlock:drawingBlock]; + [self clipContextWithClipLayers:layer.clipLayers + toLayer:layer + inContext:ctx + drawingBlock:drawingBlock]; }]; } + (void)renderLayer:(CALayer*)layer inContext:(CGContextRef)ctx + options:(IJSVGLayerDrawingOptions)options { [self performBasicRenderOfLayer:layer - inContext:ctx]; + inContext:ctx + options:options]; } + (void)applyBlendingMode:(CGBlendMode)blendMode @@ -156,71 +161,134 @@ intoUserSpaceUnitsFrom:(CALayer*)fromLayer drawingBlock(); } -/// Shape layers are the only thing we can clip, as they contain a path, however -/// the layer passed to us from a clip could have groups contained with in it that -/// have transforms on them, so simply recursively iterate over them and concat -/// their transforms down to the path and clip at the end with the path. -+ (void)recursivelyClip:(CALayer*)layer - transform:(CGAffineTransform)transform - inContext:(CGContextRef)ctx +///// Shape layers are the only thing we can clip, as they contain a path, however +///// the layer passed to us from a clip could have groups contained with in it that +///// have transforms on them, so simply recursively iterate over them and concat +///// their transforms down to the path and clip at the end with the path. +//+ (void)recursivelyClip:(CALayer*)layer +// transform:(CGAffineTransform)transform +// inContext:(CGContextRef)ctx +//{ +// if([layer isKindOfClass:IJSVGShapeLayer.class]) { +// [self clipContextWithShapeLayer:(IJSVGShapeLayer*)layer +// transform:transform +// inContext:ctx]; +// return; +// } +// +// for(CALayer* drawableLayer in layer.sublayers) { +// CGAffineTransform drawTransform = CGAffineTransformConcat(transform, +// drawableLayer.affineTransform); +// +// // If its not a shape layer, just go down the tree until we find one +// if([drawableLayer isKindOfClass:IJSVGShapeLayer.class] == NO) { +// [self recursivelyClip:drawableLayer +// transform:drawTransform +// inContext:ctx]; +// continue; +// } +// IJSVGShapeLayer* shapeLayer = (IJSVGShapeLayer*)drawableLayer; +// [self clipContextWithShapeLayer:shapeLayer +// transform:transform +// inContext:ctx]; +// } +//} +// +//+ (void)clipContextWithShapeLayer:(IJSVGShapeLayer*)shapeLayer +// transform:(CGAffineTransform)transform +// inContext:(CGContextRef)ctx +//{ +// CGAffineTransform drawTransform = CGAffineTransformConcat(transform, +// shapeLayer.affineTransform); +// // Shape layers paths are reset back to 0,0 origin, so in order to be +// // correct path, we need to shift it back to where it belongs. +// drawTransform = CGAffineTransformTranslate(transform, +// shapeLayer.frame.origin.x, +// shapeLayer.frame.origin.y); +// CGPathRef path = CGPathCreateCopyByTransformingPath(shapeLayer.path, +// &drawTransform); +// CGContextAddPath(ctx, path); +// CGPathRelease(path); +//} +// +//+ (void)clipContextWithClip:(CALayer*)clipLayer +// toLayer:(CALayer*)layer +// inContext:(CGContextRef)ctx +// drawingBlock:(dispatch_block_t)drawingBlock +//{ +// CGContextSaveGState(ctx); +// [self recursivelyClip:clipLayer +// transform:clipLayer.affineTransform +// inContext:ctx]; +// if([layer.clipRule isEqualToString:kCAFillRuleEvenOdd]) { +// CGContextEOClip(ctx); +// } else { +// CGContextClip(ctx); +// } +// drawingBlock(); +// CGContextRestoreGState(ctx); +//} + ++ (void)clipContextWithClipLayers:(NSArray*>*)clipLayers + toLayer:(CALayer*)layer + inContext:(CGContextRef)ctx + drawingBlock:(dispatch_block_t)drawingBlock { - if([layer isKindOfClass:IJSVGShapeLayer.class]) { - [self clipContextWithShapeLayer:(IJSVGShapeLayer*)layer - transform:transform - inContext:ctx]; + CGContextSaveGState(ctx); + const CGFloat maskTolerance = .5f; + const CGFloat scale = layer.backingScaleFactor; + CGRect rect = layer.clippingBoundingBox; + + // weed out the no empty clipPaths + if(CGRectEqualToRect(rect, CGRectZero) == YES) { + drawingBlock(); return; } - for(CALayer* drawableLayer in layer.sublayers) { - CGAffineTransform drawTransform = CGAffineTransformConcat(transform, - drawableLayer.affineTransform); - - // If its not a shape layer, just go down the tree until we find one - if([drawableLayer isKindOfClass:IJSVGShapeLayer.class] == NO) { - [self recursivelyClip:drawableLayer - transform:drawTransform - inContext:ctx]; - continue; + CGSize size = CGSizeMake(CGRectGetWidth(rect), + CGRectGetHeight(rect)); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray(); + void (^drawBlock)(CGContextRef maskCtx) = ^(CGContextRef maskCtx) { + CGImageRef maskImage = NULL; + for(CALayer* clipLayer in clipLayers) { + CGContextSaveGState(maskCtx); + CGRect layerRect = clipLayer.outerBoundingBox; + layerRect = CGRectApplyAffineTransform(layerRect, layer.clippingTransform); + CGImageRef layerMask = [self newMaskImageForLayer:clipLayer + options:IJSVGLayerDrawingOptionIgnoreClipping + scale:scale]; + if(maskImage != NULL) { + CGRect maskRect = CGRectInset(rect, -maskTolerance, -maskTolerance); + CGContextClipToMask(maskCtx, maskRect, maskImage); + } + CGContextDrawImage(maskCtx, layerRect, layerMask); + CGImageRelease(layerMask); + if(maskImage != NULL) { + CGImageRelease(maskImage); + } + maskImage = CGBitmapContextCreateImage(maskCtx); + CGContextRestoreGState(maskCtx); } - IJSVGShapeLayer* shapeLayer = (IJSVGShapeLayer*)drawableLayer; - [self clipContextWithShapeLayer:shapeLayer - transform:transform - inContext:ctx]; - } -} - -+ (void)clipContextWithShapeLayer:(IJSVGShapeLayer*)shapeLayer - transform:(CGAffineTransform)transform - inContext:(CGContextRef)ctx -{ - CGAffineTransform drawTransform = CGAffineTransformConcat(transform, - shapeLayer.affineTransform); - // Shape layers paths are reset back to 0,0 origin, so in order to be - // correct path, we need to shift it back to where it belongs. - drawTransform = CGAffineTransformTranslate(transform, - shapeLayer.frame.origin.x, - shapeLayer.frame.origin.y); - CGPathRef path = CGPathCreateCopyByTransformingPath(shapeLayer.path, - &drawTransform); - CGContextAddPath(ctx, path); - CGPathRelease(path); -} - -+ (void)clipContextWithClip:(CALayer*)clipLayer - toLayer:(CALayer*)layer - inContext:(CGContextRef)ctx - drawingBlock:(dispatch_block_t)drawingBlock -{ - CGContextSaveGState(ctx); - [self recursivelyClip:clipLayer - transform:clipLayer.affineTransform - inContext:ctx]; - if([layer.clipRule isEqualToString:kCAFillRuleEvenOdd]) { - CGContextEOClip(ctx); - } else { - CGContextClip(ctx); - } + CGImageRelease(maskImage); + }; + + CGImageRef image = [self newImageWithSize:size + drawBlock:drawBlock + colorSpace:colorSpace + bitmapInfo:kCGImageAlphaNone + scale:scale]; + + // we need to transform the mask rect back based on the inner bounding + // box of the layer, as this could be a group layer that inner box is + // not at 0,0. + CGRect layerRect = layer.innerBoundingBox; + CGAffineTransform transform = CGAffineTransformMakeTranslation(CGRectGetMinX(layerRect), + CGRectGetMinY(layerRect)); + rect = CGRectApplyAffineTransform(rect, transform); + CGContextClipToMask(ctx, CGRectIntegral(rect), image); drawingBlock(); + CGImageRelease(image); + CGColorSpaceRelease(colorSpace); CGContextRestoreGState(ctx); } @@ -232,8 +300,8 @@ intoUserSpaceUnitsFrom:(CALayer*)fromLayer CGContextSaveGState(ctx); CGFloat scale = layer.backingScaleFactor; CGImageRef maskImage = [self newMaskImageForLayer:maskLayer + options:IJSVGLayerDrawingOptionNone scale:scale]; - CGContextClipToRect(ctx, maskLayer.maskingClippingRect); CGContextClipToMask(ctx, maskLayer.maskingBoundingBox, maskImage); drawingBlock(); @@ -242,10 +310,12 @@ intoUserSpaceUnitsFrom:(CALayer*)fromLayer } + (CGImageRef)newMaskImageForLayer:(CALayer*)layer + options:(IJSVGLayerDrawingOptions)options scale:(CGFloat)scale { CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray(); CGImageRef ref = [self newImageForLayer:layer + options:options colorSpace:colorSpace bitmapInfo:kCGImageAlphaNone scale:scale]; @@ -253,23 +323,41 @@ intoUserSpaceUnitsFrom:(CALayer*)fromLayer return ref; } ++ (CGImageRef)newImageWithSize:(CGSize)size + drawBlock:(void (^)(CGContextRef context))drawBlock + colorSpace:(CGColorSpaceRef)colorSpace + bitmapInfo:(uint32_t)bitmapInfo + scale:(CGFloat)scale +{ + CGContextRef offscreenContext = CGBitmapContextCreate(NULL, + ceilf(size.width*scale), + ceilf(size.height*scale), + 8, 0, colorSpace, bitmapInfo); + CGContextScaleCTM(offscreenContext, scale, scale); + drawBlock(offscreenContext); + CGImageRef image = CGBitmapContextCreateImage(offscreenContext); + CGContextRelease(offscreenContext); + return image; +} + + (CGImageRef)newImageForLayer:(CALayer*)layer + options:(IJSVGLayerDrawingOptions)options colorSpace:(CGColorSpaceRef)colorSpace bitmapInfo:(uint32_t)bitmapInfo scale:(CGFloat)scale { - CGRect frame = layer.outerBoundingBox; CGRect bounds = layer.innerBoundingBox; CGContextRef offscreenContext = CGBitmapContextCreate(NULL, - ceilf(frame.size.width * scale), - ceilf(frame.size.height * scale), + ceilf(frame.size.width*scale), + ceilf(frame.size.height*scale), 8, 0, colorSpace, bitmapInfo); CGContextScaleCTM(offscreenContext, scale, scale); CGContextConcatCTM(offscreenContext, CGAffineTransformMakeTranslation(-bounds.origin.x, -bounds.origin.y)); [IJSVGLayer renderLayer:layer - inContext:offscreenContext]; + inContext:offscreenContext + options:options]; CGImageRef image = CGBitmapContextCreateImage(offscreenContext); CGContextRelease(offscreenContext); return image; @@ -392,7 +480,10 @@ intoUserSpaceUnitsFrom:(CALayer*)fromLayer // make sure its applied to any mask or clipPath _maskLayer.backingScaleFactor = newFactor; - _clipLayer.backingScaleFactor = newFactor; +// _clipLayer.backingScaleFactor = newFactor; + for(CALayer* clipLayer in _clipLayers) { + clipLayer.backingScaleFactor = newFactor; + } } - (void)performRenderInContext:(CGContextRef)ctx @@ -456,7 +547,7 @@ intoUserSpaceUnitsFrom:(CALayer*)fromLayer - (BOOL)requiresBackingScale { - return _maskLayer != nil || _clipLayer != nil; + return _maskLayer != nil || (_clipLayers != nil && _clipLayers.count != 0); } - (IJSVGShapeLayer*)maskingLayer @@ -467,7 +558,8 @@ intoUserSpaceUnitsFrom:(CALayer*)fromLayer - (void)renderInContext:(CGContextRef)ctx { [IJSVGLayer renderLayer:self - inContext:ctx]; + inContext:ctx + options:IJSVGLayerDrawingOptionNone]; } - (CGRect)absoluteFrame diff --git a/Framework/IJSVG/IJSVG/Source/Layers/IJSVGShapeLayer.h b/Framework/IJSVG/IJSVG/Source/Layers/IJSVGShapeLayer.h index 0454669..49ae88d 100644 --- a/Framework/IJSVG/IJSVG/Source/Layers/IJSVGShapeLayer.h +++ b/Framework/IJSVG/IJSVG/Source/Layers/IJSVGShapeLayer.h @@ -29,6 +29,7 @@ @property (nonatomic, assign) CGPoint originalPathOrigin; @property (nonatomic, assign) IJSVGPrimitivePathType primitiveType; @property (nonatomic, strong) CALayer* clipLayer; +@property (nonatomic, strong) NSArray*>* clipLayers; @property (nonatomic, strong) CALayer* maskLayer; @property (nonatomic, readonly) CGRect absoluteFrame; @property (nonatomic, assign) CGRect boundingBox; @@ -40,6 +41,8 @@ @property (nonatomic, copy) CAShapeLayerFillRule clipRule; @property (nonatomic, assign) CGRect maskingBoundingBox; @property (nonatomic, assign) CGRect maskingClippingRect; +@property (nonatomic, assign) CGRect clippingBoundingBox; +@property (nonatomic, assign) CGAffineTransform clippingTransform; - (void)applySublayerMaskToContext:(CGContextRef)context forSublayer:(IJSVGLayer*)sublayer diff --git a/Framework/IJSVG/IJSVG/Source/Layers/IJSVGShapeLayer.m b/Framework/IJSVG/IJSVG/Source/Layers/IJSVGShapeLayer.m index 04c92aa..c241df4 100644 --- a/Framework/IJSVG/IJSVG/Source/Layers/IJSVGShapeLayer.m +++ b/Framework/IJSVG/IJSVG/Source/Layers/IJSVGShapeLayer.m @@ -46,7 +46,7 @@ - (BOOL)requiresBackingScale { - return _maskLayer != nil || _clipLayer != nil; + return _maskLayer != nil || (_clipLayers != nil && _clipLayers.count != 0); } - (void)setBackingScaleFactor:(CGFloat)newFactor @@ -61,7 +61,10 @@ // make sure its applied to any mask or clipPath _maskLayer.backingScaleFactor = newFactor; - _clipLayer.backingScaleFactor = newFactor; + + for(CALayer* clipLayer in _clipLayers) { + clipLayer.backingScaleFactor = newFactor; + } [self setNeedsDisplay]; } @@ -128,7 +131,8 @@ - (void)renderInContext:(CGContextRef)ctx { [IJSVGLayer renderLayer:(IJSVGLayer*)self - inContext:ctx]; + inContext:ctx + options:IJSVGLayerDrawingOptionNone]; } -(NSArray*>*)debugLayers diff --git a/Framework/IJSVG/IJSVG/Source/Layers/IJSVGTransformLayer.m b/Framework/IJSVG/IJSVG/Source/Layers/IJSVGTransformLayer.m index 6b06348..12c9425 100644 --- a/Framework/IJSVG/IJSVG/Source/Layers/IJSVGTransformLayer.m +++ b/Framework/IJSVG/IJSVG/Source/Layers/IJSVGTransformLayer.m @@ -10,6 +10,7 @@ @implementation IJSVGTransformLayer +@synthesize clipLayers; @synthesize backingScaleFactor; @synthesize renderQuality; @synthesize requiresBackingScale; @@ -24,6 +25,8 @@ @synthesize innerBoundingBox; @synthesize maskingBoundingBox; @synthesize maskingClippingRect; +@synthesize clippingBoundingBox; +@synthesize clippingTransform; - (id)actionForKey:(NSString*)event { @@ -43,7 +46,8 @@ - (void)renderInContext:(CGContextRef)ctx { [IJSVGLayer renderLayer:self - inContext:ctx]; + inContext:ctx + options:IJSVGLayerDrawingOptionNone]; } - (void)performRenderInContext:(CGContextRef)ctx diff --git a/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGClipPath.m b/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGClipPath.m index c373b5b..5c4c813 100644 --- a/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGClipPath.m +++ b/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGClipPath.m @@ -17,6 +17,7 @@ self.contentUnits = IJSVGUnitUserSpaceOnUse; self.windingRule = IJSVGWindingRuleNonZero; self.overflowVisibility = IJSVGOverflowVisibilityHidden; + self.fill = [IJSVGColorNode colorNodeWithColor:NSColor.whiteColor]; } - (IJSVGUnitType)contentUnitsWithReferencingNodeBounds:(CGRect*)bounds @@ -31,4 +32,23 @@ return units; } +- (void)postProcess +{ + // clip paths only allow shapes in them, nothing else, we can simply + // check the node types for the trait of pathed + IJSVGNodeTraits childTraits = IJSVGNodeTraitPathed; + for(IJSVGNode* childNode in self.children.copy) { + if([childNode matchesTraits:childTraits] == NO) { + BOOL remove = YES; + IJSVGNodeType type = childNode.type; + if(type == IJSVGNodeTypeUse) { + remove = [(IJSVGGroup*)childNode childrenMatchTraits:childTraits] == NO; + } + if(remove == YES) { + [self removeChild:childNode]; + } + } + } +} + @end diff --git a/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGFilter.m b/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGFilter.m index 8651f6e..cb078a6 100644 --- a/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGFilter.m +++ b/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGFilter.m @@ -26,6 +26,7 @@ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); uint32_t info = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little; CGImageRef originalImage = [IJSVGLayer newImageForLayer:layer + options:IJSVGLayerDrawingOptionNone colorSpace:colorSpace bitmapInfo:info scale:scale]; diff --git a/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGGroup.h b/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGGroup.h index 7f1c709..3242f74 100644 --- a/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGGroup.h +++ b/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGGroup.h @@ -20,5 +20,8 @@ - (void)addChild:(IJSVGNode*)child; - (void)addChildren:(NSArray*)children; +- (void)removeChild:(IJSVGNode*)child; +- (void)removeChildren:(NSArray*)children; +- (BOOL)childrenMatchTraits:(IJSVGNodeTraits)traits; @end diff --git a/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGGroup.m b/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGGroup.m index 58f5947..b84f8d1 100644 --- a/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGGroup.m +++ b/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGGroup.m @@ -63,6 +63,23 @@ } } +- (void)removeChildren:(NSArray*)children +{ + for(IJSVGNode* node in children) { + [self removeChild:node]; + } +} + +- (BOOL)childrenMatchTraits:(IJSVGNodeTraits)traits +{ + for(IJSVGNode* node in _children) { + if([node matchesTraits:traits] == NO) { + return NO; + } + } + return YES; +} + - (CGRect)bounds { CGRect rect = CGRectZero; diff --git a/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGNode.h b/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGNode.h index 66c7c61..5bc20ad 100644 --- a/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGNode.h +++ b/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGNode.h @@ -30,7 +30,8 @@ typedef void (^IJSVGNodeWalkHandler)(IJSVGNode* node, BOOL* allowChildNodes, BOO typedef NS_OPTIONS(NSInteger, IJSVGNodeTraits) { IJSVGNodeTraitNone = 0, IJSVGNodeTraitStroked = 1 << 0, - IJSVGNodeTraitPaintable = 1 << 1 + IJSVGNodeTraitPaintable = 1 << 1, + IJSVGNodeTraitPathed = 1 << 2 }; typedef NS_ENUM(NSInteger, IJSVGNodeType) { @@ -184,6 +185,7 @@ void IJSVGAssertPaintableObject(id object); + (BOOL)typeIsAShape:(IJSVGNodeType)type; - (void)setDefaults; +- (void)postProcess; - (void)applyPropertiesFromNode:(IJSVGNode*)node; - (IJSVGUnitType)contentUnitsWithReferencingNodeBounds:(CGRect*)bounds; diff --git a/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGNode.m b/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGNode.m index 39a720e..3252693 100644 --- a/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGNode.m +++ b/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGNode.m @@ -267,6 +267,10 @@ return node; } +- (void)postProcess +{ +} + - (void)setFill:(IJSVGNode*)fill { NSAssert([fill matchesTraits:IJSVGNodeTraitPaintable] || fill == nil, @"Fill must a paintable node."); diff --git a/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGPath.m b/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGPath.m index a69cc41..30c668c 100644 --- a/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGPath.m +++ b/Framework/IJSVG/IJSVG/Source/Nodes/IJSVGPath.m @@ -26,6 +26,11 @@ return self; } +- (void)setDefaults +{ + [self addTraits:IJSVGNodeTraitPathed]; +} + - (CGRect)bounds { return self.pathBoundingBox; diff --git a/Framework/IJSVG/IJSVG/Source/Parsing/IJSVGParser.m b/Framework/IJSVG/IJSVG/Source/Parsing/IJSVGParser.m index 59e1df1..28f31e2 100644 --- a/Framework/IJSVG/IJSVG/Source/Parsing/IJSVGParser.m +++ b/Framework/IJSVG/IJSVG/Source/Parsing/IJSVGParser.m @@ -269,6 +269,7 @@ static NSArray* _IJSVGUseElementOverwritingAttributes = nil; if(postProcessBlock != nil) { postProcessBlock(); } + [_rootNode postProcess]; } - (void)computeDefsForElement:(NSXMLElement*)element @@ -324,8 +325,6 @@ static NSArray* _IJSVGUseElementOverwritingAttributes = nil; __block IJSVGStyle* nodeStyle = nil; // precache the attributes, this is quicker than asking for it each time - CGRect computedBounds = CGRectZero; - IJSVGUnitType unitType = [node contentUnitsWithReferencingNodeBounds:&computedBounds]; NSMutableDictionary* attributes = nil; attributes = [[NSMutableDictionary alloc] initWithCapacity:element.attributes.count]; for(NSXMLNode* attributeNode in element.attributes) { @@ -349,7 +348,9 @@ static NSArray* _IJSVGUseElementOverwritingAttributes = nil; typedef id (^IJSVGAttributeComputableParseBlock)(NSString*); void (^IJSVGAttributesParse)(NSDictionary*, IJSVGAttributeComputableParseBlock) = ^(NSDictionary* dictionary, IJSVGAttributeComputableParseBlock parseBlock) { - [dictionary enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull obj, BOOL * _Nonnull stop) { + [dictionary enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, + NSString * _Nonnull obj, + BOOL * _Nonnull stop) { IJSVGAttributeParse(key, ^(NSString* value) { [node setValue:parseBlock(value) forKey:obj]; @@ -772,6 +773,9 @@ static NSArray* _IJSVGUseElementOverwritingAttributes = nil; postProcessBlock(); } + // perform any post processing + [computedNode postProcess]; + return computedNode; } @@ -781,7 +785,8 @@ static NSArray* _IJSVGUseElementOverwritingAttributes = nil; [self computeDefsForElement:element parentNode:node]; for(NSXMLElement* childElement in element.children) { - [self parseElement:childElement parentNode:node]; + [self parseElement:childElement + parentNode:node]; } } @@ -1426,12 +1431,6 @@ static NSArray* _IJSVGUseElementOverwritingAttributes = nil; [self computeElement:element parentNode:node]; - if(node.clipPath != nil) { - IJSVGClipPath* clipPath = node.clipPath; - node.clipPath = nil; - [node addChildren:clipPath.children]; - } - return node; } diff --git a/Framework/IJSVG/IJSVG/Source/Rendering/IJSVGLayerTree.m b/Framework/IJSVG/IJSVG/Source/Rendering/IJSVGLayerTree.m index aba11f2..4571656 100644 --- a/Framework/IJSVG/IJSVG/Source/Rendering/IJSVGLayerTree.m +++ b/Framework/IJSVG/IJSVG/Source/Rendering/IJSVGLayerTree.m @@ -514,11 +514,35 @@ return maskLayer; } -- (CALayer*)clipLayerFromNode:(IJSVGClipPath*)node - referencingLayer:(CALayer*)layer - fromLayer:(CALayer*)fromLayer +- (NSArray*>*)clipLayersFromNode:(IJSVGClipPath*)node + referencingLayer:(CALayer*)layer + fromLayer:(CALayer*)fromLayer { - return nil; + NSMutableArray*>* layers = nil; + layers = [[NSMutableArray alloc] init]; + IJSVGClipPath* refClipPath = node; + IJSVGGroupLayer* groupLayer = nil; + while(refClipPath != nil) { + groupLayer = (IJSVGGroupLayer*)[self drawableLayerForNode:refClipPath]; + if(groupLayer != nil) { + groupLayer.referencingLayer = layer; + [layers addObject:groupLayer]; + } + refClipPath = refClipPath.clipPath; + } + CGRect clippingRect = [IJSVGLayer calculateFrameForSublayers:layers]; + CGAffineTransform userSpaceTransform = [IJSVGLayer userSpaceTransformForLayer:layer]; + CGAffineTransform clippingTransform = CGAffineTransformIdentity; + if(node.contentUnits == IJSVGUnitUserSpaceOnUse) { + clippingRect = CGRectApplyAffineTransform(clippingRect, userSpaceTransform); + clippingTransform = userSpaceTransform; + } + CGAffineTransform ident = CGAffineTransformMakeTranslation(-CGRectGetMinX(clippingRect), + -CGRectGetMinY(clippingRect)); + clippingTransform = CGAffineTransformConcat(clippingTransform, ident); + layer.clippingTransform = clippingTransform; + layer.clippingBoundingBox = clippingRect; + return layers; } #pragma mark Defaults @@ -535,15 +559,11 @@ // add the clip mask if any if(node.clipPath != nil) { - CALayer* clipLayer = nil; - clipLayer = [self clipLayerFromNode:node.clipPath - referencingLayer:layer - fromLayer:nil]; - - // we need to make sure the clipping rules persist - // down to the layer - layer.clipRule = clipLayer.fillRule; - layer.clipLayer = clipLayer; + NSArray*>* clipLayers = nil; + clipLayers = [self clipLayersFromNode:node.clipPath + referencingLayer:layer + fromLayer:nil]; + layer.clipLayers = clipLayers; } // setup the opacity @@ -625,8 +645,8 @@ // 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; + frame.size.width = ceilf(image.width.value); + frame.size.height = ceilf(image.height.value); layer.frame = frame; [layer setNeedsLayout]; return layer; diff --git a/IJSVGExample/IJSVGExample.xcodeproj/project.xcworkspace/xcuserdata/curtishard.xcuserdatad/UserInterfaceState.xcuserstate b/IJSVGExample/IJSVGExample.xcodeproj/project.xcworkspace/xcuserdata/curtishard.xcuserdatad/UserInterfaceState.xcuserstate index 93a7e31..ae943ea 100644 Binary files a/IJSVGExample/IJSVGExample.xcodeproj/project.xcworkspace/xcuserdata/curtishard.xcuserdatad/UserInterfaceState.xcuserstate and b/IJSVGExample/IJSVGExample.xcodeproj/project.xcworkspace/xcuserdata/curtishard.xcuserdatad/UserInterfaceState.xcuserstate differ