diff --git a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java index 351c1eb67..f62f6071f 100644 --- a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java @@ -10,9 +10,11 @@ package com.horcrux.svg; import android.graphics.Canvas; +import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; +import android.os.Build; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.uimanager.ReactShadowNode; @@ -119,7 +121,36 @@ protected Path getPath(final Canvas canvas, final Paint paint) { traverseChildren(new NodeRunnable() { public void run(ReactShadowNode node) { if (node instanceof VirtualNode) { - path.addPath(((VirtualNode)node).getPath(canvas, paint)); + VirtualNode n = (VirtualNode)node; + Matrix transform = n.mMatrix; + path.addPath(n.getPath(canvas, paint), transform); + } + } + }); + + return path; + } + + protected Path getPath(final Canvas canvas, final Paint paint, final Path.Op op) { + final Path path = new Path(); + + traverseChildren(new NodeRunnable() { + public void run(ReactShadowNode node) { + if (node instanceof VirtualNode) { + VirtualNode n = (VirtualNode)node; + Matrix transform = n.mMatrix; + Path p2; + if (n instanceof GroupShadowNode) { + p2 = ((GroupShadowNode)n).getPath(canvas, paint, op); + } else { + p2 = n.getPath(canvas, paint); + } + p2.transform(transform); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + path.op(p2, op); + } else { + path.addPath(p2); + } } } }); diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index d41ded01d..400395dfb 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -140,6 +140,11 @@ protected Path getPath(Canvas canvas, Paint paint) { return groupPath; } + @Override + protected Path getPath(Canvas canvas, Paint paint, Path.Op op) { + return getPath(canvas, paint); + } + AlignmentBaseline getAlignmentBaseline() { if (mAlignmentBaseline == null) { ReactShadowNode parent = this.getParent(); diff --git a/android/src/main/java/com/horcrux/svg/VirtualNode.java b/android/src/main/java/com/horcrux/svg/VirtualNode.java index 761126f52..e1dadc6c5 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/VirtualNode.java @@ -27,6 +27,8 @@ import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.events.EventDispatcher; +import java.util.List; + import javax.annotation.Nullable; import static com.horcrux.svg.FontData.DEFAULT_FONT_SIZE; @@ -239,10 +241,15 @@ public void setResponsible(boolean responsible) { @Nullable Path getClipPath(Canvas canvas, Paint paint) { if (mClipPath != null) { - VirtualNode node = getSvgShadowNode().getDefinedClipPath(mClipPath); + ClipPathShadowNode mClipNode = (ClipPathShadowNode) getSvgShadowNode().getDefinedClipPath(mClipPath); - if (node != null) { - Path clipPath = node.getPath(canvas, paint); + if (mClipNode != null) { + Path clipPath; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + clipPath = mClipNode.getPath(canvas, paint, Path.Op.UNION); + } else { + clipPath = mClipNode.getPath(canvas, paint); + } switch (mClipRule) { case CLIP_RULE_EVENODD: clipPath.setFillType(Path.FillType.EVEN_ODD); @@ -265,7 +272,7 @@ void clip(Canvas canvas, Paint paint) { Path clip = getClipPath(canvas, paint); if (clip != null) { - canvas.clipPath(clip, Region.Op.REPLACE); + canvas.clipPath(clip); } } diff --git a/ios/Elements/RNSVGClipPath.h b/ios/Elements/RNSVGClipPath.h index bed8b4a2a..27697e2a0 100644 --- a/ios/Elements/RNSVGClipPath.h +++ b/ios/Elements/RNSVGClipPath.h @@ -14,4 +14,6 @@ @interface RNSVGClipPath : RNSVGGroup +- (BOOL)isSimpleClipPath; + @end diff --git a/ios/Elements/RNSVGClipPath.m b/ios/Elements/RNSVGClipPath.m index 3ea72d6fe..3228920cd 100644 --- a/ios/Elements/RNSVGClipPath.m +++ b/ios/Elements/RNSVGClipPath.m @@ -10,15 +10,22 @@ @implementation RNSVGClipPath -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event -{ - return nil; -} - - (void)parseReference { [self.svgView defineClipPath:self clipPathName:self.name]; } +- (BOOL)isSimpleClipPath +{ + NSArray *children = self.subviews; + if (children.count == 1) { + UIView* child = children[0]; + if ([child class] != [RNSVGGroup class]) { + return true; + } + } + return false; +} + @end diff --git a/ios/Elements/RNSVGGroup.m b/ios/Elements/RNSVGGroup.m index 2366efc3d..1871994c2 100644 --- a/ios/Elements/RNSVGGroup.m +++ b/ios/Elements/RNSVGGroup.m @@ -7,6 +7,7 @@ */ #import "RNSVGGroup.h" +#import "RNSVGClipPath.h" @implementation RNSVGGroup { @@ -33,7 +34,7 @@ - (void)renderLayerTo:(CGContextRef)context rect:(CGRect)rect - (void)renderGroupTo:(CGContextRef)context rect:(CGRect)rect { [self pushGlyphContext]; - + __block CGRect groupRect = CGRectNull; [self traverseSubviews:^(UIView *node) { @@ -48,7 +49,7 @@ - (void)renderGroupTo:(CGContextRef)context rect:(CGRect)rect } [svgNode renderTo:context rect:rect]; - + CGRect nodeRect = svgNode.clientRect; if (!CGRectIsEmpty(nodeRect)) { groupRect = CGRectUnion(groupRect, nodeRect); @@ -124,12 +125,22 @@ - (CGPathRef)getPath:(CGContextRef)context - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { CGPoint transformed = CGPointApplyAffineTransform(point, self.invmatrix); - - CGPathRef clip = [self getClipPath]; - if (clip && !CGPathContainsPoint(clip, nil, transformed, self.clipRule == kRNSVGCGFCRuleEvenodd)) { - return nil; + + if (self.clipPath) { + RNSVGClipPath *clipNode = (RNSVGClipPath*)[self.svgView getDefinedClipPath:self.clipPath]; + if ([clipNode isSimpleClipPath]) { + CGPathRef clipPath = [self getClipPath]; + if (clipPath && !CGPathContainsPoint(clipPath, nil, transformed, self.clipRule == kRNSVGCGFCRuleEvenodd)) { + return nil; + } + } else { + RNSVGRenderable *clipGroup = (RNSVGRenderable*)clipNode; + if (![clipGroup hitTest:transformed withEvent:event]) { + return nil; + } + } } - + if (!event) { NSPredicate *const anyActive = [NSPredicate predicateWithFormat:@"active == TRUE"]; NSArray *const filtered = [self.subviews filteredArrayUsingPredicate:anyActive]; @@ -156,12 +167,12 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event return (node.responsible || (node != hitChild)) ? hitChild : self; } } - + UIView *hitSelf = [super hitTest:transformed withEvent:event]; if (hitSelf) { return hitSelf; } - + return nil; } diff --git a/ios/RNSVGNode.m b/ios/RNSVGNode.m index adccc2527..f157a3990 100644 --- a/ios/RNSVGNode.m +++ b/ios/RNSVGNode.m @@ -22,6 +22,7 @@ @implementation RNSVGNode RNSVGGlyphContext *glyphContext; BOOL _transparent; CGPathRef _cachedClipPath; + CGImageRef _clipMask; } CGFloat const RNSVG_M_SQRT1_2l = 0.707106781186547524400844362104849039; @@ -169,8 +170,10 @@ - (void)setClipPath:(NSString *)clipPath return; } CGPathRelease(_cachedClipPath); + CGImageRelease(_clipMask); _cachedClipPath = nil; _clipPath = clipPath; + _clipMask = nil; [self invalidate]; } @@ -201,7 +204,26 @@ - (CGPathRef)getClipPath - (CGPathRef)getClipPath:(CGContextRef)context { if (self.clipPath) { - _cachedClipPath = CGPathRetain([[self.svgView getDefinedClipPath:self.clipPath] getPath:context]); + RNSVGClipPath *_clipNode = (RNSVGClipPath*)[self.svgView getDefinedClipPath:self.clipPath]; + _cachedClipPath = CGPathRetain([_clipNode getPath:context]); + if (_clipMask) { + CGImageRelease(_clipMask); + } + if ([_clipNode isSimpleClipPath]) { + _clipMask = nil; + } else { + CGRect bounds = CGContextGetClipBoundingBox(context); + CGSize size = bounds.size; + + UIGraphicsBeginImageContextWithOptions(size, NO, 0.0); + CGContextRef newContext = UIGraphicsGetCurrentContext(); + CGContextTranslateCTM(newContext, 0.0, size.height); + CGContextScaleCTM(newContext, 1.0, -1.0); + + [_clipNode renderLayerTo:newContext rect:bounds]; + _clipMask = CGBitmapContextCreateImage(newContext); + UIGraphicsEndImageContext(); + } } return [self getClipPath]; @@ -212,11 +234,16 @@ - (void)clip:(CGContextRef)context CGPathRef clipPath = [self getClipPath:context]; if (clipPath) { - CGContextAddPath(context, clipPath); - if (self.clipRule == kRNSVGCGFCRuleEvenodd) { - CGContextEOClip(context); + if (!_clipMask) { + CGContextAddPath(context, clipPath); + if (self.clipRule == kRNSVGCGFCRuleEvenodd) { + CGContextEOClip(context); + } else { + CGContextClip(context); + } } else { - CGContextClip(context); + CGRect bounds = CGContextGetClipBoundingBox(context); + CGContextClipToMask(context, bounds, _clipMask); } } } @@ -343,6 +370,8 @@ - (void)traverseSubviews:(BOOL (^)(__kindof UIView *node))block - (void)dealloc { CGPathRelease(_cachedClipPath); + CGImageRelease(_clipMask); + _clipMask = nil; } @end diff --git a/ios/RNSVGRenderable.m b/ios/RNSVGRenderable.m index 98ee006e3..c584ae1ab 100644 --- a/ios/RNSVGRenderable.m +++ b/ios/RNSVGRenderable.m @@ -7,6 +7,7 @@ */ #import "RNSVGRenderable.h" +#import "RNSVGClipPath.h" @implementation RNSVGRenderable { @@ -187,7 +188,7 @@ - (void)renderLayerTo:(CGContextRef)context rect:(CGRect)rect self.path = CGPathRetain(CFAutorelease(CGPathCreateCopy([self getPath:context]))); [self setHitArea:self.path]; } - + const CGRect pathBounding = CGPathGetBoundingBox(self.path); const CGAffineTransform svgToClientTransform = CGAffineTransformConcat(CGContextGetCTM(context), self.svgView.invInitialCTM); self.clientRect = CGRectApplyAffineTransform(pathBounding, svgToClientTransform); @@ -270,7 +271,7 @@ - (void)setHitArea:(CGPathRef)path _hitArea = nil; // Add path to hitArea CGMutablePathRef hitArea = CGPathCreateMutableCopy(path); - + if (self.stroke && self.strokeWidth) { // Add stroke to hitArea CGFloat width = [self relativeOnOther:self.strokeWidth]; @@ -278,7 +279,7 @@ - (void)setHitArea:(CGPathRef)path CGPathAddPath(hitArea, nil, strokePath); CGPathRelease(strokePath); } - + _hitArea = CGPathRetain(CFAutorelease(CGPathCreateCopy(hitArea))); CGPathRelease(hitArea); @@ -304,9 +305,19 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event return nil; } - CGPathRef clipPath = [self getClipPath]; - if (clipPath && !CGPathContainsPoint(clipPath, nil, transformed, self.clipRule == kRNSVGCGFCRuleEvenodd)) { - return nil; + if (self.clipPath) { + RNSVGClipPath *clipNode = (RNSVGClipPath*)[self.svgView getDefinedClipPath:self.clipPath]; + if ([clipNode isSimpleClipPath]) { + CGPathRef clipPath = [self getClipPath]; + if (clipPath && !CGPathContainsPoint(clipPath, nil, transformed, self.clipRule == kRNSVGCGFCRuleEvenodd)) { + return nil; + } + } else { + RNSVGRenderable *clipGroup = (RNSVGRenderable*)clipNode; + if (![clipGroup hitTest:transformed withEvent:event]) { + return nil; + } + } } return self;