From 0288d95e7b5d6c9b4fb93f320f5787ca2dbd1599 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 4 Mar 2020 19:35:06 +0200 Subject: [PATCH] fix!: #1247 Animated view translation inside Svg tag Fixes performance regressions on ios Place elements inside ForeignObject to use v10 / standard behaviour ForeignObject should behave unchanged from v10 / v11 Possibly fixes #1258 as well BREAKING CHANGE: Behavior of native elements is reverted to pre v10 --- .../com/horcrux/svg/ForeignObjectView.java | 65 +++++++++++++++ .../main/java/com/horcrux/svg/GroupView.java | 3 - .../main/java/com/horcrux/svg/SvgView.java | 19 ----- ios/Elements/RNSVGForeignObject.m | 82 +++++++++++++++++++ ios/Elements/RNSVGGroup.m | 9 +- 5 files changed, 148 insertions(+), 30 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/ForeignObjectView.java b/android/src/main/java/com/horcrux/svg/ForeignObjectView.java index 59cbfb6ca..ae9068301 100644 --- a/android/src/main/java/com/horcrux/svg/ForeignObjectView.java +++ b/android/src/main/java/com/horcrux/svg/ForeignObjectView.java @@ -10,8 +10,10 @@ package com.horcrux.svg; import android.annotation.SuppressLint; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.RectF; import android.view.View; import androidx.annotation.NonNull; @@ -72,4 +74,67 @@ public void setHeight(Dynamic height) { mH = SVGLength.from(height); invalidate(); } + + void drawGroup(final Canvas canvas, final Paint paint, final float opacity) { + pushGlyphContext(); + final SvgView svg = getSvgView(); + final GroupView self = this; + final RectF groupRect = new RectF(); + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (child instanceof MaskView) { + continue; + } + if (child instanceof VirtualView) { + VirtualView node = ((VirtualView)child); + if ("none".equals(node.mDisplay)) { + continue; + } + if (node instanceof RenderableView) { + ((RenderableView)node).mergeProperties(self); + } + + int count = node.saveAndSetupCanvas(canvas, mCTM); + node.render(canvas, paint, opacity * mOpacity); + RectF r = node.getClientRect(); + if (r != null) { + groupRect.union(r); + } + + node.restoreCanvas(canvas, count); + + if (node instanceof RenderableView) { + ((RenderableView)node).resetProperties(); + } + + if (node.isResponsible()) { + svg.enableTouchEvents(); + } + } else if (child instanceof SvgView) { + SvgView svgView = (SvgView)child; + svgView.drawChildren(canvas); + if (svgView.isResponsible()) { + svg.enableTouchEvents(); + } + } else { + // Enable rendering other native ancestor views in e.g. masks + child.draw(canvas); + } + } + this.setClientRect(groupRect); + popGlyphContext(); + } + + // Enable rendering other native ancestor views in e.g. masks, but don't render them another time + Bitmap fakeBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); + Canvas fake = new Canvas(fakeBitmap); + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(fake); + } + + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + return super.drawChild(fake, child, drawingTime); + } } diff --git a/android/src/main/java/com/horcrux/svg/GroupView.java b/android/src/main/java/com/horcrux/svg/GroupView.java index badbccb08..e7b54f146 100644 --- a/android/src/main/java/com/horcrux/svg/GroupView.java +++ b/android/src/main/java/com/horcrux/svg/GroupView.java @@ -122,9 +122,6 @@ void drawGroup(final Canvas canvas, final Paint paint, final float opacity) { if (svgView.isResponsible()) { svg.enableTouchEvents(); } - } else { - // Enable rendering other native ancestor views in e.g. masks - child.draw(canvas); } } this.setClientRect(groupRect); diff --git a/android/src/main/java/com/horcrux/svg/SvgView.java b/android/src/main/java/com/horcrux/svg/SvgView.java index d045456e9..5c792bda7 100644 --- a/android/src/main/java/com/horcrux/svg/SvgView.java +++ b/android/src/main/java/com/horcrux/svg/SvgView.java @@ -96,25 +96,6 @@ public void invalidate() { mBitmap = null; } - // Enable rendering other native ancestor views in e.g. masks, but don't render them another time - Bitmap fakeBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); - Canvas fake = new Canvas(fakeBitmap); - - @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(fake); - } - - protected boolean drawChild(Canvas canvas, View child, long drawingTime) { - return super.drawChild(fake, child, drawingTime); - } - - @Override - public void onDescendantInvalidated(@NonNull View child, @NonNull View target) { - super.onDescendantInvalidated(child, target); - invalidate(); - } - @Override protected void onDraw(Canvas canvas) { if (getParent() instanceof VirtualView) { diff --git a/ios/Elements/RNSVGForeignObject.m b/ios/Elements/RNSVGForeignObject.m index e2a7b80d1..fd88f9122 100644 --- a/ios/Elements/RNSVGForeignObject.m +++ b/ios/Elements/RNSVGForeignObject.m @@ -6,6 +6,8 @@ * LICENSE file in the root directory of this source tree. */ #import "RNSVGForeignObject.h" +#import "RNSVGClipPath.h" +#import "RNSVGMask.h" #import "RNSVGNode.h" @implementation RNSVGForeignObject @@ -34,6 +36,86 @@ - (void)renderLayerTo:(CGContextRef)context rect:(CGRect)rect [super renderLayerTo:context rect:rect]; } +- (void)renderGroupTo:(CGContextRef)context rect:(CGRect)rect +{ + [self pushGlyphContext]; + + __block CGRect bounds = CGRectNull; + + [self traverseSubviews:^(UIView *node) { + if ([node isKindOfClass:[RNSVGMask class]] || [node isKindOfClass:[RNSVGClipPath class]]) { + // no-op + } else if ([node isKindOfClass:[RNSVGNode class]]) { + RNSVGNode* svgNode = (RNSVGNode*)node; + if (svgNode.display && [@"none" isEqualToString:svgNode.display]) { + return YES; + } + if (svgNode.responsible && !self.svgView.responsible) { + self.svgView.responsible = YES; + } + + if ([node isKindOfClass:[RNSVGRenderable class]]) { + [(RNSVGRenderable*)node mergeProperties:self]; + } + + [svgNode renderTo:context rect:rect]; + + CGRect nodeRect = svgNode.clientRect; + if (!CGRectIsEmpty(nodeRect)) { + bounds = CGRectUnion(bounds, nodeRect); + } + + if ([node isKindOfClass:[RNSVGRenderable class]]) { + [(RNSVGRenderable*)node resetProperties]; + } + } else if ([node isKindOfClass:[RNSVGSvgView class]]) { + RNSVGSvgView* svgView = (RNSVGSvgView*)node; + CGFloat width = [self relativeOnWidth:svgView.bbWidth]; + CGFloat height = [self relativeOnHeight:svgView.bbHeight]; + CGRect rect = CGRectMake(0, 0, width, height); + CGContextClipToRect(context, rect); + [svgView drawToContext:context withRect:rect]; + } else { + node.hidden = false; + [node.layer renderInContext:context]; + node.hidden = true; + } + + return YES; + }]; + CGPathRef path = [self getPath:context]; + [self setHitArea:path]; + if (!CGRectEqualToRect(bounds, CGRectNull)) { + self.clientRect = bounds; + self.fillBounds = CGPathGetBoundingBox(path); + self.strokeBounds = CGPathGetBoundingBox(self.strokePath); + self.pathBounds = CGRectUnion(self.fillBounds, self.strokeBounds); + + CGAffineTransform current = CGContextGetCTM(context); + CGAffineTransform svgToClientTransform = CGAffineTransformConcat(current, self.svgView.invInitialCTM); + + self.ctm = svgToClientTransform; + self.screenCTM = current; + + CGAffineTransform transform = CGAffineTransformConcat(self.matrix, self.transforms); + CGPoint mid = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds)); + CGPoint center = CGPointApplyAffineTransform(mid, transform); + + self.bounds = bounds; + if (!isnan(center.x) && !isnan(center.y)) { + self.center = center; + } + self.frame = bounds; + } + + [self popGlyphContext]; +} + +- (void)drawRect:(CGRect)rect +{ + [self invalidate]; +} + - (void)setX:(RNSVGLength *)x { if ([x isEqualTo:_x]) { diff --git a/ios/Elements/RNSVGGroup.m b/ios/Elements/RNSVGGroup.m index e7b8bce12..a8787b8d1 100644 --- a/ios/Elements/RNSVGGroup.m +++ b/ios/Elements/RNSVGGroup.m @@ -72,9 +72,7 @@ - (void)renderGroupTo:(CGContextRef)context rect:(CGRect)rect CGContextClipToRect(context, rect); [svgView drawToContext:context withRect:rect]; } else { - node.hidden = false; - [node.layer renderInContext:context]; - node.hidden = true; + [node drawRect:rect]; } return YES; @@ -107,11 +105,6 @@ - (void)renderGroupTo:(CGContextRef)context rect:(CGRect)rect [self popGlyphContext]; } -- (void)drawRect:(CGRect)rect -{ - [self invalidate]; -} - - (void)setupGlyphContext:(CGContextRef)context { CGRect clipBounds = CGContextGetClipBoundingBox(context);