From ee1d1f78ccaaddcd4c2fae851944aec5f61aad6c Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Tue, 6 Feb 2018 11:18:18 +0200 Subject: [PATCH 01/10] First attempt at nested svg support. --- .../java/com/horcrux/svg/DefsShadowNode.java | 16 +++- .../java/com/horcrux/svg/GroupShadowNode.java | 47 ++++++---- .../com/horcrux/svg/SvgViewShadowNode.java | 58 +++++++++---- .../java/com/horcrux/svg/TextShadowNode.java | 8 +- .../java/com/horcrux/svg/VirtualNode.java | 6 +- elements/Svg.js | 13 ++- ios/Elements/RNSVGDefs.m | 4 +- ios/Elements/RNSVGGroup.m | 40 ++++++--- ios/Elements/RNSVGSvgView.h | 8 ++ ios/Elements/RNSVGSvgView.m | 86 ++++++++++++------- ios/RNSVGNode.h | 2 +- ios/RNSVGNode.m | 10 ++- ios/ViewManagers/RNSVGSvgViewManager.m | 2 + 13 files changed, 208 insertions(+), 92 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/DefsShadowNode.java b/android/src/main/java/com/horcrux/svg/DefsShadowNode.java index 8e4fc42e..d636d3b0 100644 --- a/android/src/main/java/com/horcrux/svg/DefsShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/DefsShadowNode.java @@ -12,6 +12,8 @@ package com.horcrux.svg; import android.graphics.Canvas; import android.graphics.Paint; +import com.facebook.react.uimanager.LayoutShadowNode; + /** * Shadow node for virtual Defs view */ @@ -20,9 +22,13 @@ class DefsShadowNode extends DefinitionShadowNode { @Override public void draw(Canvas canvas, Paint paint, float opacity) { NodeRunnable markUpdateSeenRecursive = new NodeRunnable() { - public void run(VirtualNode node) { + public void run(LayoutShadowNode node) { node.markUpdateSeen(); - node.traverseChildren(this); + if (node instanceof VirtualNode) { + ((VirtualNode) node).traverseChildren(this); + } else if (node instanceof SvgViewShadowNode) { + ((SvgViewShadowNode) node).traverseChildren(this); + } } }; traverseChildren(markUpdateSeenRecursive); @@ -30,8 +36,10 @@ class DefsShadowNode extends DefinitionShadowNode { void saveDefinition() { traverseChildren(new NodeRunnable() { - public void run(VirtualNode node) { - node.saveDefinition(); + public void run(LayoutShadowNode node) { + if (node instanceof VirtualNode) { + ((VirtualNode)node).saveDefinition(); + } } }); } diff --git a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java index 3f0bccea..f561151f 100644 --- a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java @@ -17,6 +17,7 @@ import android.graphics.Point; import android.graphics.RectF; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.uimanager.LayoutShadowNode; import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.annotations.ReactProp; @@ -72,23 +73,29 @@ class GroupShadowNode extends RenderableShadowNode { final SvgViewShadowNode svg = getSvgShadowNode(); final GroupShadowNode self = this; traverseChildren(new NodeRunnable() { - public void run(VirtualNode node) { - if (node instanceof RenderableShadowNode) { - ((RenderableShadowNode)node).mergeProperties(self); - } + public void run(LayoutShadowNode lNode) { + if (lNode instanceof VirtualNode) { + VirtualNode node = ((VirtualNode)lNode); + if (node instanceof RenderableShadowNode) { + ((RenderableShadowNode)node).mergeProperties(self); + } - int count = node.saveAndSetupCanvas(canvas); - node.draw(canvas, paint, opacity * mOpacity); - node.restoreCanvas(canvas, count); + int count = node.saveAndSetupCanvas(canvas); + node.draw(canvas, paint, opacity * mOpacity); + node.restoreCanvas(canvas, count); - if (node instanceof RenderableShadowNode) { - ((RenderableShadowNode)node).resetProperties(); - } + if (node instanceof RenderableShadowNode) { + ((RenderableShadowNode)node).resetProperties(); + } - node.markUpdateSeen(); + node.markUpdateSeen(); - if (node.isResponsible()) { - svg.enableTouchEvents(); + if (node.isResponsible()) { + svg.enableTouchEvents(); + } + } else if (lNode instanceof SvgViewShadowNode) { + SvgViewShadowNode svgView = (SvgViewShadowNode)lNode; + svgView.drawChildren(canvas); } } }); @@ -104,8 +111,10 @@ class GroupShadowNode extends RenderableShadowNode { final Path path = new Path(); traverseChildren(new NodeRunnable() { - public void run(VirtualNode node) { - path.addPath(node.getPath(canvas, paint)); + public void run(LayoutShadowNode node) { + if (node instanceof VirtualNode) { + path.addPath(((VirtualNode)node).getPath(canvas, paint)); + } } }); @@ -154,8 +163,10 @@ class GroupShadowNode extends RenderableShadowNode { } traverseChildren(new NodeRunnable() { - public void run(VirtualNode node) { - node.saveDefinition(); + public void run(LayoutShadowNode node) { + if (node instanceof VirtualNode) { + ((VirtualNode)node).saveDefinition(); + } } }); } @@ -163,7 +174,7 @@ class GroupShadowNode extends RenderableShadowNode { @Override public void resetProperties() { traverseChildren(new NodeRunnable() { - public void run(VirtualNode node) { + public void run(LayoutShadowNode node) { if (node instanceof RenderableShadowNode) { ((RenderableShadowNode)node).resetProperties(); } diff --git a/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java b/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java index 808ef4ab..6de11b17 100644 --- a/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java @@ -45,6 +45,8 @@ public class SvgViewShadowNode extends LayoutShadowNode { private float mMinY; private float mVbWidth; private float mVbHeight; + private String mbbWidth; + private String mbbHeight; private String mAlign; private int mMeetOrSlice; private Matrix mViewBoxMatrix; @@ -77,6 +79,18 @@ public class SvgViewShadowNode extends LayoutShadowNode { markUpdated(); } + @ReactProp(name = "bbWidth") + public void setVbWidth(String bbWidth) { + mbbWidth = bbWidth; + markUpdated(); + } + + @ReactProp(name = "bbHeight") + public void setVbHeight(String bbHeight) { + mbbHeight = bbHeight; + markUpdated(); + } + @ReactProp(name = "align") public void setAlign(String align) { mAlign = align; @@ -117,8 +131,7 @@ public class SvgViewShadowNode extends LayoutShadowNode { (int) getLayoutHeight(), Bitmap.Config.ARGB_8888); - mCanvas = new Canvas(bitmap); - drawChildren(mCanvas); + drawChildren(new Canvas(bitmap)); return bitmap; } @@ -126,11 +139,21 @@ public class SvgViewShadowNode extends LayoutShadowNode { return mCanvas.getClipBounds(); } - private void drawChildren(final Canvas canvas) { - + void drawChildren(final Canvas canvas) { + mCanvas = canvas; if (mAlign != null) { RectF vbRect = getViewBox(); - RectF eRect = new RectF(0, 0, getLayoutWidth(), getLayoutHeight()); + float width = getLayoutWidth(); + float height = getLayoutHeight(); + boolean nested = Float.isNaN(width) || Float.isNaN(height); + if (nested) { + width = Float.parseFloat(mbbWidth) * mScale; + height = Float.parseFloat(mbbHeight) * mScale; + } + RectF eRect = new RectF(0,0, width, height); + if (nested) { + canvas.clipRect(eRect); + } mViewBoxMatrix = ViewBox.getTransform(vbRect, eRect, mAlign, mMeetOrSlice); canvas.concat(mViewBoxMatrix); } @@ -143,20 +166,25 @@ public class SvgViewShadowNode extends LayoutShadowNode { traverseChildren(new VirtualNode.NodeRunnable() { - public void run(VirtualNode node) { - node.saveDefinition(); + public void run(LayoutShadowNode node) { + if (node instanceof VirtualNode) { + ((VirtualNode)node).saveDefinition(); + } } }); traverseChildren(new VirtualNode.NodeRunnable() { - public void run(VirtualNode node) { - int count = node.saveAndSetupCanvas(canvas); - node.draw(canvas, paint, 1f); - node.restoreCanvas(canvas, count); - node.markUpdateSeen(); + public void run(LayoutShadowNode lNode) { + if (lNode instanceof VirtualNode) { + VirtualNode node = (VirtualNode)lNode; + int count = node.saveAndSetupCanvas(canvas); + node.draw(canvas, paint, 1f); + node.restoreCanvas(canvas, count); + node.markUpdateSeen(); - if (node.isResponsible() && !mResponsible) { - mResponsible = true; + if (node.isResponsible() && !mResponsible) { + mResponsible = true; + } } } }); @@ -238,7 +266,7 @@ public class SvgViewShadowNode extends LayoutShadowNode { continue; } - runner.run((VirtualNode) child); + runner.run((LayoutShadowNode) child); } } } diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index aa539667..d7ae8491 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -15,6 +15,7 @@ import android.graphics.Path; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.uimanager.LayoutShadowNode; import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.annotations.ReactProp; @@ -177,9 +178,10 @@ class TextShadowNode extends GroupShadowNode { void releaseCachedPath() { traverseChildren(new NodeRunnable() { - public void run(VirtualNode node) { - TextShadowNode text = (TextShadowNode)node; - text.releaseCachedPath(); + public void run(LayoutShadowNode node) { + if (node instanceof TextShadowNode) { + ((TextShadowNode)node).releaseCachedPath(); + } } }); } diff --git a/android/src/main/java/com/horcrux/svg/VirtualNode.java b/android/src/main/java/com/horcrux/svg/VirtualNode.java index 4af0085d..8cc3489b 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/VirtualNode.java @@ -308,17 +308,17 @@ abstract class VirtualNode extends LayoutShadowNode { } interface NodeRunnable { - void run(VirtualNode node); + void run(LayoutShadowNode node); } void traverseChildren(NodeRunnable runner) { for (int i = 0; i < getChildCount(); i++) { ReactShadowNode child = getChildAt(i); - if (!(child instanceof VirtualNode)) { + if (!(child instanceof VirtualNode) && !(child instanceof SvgViewShadowNode)) { continue; } - runner.run((VirtualNode) child); + runner.run((LayoutShadowNode) child); } } } diff --git a/elements/Svg.js b/elements/Svg.js index 377dadd8..4dc09d64 100644 --- a/elements/Svg.js +++ b/elements/Svg.js @@ -74,14 +74,19 @@ class Svg extends Component{ if (width && height) { dimensions = { - width: +width, - height: +height, + width: width[width.length - 1] === '%' ? width : +width, + height: height[height.length - 1] === '%' ? height : +height, flex: 0 }; } + const w = `${width}`; + const h = `${height}`; + return {this.root = ele;}} style={[ @@ -98,7 +103,9 @@ class Svg extends Component{ const NativeSvgView = requireNativeComponent('RNSVGSvgView', null, { nativeOnly: { - ...ViewBoxAttributes + ...ViewBoxAttributes, + width: true, + height: true, } }); diff --git a/ios/Elements/RNSVGDefs.m b/ios/Elements/RNSVGDefs.m index 765af660..af9fd1c6 100644 --- a/ios/Elements/RNSVGDefs.m +++ b/ios/Elements/RNSVGDefs.m @@ -19,7 +19,9 @@ - (void)parseReference { [self traverseSubviews:^(RNSVGNode *node) { - [node parseReference]; + if ([node isKindOfClass:[RNSVGNode class]]) { + [node parseReference]; + } return YES; }]; } diff --git a/ios/Elements/RNSVGGroup.m b/ios/Elements/RNSVGGroup.m index c2ec51ae..2acab72c 100644 --- a/ios/Elements/RNSVGGroup.m +++ b/ios/Elements/RNSVGGroup.m @@ -34,19 +34,29 @@ { [self pushGlyphContext]; RNSVGSvgView* svg = [self getSvgView]; - [self traverseSubviews:^(RNSVGNode *node) { - if (node.responsible && !svg.responsible) { - svg.responsible = YES; - } + [self traverseSubviews:^(UIView *node) { + if ([node isKindOfClass:[RNSVGNode class]]) { + RNSVGNode* svgNode = (RNSVGNode*)node; + if (svgNode.responsible && !svg.responsible) { + svg.responsible = YES; + } - if ([node isKindOfClass:[RNSVGRenderable class]]) { - [(RNSVGRenderable*)node mergeProperties:self]; - } + if ([node isKindOfClass:[RNSVGRenderable class]]) { + [(RNSVGRenderable*)node mergeProperties:self]; + } - [node renderTo:context]; + [svgNode renderTo:context]; - if ([node isKindOfClass:[RNSVGRenderable class]]) { - [(RNSVGRenderable*)node resetProperties]; + if ([node isKindOfClass:[RNSVGRenderable class]]) { + [(RNSVGRenderable*)node resetProperties]; + } + } else if ([node isKindOfClass:[RNSVGSvgView class]]) { + RNSVGSvgView* svgView = (RNSVGSvgView*)node; + CGRect rect = CGRectMake(0, 0, [svgView.bbWidth floatValue], [svgView.bbHeight floatValue]); + CGContextClipToRect(context, rect); + [svgView drawToContext:context withRect:(CGRect)rect]; + } else { + RCTLogWarn(@"Not a RNSVGNode: %@", node.class); } return YES; @@ -89,8 +99,10 @@ { CGMutablePathRef __block path = CGPathCreateMutable(); [self traverseSubviews:^(RNSVGNode *node) { - CGAffineTransform transform = node.matrix; - CGPathAddPath(path, &transform, [node getPath:context]); + if ([node isKindOfClass:[RNSVGNode class]]) { + CGAffineTransform transform = node.matrix; + CGPathAddPath(path, &transform, [node getPath:context]); + } return YES; }]; @@ -147,7 +159,9 @@ } [self traverseSubviews:^(__kindof RNSVGNode *node) { - [node parseReference]; + if ([node isKindOfClass:[RNSVGNode class]]) { + [node parseReference]; + } return YES; }]; } diff --git a/ios/Elements/RNSVGSvgView.h b/ios/Elements/RNSVGSvgView.h index e21087e0..88eabdca 100644 --- a/ios/Elements/RNSVGSvgView.h +++ b/ios/Elements/RNSVGSvgView.h @@ -15,6 +15,8 @@ @interface RNSVGSvgView : UIView +@property (nonatomic, strong) NSString *bbWidth; +@property (nonatomic, strong) NSString *bbHeight; @property (nonatomic, assign) CGFloat minX; @property (nonatomic, assign) CGFloat minY; @property (nonatomic, assign) CGFloat vbWidth; @@ -22,6 +24,8 @@ @property (nonatomic, strong) NSString *align; @property (nonatomic, assign) RNSVGVBMOS meetOrSlice; @property (nonatomic, assign) BOOL responsible; +@property (nonatomic, assign) CGRect boundingBox; + /** * define content as clipPath template. @@ -42,4 +46,8 @@ - (CGRect)getContextBounds; +- (void)drawRect:(CGRect)rect; + +- (void)drawToContext:(CGContextRef)context withRect:(CGRect)rect; + @end diff --git a/ios/Elements/RNSVGSvgView.m b/ios/Elements/RNSVGSvgView.m index 46065fac..8c2e032d 100644 --- a/ios/Elements/RNSVGSvgView.m +++ b/ios/Elements/RNSVGSvgView.m @@ -16,7 +16,6 @@ NSMutableDictionary *_clipPaths; NSMutableDictionary *_templates; NSMutableDictionary *_painters; - CGRect _boundingBox; CGAffineTransform _viewBoxTransform; } @@ -48,7 +47,7 @@ if (minX == _minX) { return; } - + [self invalidate]; _minX = minX; } @@ -58,7 +57,7 @@ if (minY == _minY) { return; } - + [self invalidate]; _minY = minY; } @@ -68,7 +67,7 @@ if (vbWidth == _vbWidth) { return; } - + [self invalidate]; _vbWidth = vbWidth; } @@ -78,17 +77,37 @@ if (_vbHeight == vbHeight) { return; } - + [self invalidate]; _vbHeight = vbHeight; } +- (void)setBBWidth:(NSString *)bbWidth +{ + if ([bbWidth isEqualToString:_bbWidth]) { + return; + } + + [self invalidate]; + _bbWidth = bbWidth; +} + +- (void)setBBHeight:(NSString *)bbHeight +{ + if ([bbHeight isEqualToString:_bbHeight]) { + return; + } + + [self invalidate]; + _bbHeight = bbHeight; +} + - (void)setAlign:(NSString *)align { if ([align isEqualToString:_align]) { return; } - + [self invalidate]; _align = align; } @@ -98,11 +117,29 @@ if (meetOrSlice == _meetOrSlice) { return; } - + [self invalidate]; _meetOrSlice = meetOrSlice; } +- (void)drawToContext:(CGContextRef)context withRect:(CGRect)rect { + + if (self.align) { + _viewBoxTransform = [RNSVGViewBox getTransform:CGRectMake(self.minX, self.minY, self.vbWidth, self.vbHeight) + eRect:rect + align:self.align + meetOrSlice:self.meetOrSlice]; + CGContextConcatCTM(context, _viewBoxTransform); + } + + for (UIView *node in self.subviews) { + if ([node isKindOfClass:[RNSVGNode class]]) { + RNSVGNode *svg = (RNSVGNode *)node; + [svg renderTo:context]; + } + } +} + - (void)drawRect:(CGRect)rect { _clipPaths = nil; @@ -110,30 +147,21 @@ _painters = nil; _boundingBox = rect; CGContextRef context = UIGraphicsGetCurrentContext(); - - if (self.align) { - _viewBoxTransform = [RNSVGViewBox getTransform:CGRectMake(self.minX, self.minY, self.vbWidth, self.vbHeight) - eRect:rect - align:self.align - meetOrSlice:self.meetOrSlice]; - CGContextConcatCTM(context, _viewBoxTransform); - } - - for (RNSVGNode *node in self.subviews) { + + for (UIView *node in self.subviews) { if ([node isKindOfClass:[RNSVGNode class]]) { - if (node.responsible && !self.responsible) { + RNSVGNode *svg = (RNSVGNode *)node; + if (svg.responsible && !self.responsible) { self.responsible = YES; } - - [node parseReference]; - } - } - - for (RNSVGNode *node in self.subviews) { - if ([node isKindOfClass:[RNSVGNode class]]) { - [node renderTo:context]; + + [svg parseReference]; + } else { + RCTLogWarn(@"Not a RNSVGNode: %@", node.class); } } + + [self drawToContext:context withRect:rect]; } - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event @@ -143,15 +171,15 @@ if (![node isKindOfClass:[RNSVGNode class]]) { continue; } - + if (event) { node.active = NO; } else if (node.active) { return node; } - + UIView *hitChild = [node hitTest: point withEvent:event withTransform:_viewBoxTransform]; - + if (hitChild) { node.active = YES; return (node.responsible || (node != hitChild)) ? hitChild : self; diff --git a/ios/RNSVGNode.h b/ios/RNSVGNode.h index 07803745..b57e8f96 100644 --- a/ios/RNSVGNode.h +++ b/ios/RNSVGNode.h @@ -103,6 +103,6 @@ extern CGFloat const RNSVG_DEFAULT_FONT_SIZE; - (void)endTransparencyLayer:(CGContextRef)context; -- (void)traverseSubviews:(BOOL (^)(__kindof RNSVGNode *node))block; +- (void)traverseSubviews:(BOOL (^)(__kindof UIView *node))block; @end diff --git a/ios/RNSVGNode.m b/ios/RNSVGNode.m index e32dafcd..e7a19df6 100644 --- a/ios/RNSVGNode.m +++ b/ios/RNSVGNode.m @@ -310,13 +310,19 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; } } -- (void)traverseSubviews:(BOOL (^)(__kindof RNSVGNode *node))block +- (void)traverseSubviews:(BOOL (^)(__kindof UIView *node))block { - for (RNSVGNode *node in self.subviews) { + for (UIView *node in self.subviews) { if ([node isKindOfClass:[RNSVGNode class]]) { if (!block(node)) { break; } + } else if ([node isKindOfClass:[RNSVGSvgView class]]) { + if (!block(node)) { + break; + } + } else { + RCTLogWarn(@"Not a RNSVGNode: %@", node.class); } } } diff --git a/ios/ViewManagers/RNSVGSvgViewManager.m b/ios/ViewManagers/RNSVGSvgViewManager.m index 1cfd6345..84438cf7 100644 --- a/ios/ViewManagers/RNSVGSvgViewManager.m +++ b/ios/ViewManagers/RNSVGSvgViewManager.m @@ -20,6 +20,8 @@ RCT_EXPORT_MODULE() return [RNSVGSvgView new]; } +RCT_EXPORT_VIEW_PROPERTY(bbWidth, NSString) +RCT_EXPORT_VIEW_PROPERTY(bbHeight, NSString) RCT_EXPORT_VIEW_PROPERTY(minX, CGFloat) RCT_EXPORT_VIEW_PROPERTY(minY, CGFloat) RCT_EXPORT_VIEW_PROPERTY(vbWidth, CGFloat) From b5eeb1ee72913a902d9fd3af53a09ac297c7bbba Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 21 Feb 2018 00:48:03 +0200 Subject: [PATCH 02/10] Allow nesting React-Native View components inside svg --- ios/Elements/RNSVGGroup.h | 4 ++-- ios/Elements/RNSVGGroup.m | 14 +++++++------- ios/Elements/RNSVGSvgView.m | 6 +++--- ios/Elements/RNSVGSymbol.m | 6 +++--- ios/Elements/RNSVGUse.m | 4 ++-- ios/RNSVGNode.h | 4 ++-- ios/RNSVGNode.m | 16 ++++------------ ios/RNSVGRenderable.m | 6 +++--- ios/Text/RNSVGTSpan.m | 6 +++--- ios/Text/RNSVGText.m | 8 ++++---- ios/Text/RNSVGTextPath.m | 4 ++-- 11 files changed, 35 insertions(+), 43 deletions(-) diff --git a/ios/Elements/RNSVGGroup.h b/ios/Elements/RNSVGGroup.h index 163d8477..44020140 100644 --- a/ios/Elements/RNSVGGroup.h +++ b/ios/Elements/RNSVGGroup.h @@ -18,8 +18,8 @@ @property (nonatomic, strong) NSDictionary *font; -- (void)renderPathTo:(CGContextRef)context; -- (void)renderGroupTo:(CGContextRef)context; +- (void)renderPathTo:(CGContextRef)context rect:(CGRect)rect; +- (void)renderGroupTo:(CGContextRef)context rect:(CGRect)rect; - (RNSVGGlyphContext *)getGlyphContext; - (void)pushGlyphContext; diff --git a/ios/Elements/RNSVGGroup.m b/ios/Elements/RNSVGGroup.m index 2acab72c..60397c16 100644 --- a/ios/Elements/RNSVGGroup.m +++ b/ios/Elements/RNSVGGroup.m @@ -23,14 +23,14 @@ _font = font; } -- (void)renderLayerTo:(CGContextRef)context +- (void)renderLayerTo:(CGContextRef)context rect:(CGRect)rect { [self clip:context]; [self setupGlyphContext:context]; - [self renderGroupTo:context]; + [self renderGroupTo:context rect:rect]; } -- (void)renderGroupTo:(CGContextRef)context +- (void)renderGroupTo:(CGContextRef)context rect:(CGRect)rect { [self pushGlyphContext]; RNSVGSvgView* svg = [self getSvgView]; @@ -45,7 +45,7 @@ [(RNSVGRenderable*)node mergeProperties:self]; } - [svgNode renderTo:context]; + [svgNode renderTo:context rect:rect]; if ([node isKindOfClass:[RNSVGRenderable class]]) { [(RNSVGRenderable*)node resetProperties]; @@ -56,7 +56,7 @@ CGContextClipToRect(context, rect); [svgView drawToContext:context withRect:(CGRect)rect]; } else { - RCTLogWarn(@"Not a RNSVGNode: %@", node.class); + [node drawRect:rect]; } return YES; @@ -90,9 +90,9 @@ [[[self getTextRoot] getGlyphContext] popContext]; } -- (void)renderPathTo:(CGContextRef)context +- (void)renderPathTo:(CGContextRef)context rect:(CGRect)rect { - [super renderLayerTo:context]; + [super renderLayerTo:context rect:rect]; } - (CGPathRef)getPath:(CGContextRef)context diff --git a/ios/Elements/RNSVGSvgView.m b/ios/Elements/RNSVGSvgView.m index 8c2e032d..4bf9f3ce 100644 --- a/ios/Elements/RNSVGSvgView.m +++ b/ios/Elements/RNSVGSvgView.m @@ -135,7 +135,9 @@ for (UIView *node in self.subviews) { if ([node isKindOfClass:[RNSVGNode class]]) { RNSVGNode *svg = (RNSVGNode *)node; - [svg renderTo:context]; + [svg renderTo:context rect:rect]; + } else { + [node drawRect:rect]; } } } @@ -156,8 +158,6 @@ } [svg parseReference]; - } else { - RCTLogWarn(@"Not a RNSVGNode: %@", node.class); } } diff --git a/ios/Elements/RNSVGSymbol.m b/ios/Elements/RNSVGSymbol.m index d4fcfc34..8f3b41f0 100644 --- a/ios/Elements/RNSVGSymbol.m +++ b/ios/Elements/RNSVGSymbol.m @@ -72,15 +72,15 @@ _meetOrSlice = meetOrSlice; } -- (void)renderTo:(CGContextRef)context +- (void)renderTo:(CGContextRef)context rect:(CGRect)rect { // Do not render Symbol } - (void)renderSymbolTo:(CGContextRef)context width:(CGFloat)width height:(CGFloat)height { + CGRect eRect = CGRectMake(0, 0, width, height); if (self.align) { - CGRect eRect = CGRectMake(0, 0, width, height); CGAffineTransform viewBoxTransform = [RNSVGViewBox getTransform:CGRectMake(self.minX, self.minY, self.vbWidth, self.vbHeight) eRect:eRect @@ -89,7 +89,7 @@ CGContextConcatCTM(context, viewBoxTransform); } - [self renderGroupTo:context]; + [self renderGroupTo:context rect:eRect]; } @end diff --git a/ios/Elements/RNSVGUse.m b/ios/Elements/RNSVGUse.m index 075b4158..8656921e 100644 --- a/ios/Elements/RNSVGUse.m +++ b/ios/Elements/RNSVGUse.m @@ -22,7 +22,7 @@ } -- (void)renderLayerTo:(CGContextRef)context +- (void)renderLayerTo:(CGContextRef)context rect:(CGRect)rect { RNSVGNode* template = [[self getSvgView] getDefinedTemplate:self.href]; if (template) { @@ -37,7 +37,7 @@ RNSVGSymbol *symbol = (RNSVGSymbol*)template; [symbol renderSymbolTo:context width:[self relativeOnWidth:self.width] height:[self relativeOnWidth:self.height]]; } else { - [template renderTo:context]; + [template renderTo:context rect:rect]; } if ([template isKindOfClass:[RNSVGRenderable class]]) { diff --git a/ios/RNSVGNode.h b/ios/RNSVGNode.h index b57e8f96..96d1d924 100644 --- a/ios/RNSVGNode.h +++ b/ios/RNSVGNode.h @@ -39,14 +39,14 @@ extern CGFloat const RNSVG_DEFAULT_FONT_SIZE; - (RNSVGGroup *)getTextRoot; - (RNSVGGroup *)getParentTextRoot; -- (void)renderTo:(CGContextRef)context; +- (void)renderTo:(CGContextRef)context rect:(CGRect)rect; /** * renderTo will take opacity into account and draw renderLayerTo off-screen if there is opacity * specified, then composite that onto the context. renderLayerTo always draws at opacity=1. * @abstract */ -- (void)renderLayerTo:(CGContextRef)context; +- (void)renderLayerTo:(CGContextRef)context rect:(CGRect)rect; /** * get clipPath from cache diff --git a/ios/RNSVGNode.m b/ios/RNSVGNode.m index e7a19df6..bca9585d 100644 --- a/ios/RNSVGNode.m +++ b/ios/RNSVGNode.m @@ -159,7 +159,7 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; } } -- (void)renderTo:(CGContextRef)context +- (void)renderTo:(CGContextRef)context rect:(CGRect)rect { // abstract } @@ -199,7 +199,7 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; return nil; } -- (void)renderLayerTo:(CGContextRef)context +- (void)renderLayerTo:(CGContextRef)context rect:(CGRect)rect { // abstract } @@ -313,16 +313,8 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; - (void)traverseSubviews:(BOOL (^)(__kindof UIView *node))block { for (UIView *node in self.subviews) { - if ([node isKindOfClass:[RNSVGNode class]]) { - if (!block(node)) { - break; - } - } else if ([node isKindOfClass:[RNSVGSvgView class]]) { - if (!block(node)) { - break; - } - } else { - RCTLogWarn(@"Not a RNSVGNode: %@", node.class); + if (!block(node)) { + break; } } } diff --git a/ios/RNSVGRenderable.m b/ios/RNSVGRenderable.m index f7c0e72b..626e7aea 100644 --- a/ios/RNSVGRenderable.m +++ b/ios/RNSVGRenderable.m @@ -157,7 +157,7 @@ } } -- (void)renderTo:(CGContextRef)context +- (void)renderTo:(CGContextRef)context rect:(CGRect)rect { // This needs to be painted on a layer before being composited. CGContextSaveGState(context); @@ -165,14 +165,14 @@ CGContextSetAlpha(context, self.opacity); [self beginTransparencyLayer:context]; - [self renderLayerTo:context]; + [self renderLayerTo:context rect:rect]; [self endTransparencyLayer:context]; CGContextRestoreGState(context); } -- (void)renderLayerTo:(CGContextRef)context +- (void)renderLayerTo:(CGContextRef)context rect:(CGRect)rect { if (!self.fill && !self.stroke) { return; diff --git a/ios/Text/RNSVGTSpan.m b/ios/Text/RNSVGTSpan.m index bd4deaee..abbe956c 100644 --- a/ios/Text/RNSVGTSpan.m +++ b/ios/Text/RNSVGTSpan.m @@ -46,13 +46,13 @@ static double RNSVGTSpan_radToDeg = 180 / M_PI; _content = content; } -- (void)renderLayerTo:(CGContextRef)context +- (void)renderLayerTo:(CGContextRef)context rect:(CGRect)rect { if (self.content) { - [self renderPathTo:context]; + [self renderPathTo:context rect:rect]; } else { [self clip:context]; - [self renderGroupTo:context]; + [self renderGroupTo:context rect:rect]; } } diff --git a/ios/Text/RNSVGText.m b/ios/Text/RNSVGText.m index 28f665a9..26133afc 100644 --- a/ios/Text/RNSVGText.m +++ b/ios/Text/RNSVGText.m @@ -19,14 +19,14 @@ RNSVGGlyphContext *_glyphContext; } -- (void)renderLayerTo:(CGContextRef)context +- (void)renderLayerTo:(CGContextRef)context rect:(CGRect)rect { [self clip:context]; CGContextSaveGState(context); [self setupGlyphContext:context]; CGPathRef path = [self getGroupPath:context]; - [self renderGroupTo:context]; + [self renderGroupTo:context rect:rect]; [self releaseCachedPath]; CGContextRestoreGState(context); @@ -69,10 +69,10 @@ return (CGPathRef)CFAutorelease(CGPathCreateCopyByTransformingPath(groupPath, &CGAffineTransformIdentity)); } -- (void)renderGroupTo:(CGContextRef)context +- (void)renderGroupTo:(CGContextRef)context rect:(CGRect)rect { [self pushGlyphContext]; - [super renderGroupTo:context]; + [super renderGroupTo:context rect:rect]; [self popGlyphContext]; } diff --git a/ios/Text/RNSVGTextPath.m b/ios/Text/RNSVGTextPath.m index 1bf7bbbc..e74e54fa 100644 --- a/ios/Text/RNSVGTextPath.m +++ b/ios/Text/RNSVGTextPath.m @@ -209,9 +209,9 @@ void RNSVGPerformanceBezier_addLine(CGPoint *last, const CGPoint *next, NSMutabl *linesP = lines; } -- (void)renderLayerTo:(CGContextRef)context +- (void)renderLayerTo:(CGContextRef)context rect:(CGRect)rect { - [self renderGroupTo:context]; + [self renderGroupTo:context rect:rect]; } - (CGPathRef)getPath:(CGContextRef)context From f7b195a5a08387d2619c3e26854c473af49d846e Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 21 Feb 2018 16:50:04 +0200 Subject: [PATCH 03/10] Allow nesting React-Native View components inside svg (Android, poc wip) --- .../java/com/horcrux/svg/GroupShadowNode.java | 2 ++ .../main/java/com/horcrux/svg/SvgView.java | 32 +++++++++++++++++-- .../java/com/horcrux/svg/SvgViewManager.java | 3 +- .../com/horcrux/svg/SvgViewShadowNode.java | 9 +++--- .../java/com/horcrux/svg/VirtualNode.java | 10 +++--- 5 files changed, 44 insertions(+), 12 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java index f561151f..dc6b7809 100644 --- a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java @@ -96,6 +96,8 @@ class GroupShadowNode extends RenderableShadowNode { } else if (lNode instanceof SvgViewShadowNode) { SvgViewShadowNode svgView = (SvgViewShadowNode)lNode; svgView.drawChildren(canvas); + } else { + lNode.calculateLayout(); } } }); diff --git a/android/src/main/java/com/horcrux/svg/SvgView.java b/android/src/main/java/com/horcrux/svg/SvgView.java index 4ba7b99d..ae50edaf 100644 --- a/android/src/main/java/com/horcrux/svg/SvgView.java +++ b/android/src/main/java/com/horcrux/svg/SvgView.java @@ -16,22 +16,26 @@ import android.graphics.Point; import android.util.Log; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import com.facebook.react.ReactRootView; import com.facebook.react.bridge.ReactContext; +import com.facebook.react.uimanager.LayoutShadowNode; +import com.facebook.react.uimanager.ReactShadowNodeImpl; import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.events.TouchEvent; import com.facebook.react.uimanager.events.TouchEventCoalescingKeyHelper; import com.facebook.react.uimanager.events.TouchEventType; import com.facebook.react.uimanager.events.EventDispatcher; +import com.facebook.react.views.view.ReactViewGroup; import javax.annotation.Nullable; /** - * Custom {@link View} implementation that draws an RNSVGSvg React view and its \childrn. + * Custom {@link View} implementation that draws an RNSVGSvg React view and its children. */ @SuppressLint("ViewConstructor") -public class SvgView extends View { +public class SvgView extends ViewGroup { public enum Events { @SuppressWarnings("unused") EVENT_DATA_URL("onDataURL"); @@ -100,6 +104,30 @@ public class SvgView extends View { return super.dispatchTouchEvent(ev); } + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + for (int i = 0; i < this.getChildCount(); i++) { + View child = this.getChildAt(i); + if (child instanceof ReactViewGroup) { + ReactShadowNodeImpl node = getShadowNode(); + for (int j = 0; j < node.getChildCount(); j++) { + ReactShadowNodeImpl nodeChild = node.getChildAt(j); + if (nodeChild.getReactTag() != child.getId()) { + continue; + } + + float x = nodeChild.getLayoutX(); + float y = nodeChild.getLayoutY(); + float nr = x + nodeChild.getLayoutWidth(); + float nb = y + nodeChild.getLayoutHeight(); + + child.layout(Math.round(x), Math.round(y), Math.round(nr), Math.round(nb)); + break; + } + } + } + } + private int getAbsoluteLeft(View view) { int left = view.getLeft() - view.getScrollX(); diff --git a/android/src/main/java/com/horcrux/svg/SvgViewManager.java b/android/src/main/java/com/horcrux/svg/SvgViewManager.java index ec0236d6..2efc3639 100644 --- a/android/src/main/java/com/horcrux/svg/SvgViewManager.java +++ b/android/src/main/java/com/horcrux/svg/SvgViewManager.java @@ -12,6 +12,7 @@ package com.horcrux.svg; import android.graphics.Bitmap; import android.util.SparseArray; +import com.facebook.react.uimanager.ViewGroupManager; import com.facebook.yoga.YogaMeasureMode; import com.facebook.yoga.YogaMeasureFunction; import com.facebook.yoga.YogaNode; @@ -24,7 +25,7 @@ import javax.annotation.Nullable; * ViewManager for RNSVGSvgView React views. Renders as a {@link SvgView} and handles * invalidating the native view on shadow view updates happening in the underlying tree. */ -class SvgViewManager extends BaseViewManager { +class SvgViewManager extends ViewGroupManager { private static final String REACT_CLASS = "RNSVGSvgView"; diff --git a/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java b/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java index 6de11b17..80db8d87 100644 --- a/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java @@ -18,6 +18,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; import android.util.Base64; +import android.view.View; import com.facebook.react.uimanager.DisplayMetricsHolder; import com.facebook.react.uimanager.LayoutShadowNode; @@ -110,7 +111,7 @@ public class SvgViewShadowNode extends LayoutShadowNode { @Override public boolean isVirtualAnchor() { - return true; + return false; } @Override @@ -185,6 +186,8 @@ public class SvgViewShadowNode extends LayoutShadowNode { if (node.isResponsible() && !mResponsible) { mResponsible = true; } + } else { + lNode.calculateLayout(); } } }); @@ -262,10 +265,6 @@ public class SvgViewShadowNode extends LayoutShadowNode { void traverseChildren(VirtualNode.NodeRunnable runner) { for (int i = 0; i < getChildCount(); i++) { ReactShadowNode child = getChildAt(i); - if (!(child instanceof VirtualNode)) { - continue; - } - runner.run((LayoutShadowNode) child); } } diff --git a/android/src/main/java/com/horcrux/svg/VirtualNode.java b/android/src/main/java/com/horcrux/svg/VirtualNode.java index 8cc3489b..f29cc9e9 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/VirtualNode.java @@ -64,6 +64,7 @@ abstract class VirtualNode extends LayoutShadowNode { private GlyphContext glyphContext; VirtualNode() { + setIsLayoutOnly(true); mScale = DisplayMetricsHolder.getScreenDisplayMetrics().density; } @@ -72,6 +73,11 @@ abstract class VirtualNode extends LayoutShadowNode { return true; } + @Override + public boolean isVirtualAnchor() { + return true; + } + @Nullable GroupShadowNode getTextRoot() { VirtualNode node = this; @@ -314,10 +320,6 @@ abstract class VirtualNode extends LayoutShadowNode { void traverseChildren(NodeRunnable runner) { for (int i = 0; i < getChildCount(); i++) { ReactShadowNode child = getChildAt(i); - if (!(child instanceof VirtualNode) && !(child instanceof SvgViewShadowNode)) { - continue; - } - runner.run((LayoutShadowNode) child); } } From aed804776a025edb3370d2ae4795f16bf7420149 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Wed, 21 Feb 2018 20:58:12 +0200 Subject: [PATCH 04/10] Fix linting --- .../src/main/java/com/horcrux/svg/DefsShadowNode.java | 6 +++--- .../src/main/java/com/horcrux/svg/GroupShadowNode.java | 9 ++++----- android/src/main/java/com/horcrux/svg/SvgPackage.java | 1 + android/src/main/java/com/horcrux/svg/SvgView.java | 4 ++-- .../src/main/java/com/horcrux/svg/SvgViewManager.java | 9 ++++----- android/src/main/java/com/horcrux/svg/SvgViewModule.java | 1 + .../src/main/java/com/horcrux/svg/SvgViewShadowNode.java | 7 +++---- .../src/main/java/com/horcrux/svg/TextShadowNode.java | 5 ++--- android/src/main/java/com/horcrux/svg/VirtualNode.java | 4 ++-- 9 files changed, 22 insertions(+), 24 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/DefsShadowNode.java b/android/src/main/java/com/horcrux/svg/DefsShadowNode.java index d636d3b0..8cd720a0 100644 --- a/android/src/main/java/com/horcrux/svg/DefsShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/DefsShadowNode.java @@ -12,7 +12,7 @@ package com.horcrux.svg; import android.graphics.Canvas; import android.graphics.Paint; -import com.facebook.react.uimanager.LayoutShadowNode; +import com.facebook.react.uimanager.ReactShadowNode; /** * Shadow node for virtual Defs view @@ -22,7 +22,7 @@ class DefsShadowNode extends DefinitionShadowNode { @Override public void draw(Canvas canvas, Paint paint, float opacity) { NodeRunnable markUpdateSeenRecursive = new NodeRunnable() { - public void run(LayoutShadowNode node) { + public void run(ReactShadowNode node) { node.markUpdateSeen(); if (node instanceof VirtualNode) { ((VirtualNode) node).traverseChildren(this); @@ -36,7 +36,7 @@ class DefsShadowNode extends DefinitionShadowNode { void saveDefinition() { traverseChildren(new NodeRunnable() { - public void run(LayoutShadowNode node) { + public void run(ReactShadowNode node) { if (node instanceof VirtualNode) { ((VirtualNode)node).saveDefinition(); } diff --git a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java index dc6b7809..4847010f 100644 --- a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java @@ -17,7 +17,6 @@ import android.graphics.Point; import android.graphics.RectF; import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.uimanager.LayoutShadowNode; import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.annotations.ReactProp; @@ -73,7 +72,7 @@ class GroupShadowNode extends RenderableShadowNode { final SvgViewShadowNode svg = getSvgShadowNode(); final GroupShadowNode self = this; traverseChildren(new NodeRunnable() { - public void run(LayoutShadowNode lNode) { + public void run(ReactShadowNode lNode) { if (lNode instanceof VirtualNode) { VirtualNode node = ((VirtualNode)lNode); if (node instanceof RenderableShadowNode) { @@ -113,7 +112,7 @@ class GroupShadowNode extends RenderableShadowNode { final Path path = new Path(); traverseChildren(new NodeRunnable() { - public void run(LayoutShadowNode node) { + public void run(ReactShadowNode node) { if (node instanceof VirtualNode) { path.addPath(((VirtualNode)node).getPath(canvas, paint)); } @@ -165,7 +164,7 @@ class GroupShadowNode extends RenderableShadowNode { } traverseChildren(new NodeRunnable() { - public void run(LayoutShadowNode node) { + public void run(ReactShadowNode node) { if (node instanceof VirtualNode) { ((VirtualNode)node).saveDefinition(); } @@ -176,7 +175,7 @@ class GroupShadowNode extends RenderableShadowNode { @Override public void resetProperties() { traverseChildren(new NodeRunnable() { - public void run(LayoutShadowNode node) { + public void run(ReactShadowNode node) { if (node instanceof RenderableShadowNode) { ((RenderableShadowNode)node).resetProperties(); } diff --git a/android/src/main/java/com/horcrux/svg/SvgPackage.java b/android/src/main/java/com/horcrux/svg/SvgPackage.java index 5aaaa9a4..be0605ab 100644 --- a/android/src/main/java/com/horcrux/svg/SvgPackage.java +++ b/android/src/main/java/com/horcrux/svg/SvgPackage.java @@ -49,6 +49,7 @@ public class SvgPackage implements ReactPackage { return Collections.singletonList(new SvgViewModule(reactContext)); } + @SuppressWarnings("unused") public List> createJSModules() { return Collections.emptyList(); } diff --git a/android/src/main/java/com/horcrux/svg/SvgView.java b/android/src/main/java/com/horcrux/svg/SvgView.java index ae50edaf..44ebe388 100644 --- a/android/src/main/java/com/horcrux/svg/SvgView.java +++ b/android/src/main/java/com/horcrux/svg/SvgView.java @@ -20,13 +20,12 @@ import android.view.ViewGroup; import com.facebook.react.ReactRootView; import com.facebook.react.bridge.ReactContext; -import com.facebook.react.uimanager.LayoutShadowNode; import com.facebook.react.uimanager.ReactShadowNodeImpl; import com.facebook.react.uimanager.UIManagerModule; +import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.react.uimanager.events.TouchEvent; import com.facebook.react.uimanager.events.TouchEventCoalescingKeyHelper; import com.facebook.react.uimanager.events.TouchEventType; -import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.react.views.view.ReactViewGroup; import javax.annotation.Nullable; @@ -36,6 +35,7 @@ import javax.annotation.Nullable; */ @SuppressLint("ViewConstructor") public class SvgView extends ViewGroup { + @SuppressWarnings("unused") public enum Events { @SuppressWarnings("unused") EVENT_DATA_URL("onDataURL"); diff --git a/android/src/main/java/com/horcrux/svg/SvgViewManager.java b/android/src/main/java/com/horcrux/svg/SvgViewManager.java index 2efc3639..150e96e2 100644 --- a/android/src/main/java/com/horcrux/svg/SvgViewManager.java +++ b/android/src/main/java/com/horcrux/svg/SvgViewManager.java @@ -12,12 +12,11 @@ package com.horcrux.svg; import android.graphics.Bitmap; import android.util.SparseArray; -import com.facebook.react.uimanager.ViewGroupManager; -import com.facebook.yoga.YogaMeasureMode; -import com.facebook.yoga.YogaMeasureFunction; -import com.facebook.yoga.YogaNode; -import com.facebook.react.uimanager.BaseViewManager; import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.ViewGroupManager; +import com.facebook.yoga.YogaMeasureFunction; +import com.facebook.yoga.YogaMeasureMode; +import com.facebook.yoga.YogaNode; import javax.annotation.Nullable; diff --git a/android/src/main/java/com/horcrux/svg/SvgViewModule.java b/android/src/main/java/com/horcrux/svg/SvgViewModule.java index b06f8c90..b5049db2 100644 --- a/android/src/main/java/com/horcrux/svg/SvgViewModule.java +++ b/android/src/main/java/com/horcrux/svg/SvgViewModule.java @@ -25,6 +25,7 @@ class SvgViewModule extends ReactContextBaseJavaModule { } + @SuppressWarnings("unused") @ReactMethod public void toDataURL(int tag, Callback successCallback) { SvgViewShadowNode svg = SvgViewManager.getShadowNodeByTag(tag); diff --git a/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java b/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java index 80db8d87..defaf63d 100644 --- a/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java @@ -18,7 +18,6 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; import android.util.Base64; -import android.view.View; import com.facebook.react.uimanager.DisplayMetricsHolder; import com.facebook.react.uimanager.LayoutShadowNode; @@ -167,7 +166,7 @@ public class SvgViewShadowNode extends LayoutShadowNode { traverseChildren(new VirtualNode.NodeRunnable() { - public void run(LayoutShadowNode node) { + public void run(ReactShadowNode node) { if (node instanceof VirtualNode) { ((VirtualNode)node).saveDefinition(); } @@ -175,7 +174,7 @@ public class SvgViewShadowNode extends LayoutShadowNode { }); traverseChildren(new VirtualNode.NodeRunnable() { - public void run(LayoutShadowNode lNode) { + public void run(ReactShadowNode lNode) { if (lNode instanceof VirtualNode) { VirtualNode node = (VirtualNode)lNode; int count = node.saveAndSetupCanvas(canvas); @@ -265,7 +264,7 @@ public class SvgViewShadowNode extends LayoutShadowNode { void traverseChildren(VirtualNode.NodeRunnable runner) { for (int i = 0; i < getChildCount(); i++) { ReactShadowNode child = getChildAt(i); - runner.run((LayoutShadowNode) child); + runner.run(child); } } } diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index d7ae8491..9b4225cf 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -15,7 +15,6 @@ import android.graphics.Path; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.uimanager.LayoutShadowNode; import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.annotations.ReactProp; @@ -27,7 +26,7 @@ import javax.annotation.Nullable; class TextShadowNode extends GroupShadowNode { String mTextLength = null; - String mBaselineShift = null; + private String mBaselineShift = null; TextLengthAdjust mLengthAdjust = TextLengthAdjust.spacing; private AlignmentBaseline mAlignmentBaseline; private @Nullable ReadableArray mPositionX; @@ -178,7 +177,7 @@ class TextShadowNode extends GroupShadowNode { void releaseCachedPath() { traverseChildren(new NodeRunnable() { - public void run(LayoutShadowNode node) { + public void run(ReactShadowNode node) { if (node instanceof TextShadowNode) { ((TextShadowNode)node).releaseCachedPath(); } diff --git a/android/src/main/java/com/horcrux/svg/VirtualNode.java b/android/src/main/java/com/horcrux/svg/VirtualNode.java index f29cc9e9..d4a3d0d0 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/VirtualNode.java @@ -314,13 +314,13 @@ abstract class VirtualNode extends LayoutShadowNode { } interface NodeRunnable { - void run(LayoutShadowNode node); + void run(ReactShadowNode node); } void traverseChildren(NodeRunnable runner) { for (int i = 0; i < getChildCount(); i++) { ReactShadowNode child = getChildAt(i); - runner.run((LayoutShadowNode) child); + runner.run(child); } } } From 149f4601084a4ba3cec56844e4c91ff4f2c4ef62 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Thu, 22 Feb 2018 02:24:11 +0200 Subject: [PATCH 05/10] Improve path caching on android --- .../java/com/horcrux/svg/RenderableShadowNode.java | 9 +++++---- android/src/main/java/com/horcrux/svg/SvgView.java | 5 +++-- .../src/main/java/com/horcrux/svg/VirtualNode.java | 13 +++++++++++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java b/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java index 886bf557..8675c1ba 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java @@ -67,8 +67,6 @@ abstract public class RenderableShadowNode extends VirtualNode { public float mFillOpacity = 1; public Path.FillType mFillRule = Path.FillType.WINDING; - protected Path mPath; - private @Nullable ReadableArray mLastMergedList; private @Nullable ArrayList mOriginProperties; protected @Nullable ReadableArray mPropList; @@ -204,10 +202,13 @@ abstract public class RenderableShadowNode extends VirtualNode { opacity *= mOpacity; if (opacity > MIN_OPACITY_FOR_DRAW) { - mPath = getPath(canvas, paint); - mPath.setFillType(mFillRule); + if (mPath == null) { + mPath = getPath(canvas, paint); + mPath.setFillType(mFillRule); + } clip(canvas, paint); + if (setupFillPaint(paint, opacity * mFillOpacity)) { canvas.drawPath(mPath, paint); } diff --git a/android/src/main/java/com/horcrux/svg/SvgView.java b/android/src/main/java/com/horcrux/svg/SvgView.java index 44ebe388..21d91127 100644 --- a/android/src/main/java/com/horcrux/svg/SvgView.java +++ b/android/src/main/java/com/horcrux/svg/SvgView.java @@ -106,13 +106,14 @@ public class SvgView extends ViewGroup { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { + ReactShadowNodeImpl node = getShadowNode(); for (int i = 0; i < this.getChildCount(); i++) { View child = this.getChildAt(i); if (child instanceof ReactViewGroup) { - ReactShadowNodeImpl node = getShadowNode(); + int id = child.getId(); for (int j = 0; j < node.getChildCount(); j++) { ReactShadowNodeImpl nodeChild = node.getChildAt(j); - if (nodeChild.getReactTag() != child.getId()) { + if (nodeChild.getReactTag() != id) { continue; } diff --git a/android/src/main/java/com/horcrux/svg/VirtualNode.java b/android/src/main/java/com/horcrux/svg/VirtualNode.java index d4a3d0d0..d8d496bd 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/VirtualNode.java @@ -63,6 +63,8 @@ abstract class VirtualNode extends LayoutShadowNode { private float canvasWidth = -1; private GlyphContext glyphContext; + Path mPath; + VirtualNode() { setIsLayoutOnly(true); mScale = DisplayMetricsHolder.getScreenDisplayMetrics().density; @@ -78,6 +80,12 @@ abstract class VirtualNode extends LayoutShadowNode { return true; } + @Override + public void markUpdated() { + super.markUpdated(); + mPath = null; + } + @Nullable GroupShadowNode getTextRoot() { VirtualNode node = this; @@ -161,6 +169,7 @@ abstract class VirtualNode extends LayoutShadowNode { @ReactProp(name = "clipPath") public void setClipPath(String clipPath) { + mCachedClipPath = null; mClipPath = clipPath; markUpdated(); } @@ -193,7 +202,7 @@ abstract class VirtualNode extends LayoutShadowNode { mMatrix = null; } - markUpdated(); + super.markUpdated(); } @ReactProp(name = "responsible") @@ -207,7 +216,7 @@ abstract class VirtualNode extends LayoutShadowNode { } @Nullable Path getClipPath(Canvas canvas, Paint paint) { - if (mClipPath != null) { + if (mClipPath != null && mCachedClipPath == null) { VirtualNode node = getSvgShadowNode().getDefinedClipPath(mClipPath); if (node != null) { From db8f1da46b3439435f6b057897936955d229f4bb Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Thu, 22 Feb 2018 03:48:39 +0200 Subject: [PATCH 06/10] Improve path caching on ios --- ios/RNSVGNode.h | 1 + ios/RNSVGNode.m | 10 +++++++--- ios/RNSVGRenderable.m | 29 +++++++++++++++++++++-------- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/ios/RNSVGNode.h b/ios/RNSVGNode.h index 96d1d924..03dc320f 100644 --- a/ios/RNSVGNode.h +++ b/ios/RNSVGNode.h @@ -33,6 +33,7 @@ extern CGFloat const RNSVG_DEFAULT_FONT_SIZE; @property (nonatomic, assign) BOOL responsible; @property (nonatomic, assign) CGAffineTransform matrix; @property (nonatomic, assign) BOOL active; +@property (nonatomic, assign) CGPathRef path; - (void)invalidate; diff --git a/ios/RNSVGNode.m b/ios/RNSVGNode.m index bca9585d..2dd935cb 100644 --- a/ios/RNSVGNode.m +++ b/ios/RNSVGNode.m @@ -54,6 +54,10 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; { id container = (id)self.superview; [container invalidate]; + if (_path) { + CGPathRelease(_path); + _path = nil; + } } - (RNSVGGroup *)getTextRoot @@ -130,8 +134,9 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; if (CGAffineTransformEqualToTransform(matrix, _matrix)) { return; } - [self invalidate]; _matrix = matrix; + id container = (id)self.superview; + [container invalidate]; } - (void)setClipPath:(NSString *)clipPath @@ -171,8 +176,7 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; - (CGPathRef)getClipPath:(CGContextRef)context { - if (self.clipPath) { - CGPathRelease(_cachedClipPath); + if (self.clipPath && !_cachedClipPath) { _cachedClipPath = CGPathRetain([[[self getSvgView] getDefinedClipPath:self.clipPath] getPath:context]); } diff --git a/ios/RNSVGRenderable.m b/ios/RNSVGRenderable.m index 626e7aea..7daa992d 100644 --- a/ios/RNSVGRenderable.m +++ b/ios/RNSVGRenderable.m @@ -14,6 +14,16 @@ NSArray *_lastMergedList; NSArray *_attributeList; CGPathRef _hitArea; + CGPathRef _path; +} + +- (void)invalidate +{ + [super invalidate]; + if (_path) { + CGPathRelease(_path); + _path = nil; + } } - (id)init @@ -151,6 +161,7 @@ - (void)dealloc { + CGPathRelease(_path); CGPathRelease(_hitArea); if (_strokeDasharrayData.array) { free(_strokeDasharrayData.array); @@ -178,13 +189,15 @@ return; } - CGPathRef path = [self getPath:context]; - [self setHitArea:path]; - if (self.opacity == 0) { return; } + if (!_path) { + _path = [self getPath:context]; + [self setHitArea:_path]; + } + CGPathDrawingMode mode = kCGPathStroke; BOOL fillColor = NO; [self clip:context]; @@ -198,7 +211,7 @@ mode = evenodd ? kCGPathEOFill : kCGPathFill; } else { CGContextSaveGState(context); - CGContextAddPath(context, path); + CGContextAddPath(context, _path); CGContextClip(context); [self.fill paint:context opacity:self.fillOpacity @@ -224,7 +237,7 @@ } if (!fillColor) { - CGContextAddPath(context, path); + CGContextAddPath(context, _path); CGContextReplacePathWithStrokedPath(context); CGContextClip(context); } @@ -236,12 +249,12 @@ } else if (!strokeColor) { // draw fill if (fillColor) { - CGContextAddPath(context, path); + CGContextAddPath(context, _path); CGContextDrawPath(context, mode); } // draw stroke - CGContextAddPath(context, path); + CGContextAddPath(context, _path); CGContextReplacePathWithStrokedPath(context); CGContextClip(context); @@ -253,7 +266,7 @@ } } - CGContextAddPath(context, path); + CGContextAddPath(context, _path); CGContextDrawPath(context, mode); } From cd43fc151e2b312cf7766bc107920635c457be67 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 23 Feb 2018 00:19:10 +0200 Subject: [PATCH 07/10] Optimize hit testing. --- .../com/horcrux/svg/ClipPathShadowNode.java | 2 +- .../com/horcrux/svg/DefinitionShadowNode.java | 2 +- .../java/com/horcrux/svg/GroupShadowNode.java | 28 ++++--- .../com/horcrux/svg/RenderableShadowNode.java | 78 ++++++++++++------- .../com/horcrux/svg/SvgViewShadowNode.java | 12 ++- .../java/com/horcrux/svg/TSpanShadowNode.java | 2 - .../java/com/horcrux/svg/VirtualNode.java | 14 +++- ios/Elements/RNSVGGroup.m | 17 ++-- ios/Elements/RNSVGSvgView.m | 5 +- ios/RNSVG.xcodeproj/project.pbxproj | 25 +++++- ios/RNSVGNode.h | 6 +- ios/RNSVGNode.m | 7 +- ios/RNSVGRenderable.m | 59 +++++--------- 13 files changed, 143 insertions(+), 114 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/ClipPathShadowNode.java b/android/src/main/java/com/horcrux/svg/ClipPathShadowNode.java index 42c11f5c..58fc52bb 100644 --- a/android/src/main/java/com/horcrux/svg/ClipPathShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/ClipPathShadowNode.java @@ -38,7 +38,7 @@ class ClipPathShadowNode extends GroupShadowNode { } @Override - public int hitTest(Point point, Matrix matrix) { + public int hitTest(float[] src) { return -1; } diff --git a/android/src/main/java/com/horcrux/svg/DefinitionShadowNode.java b/android/src/main/java/com/horcrux/svg/DefinitionShadowNode.java index bb51c74c..00415b2c 100644 --- a/android/src/main/java/com/horcrux/svg/DefinitionShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/DefinitionShadowNode.java @@ -33,7 +33,7 @@ class DefinitionShadowNode extends VirtualNode { } @Override - public int hitTest(Point point, Matrix matrix) { + public int hitTest(float[] src) { return -1; } } diff --git a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java index 4847010f..5eadcd22 100644 --- a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java @@ -123,22 +123,26 @@ class GroupShadowNode extends RenderableShadowNode { } @Override - public int hitTest(final Point point, final @Nullable Matrix matrix) { - int hitSelf = super.hitTest(point, matrix); - if (hitSelf != -1) { - return hitSelf; + public int hitTest(final float[] src) { + if (!mInvertible) { + return -1; } - Matrix groupMatrix = new Matrix(mMatrix); + float[] dst = new float[2]; + mInvMatrix.mapPoints(dst, src); - if (matrix != null) { - groupMatrix.postConcat(matrix); - } + int x = Math.round(dst[0]); + int y = Math.round(dst[1]); Path clipPath = getClipPath(); - - if (clipPath != null && !pathContainsPoint(clipPath, groupMatrix, point)) { - return -1; + if (clipPath != null) { + if (mClipRegionPath != clipPath) { + mClipRegionPath = clipPath; + mClipRegion = getRegion(clipPath); + } + if (!mClipRegion.contains(x, y)) { + return -1; + } } for (int i = getChildCount() - 1; i >= 0; i--) { @@ -149,7 +153,7 @@ class GroupShadowNode extends RenderableShadowNode { VirtualNode node = (VirtualNode) child; - int hitChild = node.hitTest(point, groupMatrix); + int hitChild = node.hitTest(dst); if (hitChild != -1) { return (node.isResponsible() || hitChild != child.getReactTag()) ? hitChild : getReactTag(); } diff --git a/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java b/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java index 8675c1ba..4f97ca3e 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java @@ -20,6 +20,8 @@ import android.graphics.Path; import android.graphics.Point; import android.graphics.RectF; import android.graphics.Region; +import android.os.Build; +import android.util.Log; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; @@ -32,6 +34,8 @@ import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static android.content.ContentValues.TAG; + /** * Renderable shadow node */ @@ -97,7 +101,6 @@ abstract public class RenderableShadowNode extends VirtualNode { "fillRule " + mFillRule + " unrecognized"); } - mPath = null; markUpdated(); } @@ -205,6 +208,19 @@ abstract public class RenderableShadowNode extends VirtualNode { if (mPath == null) { mPath = getPath(canvas, paint); mPath.setFillType(mFillRule); + + mBox = new RectF(); + mPath.computeBounds(mBox, true); + + mRegion = new Region(); + mRegion.setPath(mPath, + new Region( + (int) Math.floor(mBox.left), + (int) Math.floor(mBox.top), + (int) Math.ceil(mBox.right), + (int) Math.ceil(mBox.bottom) + ) + ); } clip(canvas, paint); @@ -264,7 +280,6 @@ abstract public class RenderableShadowNode extends VirtualNode { return true; } - private void setupPaint(Paint paint, float opacity, ReadableArray colors) { int colorType = colors.getInt(0); if (colorType == 0) { @@ -275,55 +290,60 @@ abstract public class RenderableShadowNode extends VirtualNode { (int) (colors.getDouble(2) * 255), (int) (colors.getDouble(3) * 255)); } else if (colorType == 1) { - RectF box = new RectF(); - mPath.computeBounds(box, true); - Brush brush = getSvgShadowNode().getDefinedBrush(colors.getString(1)); if (brush != null) { - brush.setupPaint(paint, box, mScale, opacity); + brush.setupPaint(paint, mBox, mScale, opacity); } } } - abstract protected Path getPath(Canvas canvas, Paint paint); @Override - public int hitTest(Point point, @Nullable Matrix matrix) { - if (mPath == null) { + public int hitTest(final float[] src) { + if (mPath == null || !mInvertible) { return -1; } - Matrix pathMatrix = new Matrix(mMatrix); + float[] dst = new float[2]; + mInvMatrix.mapPoints(dst, src); + int x = Math.round(dst[0]); + int y = Math.round(dst[1]); - if (matrix != null) { - pathMatrix.postConcat(matrix); + if (!mRegion.contains(x, y)) { + return -1; } - if (pathContainsPoint(mPath, pathMatrix, point)) { - Path clipPath = getClipPath(); - if (clipPath != null && !pathContainsPoint(clipPath, pathMatrix, point)) { - return -1; + Path clipPath = getClipPath(); + if (clipPath != null) { + if (mClipRegionPath != clipPath) { + mClipRegionPath = clipPath; + mClipRegion = getRegion(clipPath); + } + if (!mClipRegion.contains(x, y)) { + return -1; } - - return getReactTag(); - } else{ - return -1; } + + return getReactTag(); } - boolean pathContainsPoint(Path path, Matrix matrix, Point point) { - Path copy = new Path(path); - - copy.transform(matrix); - + Region getRegion(Path path) { RectF rectF = new RectF(); - copy.computeBounds(rectF, true); - Region region = new Region(); - region.setPath(copy, new Region((int) rectF.left, (int) rectF.top, (int) rectF.right, (int) rectF.bottom)); + path.computeBounds(rectF, true); - return region.contains(point.x, point.y); + Region region = new Region(); + region.setPath(path, + new Region( + (int) Math.floor(rectF.left), + (int) Math.floor(rectF.top), + (int) Math.ceil(rectF.right), + (int) Math.ceil(rectF.bottom) + ) + ); + + return region; } private WritableArray getAttributeList() { diff --git a/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java b/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java index defaf63d..0495683c 100644 --- a/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java @@ -49,7 +49,9 @@ public class SvgViewShadowNode extends LayoutShadowNode { private String mbbHeight; private String mAlign; private int mMeetOrSlice; - private Matrix mViewBoxMatrix; + private Matrix mViewBoxMatrix = new Matrix(); + private Matrix mInvViewBoxMatrix = new Matrix(); + private boolean mInvertible = true; public SvgViewShadowNode() { mScale = DisplayMetricsHolder.getScreenDisplayMetrics().density; @@ -155,6 +157,7 @@ public class SvgViewShadowNode extends LayoutShadowNode { canvas.clipRect(eRect); } mViewBoxMatrix = ViewBox.getTransform(vbRect, eRect, mAlign, mMeetOrSlice); + mInvertible = mViewBoxMatrix.invert(mInvViewBoxMatrix); canvas.concat(mViewBoxMatrix); } @@ -217,10 +220,13 @@ public class SvgViewShadowNode extends LayoutShadowNode { } int hitTest(Point point) { - if (!mResponsible) { + if (!mResponsible || !mInvertible) { return -1; } + float[] transformed = { point.x, point.y }; + mInvViewBoxMatrix.mapPoints(transformed); + int count = getChildCount(); int viewTag = -1; for (int i = count - 1; i >= 0; i--) { @@ -228,7 +234,7 @@ public class SvgViewShadowNode extends LayoutShadowNode { continue; } - viewTag = ((VirtualNode) getChildAt(i)).hitTest(point, mViewBoxMatrix); + viewTag = ((VirtualNode) getChildAt(i)).hitTest(transformed); if (viewTag != -1) { break; } diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 03596594..c20c388a 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -84,8 +84,6 @@ class TSpanShadowNode extends TextShadowNode { mCache = getLinePath(mContent, paint, canvas); popGlyphContext(); - mCache.computeBounds(new RectF(), true); - return mCache; } diff --git a/android/src/main/java/com/horcrux/svg/VirtualNode.java b/android/src/main/java/com/horcrux/svg/VirtualNode.java index d8d496bd..bde0883f 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/VirtualNode.java @@ -14,6 +14,7 @@ import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Point; +import android.graphics.RectF; import android.graphics.Region; import com.facebook.common.logging.FLog; @@ -45,6 +46,8 @@ abstract class VirtualNode extends LayoutShadowNode { }; float mOpacity = 1f; Matrix mMatrix = new Matrix(); + Matrix mInvMatrix = new Matrix(); + boolean mInvertible = true; private int mClipRule; private @Nullable String mClipPath; @@ -64,6 +67,10 @@ abstract class VirtualNode extends LayoutShadowNode { private GlyphContext glyphContext; Path mPath; + RectF mBox; + Region mRegion; + Path mClipRegionPath; + Region mClipRegion; VirtualNode() { setIsLayoutOnly(true); @@ -84,6 +91,8 @@ abstract class VirtualNode extends LayoutShadowNode { public void markUpdated() { super.markUpdated(); mPath = null; + mBox = null; + mRegion = null; } @Nullable @@ -193,13 +202,16 @@ abstract class VirtualNode extends LayoutShadowNode { if (matrixSize == 6) { if (mMatrix == null) { mMatrix = new Matrix(); + mInvMatrix = new Matrix(); } mMatrix.setValues(sRawMatrix); + mInvertible = mMatrix.invert(mInvMatrix); } else if (matrixSize != -1) { FLog.w(ReactConstants.TAG, "RNSVG: Transform matrices must be of size 6"); } } else { mMatrix = null; + mInvMatrix = null; } super.markUpdated(); @@ -247,7 +259,7 @@ abstract class VirtualNode extends LayoutShadowNode { } } - abstract public int hitTest(Point point, @Nullable Matrix matrix); + abstract public int hitTest(final float[] point); public boolean isResponsible() { return mResponsible; diff --git a/ios/Elements/RNSVGGroup.m b/ios/Elements/RNSVGGroup.m index 60397c16..5b1435a5 100644 --- a/ios/Elements/RNSVGGroup.m +++ b/ios/Elements/RNSVGGroup.m @@ -109,25 +109,21 @@ return (CGPathRef)CFAutorelease(path); } -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event withTransform:(CGAffineTransform)transform +- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { - UIView *hitSelf = [super hitTest:point withEvent:event withTransform:transform]; + CGPoint transformed = CGPointApplyAffineTransform(point, CGAffineTransformInvert(self.matrix)); + + UIView *hitSelf = [super hitTest:transformed withEvent:event]; if (hitSelf) { return hitSelf; } - CGAffineTransform matrix = CGAffineTransformConcat(self.matrix, transform); - CGPathRef clip = [self getClipPath]; if (clip) { - CGPathRef transformedClipPath = CGPathCreateCopyByTransformingPath(clip, &matrix); - BOOL insideClipPath = CGPathContainsPoint(clip, nil, point, self.clipRule == kRNSVGCGFCRuleEvenodd); - CGPathRelease(transformedClipPath); - + BOOL insideClipPath = CGPathContainsPoint(clip, nil, transformed, self.clipRule == kRNSVGCGFCRuleEvenodd); if (!insideClipPath) { return nil; } - } for (RNSVGNode *node in [self.subviews reverseObjectEnumerator]) { @@ -141,13 +137,14 @@ return node; } - UIView *hitChild = [node hitTest: point withEvent:event withTransform:matrix]; + UIView *hitChild = [node hitTest:transformed withEvent:event]; if (hitChild) { node.active = YES; return (node.responsible || (node != hitChild)) ? hitChild : self; } } + return nil; } diff --git a/ios/Elements/RNSVGSvgView.m b/ios/Elements/RNSVGSvgView.m index 4bf9f3ce..edb50447 100644 --- a/ios/Elements/RNSVGSvgView.m +++ b/ios/Elements/RNSVGSvgView.m @@ -17,6 +17,7 @@ NSMutableDictionary *_templates; NSMutableDictionary *_painters; CGAffineTransform _viewBoxTransform; + CGAffineTransform _invviewBoxTransform; } - (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex @@ -129,6 +130,7 @@ eRect:rect align:self.align meetOrSlice:self.meetOrSlice]; + _invviewBoxTransform = CGAffineTransformInvert(_viewBoxTransform); CGContextConcatCTM(context, _viewBoxTransform); } @@ -167,6 +169,7 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { if (self.align) { + CGPoint transformed = CGPointApplyAffineTransform(point, _invviewBoxTransform); for (RNSVGNode *node in [self.subviews reverseObjectEnumerator]) { if (![node isKindOfClass:[RNSVGNode class]]) { continue; @@ -178,7 +181,7 @@ return node; } - UIView *hitChild = [node hitTest: point withEvent:event withTransform:_viewBoxTransform]; + UIView *hitChild = [node hitTest:transformed withEvent:event]; if (hitChild) { node.active = YES; diff --git a/ios/RNSVG.xcodeproj/project.pbxproj b/ios/RNSVG.xcodeproj/project.pbxproj index 83003f4c..f5234e0e 100644 --- a/ios/RNSVG.xcodeproj/project.pbxproj +++ b/ios/RNSVG.xcodeproj/project.pbxproj @@ -505,7 +505,7 @@ 0CF68AB91AF0540F00FF9E5C /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0620; + LastUpgradeCheck = 0920; TargetAttributes = { 0CF68AC01AF0540F00FF9E5C = { CreatedOnToolsVersion = 6.2; @@ -653,19 +653,29 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -683,7 +693,7 @@ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/../../React/**", ); - IPHONEOS_DEPLOYMENT_TARGET = 7.0; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = ( @@ -702,19 +712,28 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; @@ -726,7 +745,7 @@ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/../../React/**", ); - IPHONEOS_DEPLOYMENT_TARGET = 7.0; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; OTHER_LDFLAGS = ( "-ObjC++", diff --git a/ios/RNSVGNode.h b/ios/RNSVGNode.h index 03dc320f..ee1afde8 100644 --- a/ios/RNSVGNode.h +++ b/ios/RNSVGNode.h @@ -32,6 +32,7 @@ extern CGFloat const RNSVG_DEFAULT_FONT_SIZE; @property (nonatomic, strong) NSString *clipPath; @property (nonatomic, assign) BOOL responsible; @property (nonatomic, assign) CGAffineTransform matrix; +@property (nonatomic, assign) CGAffineTransform invmatrix; @property (nonatomic, assign) BOOL active; @property (nonatomic, assign) CGPathRef path; @@ -69,11 +70,6 @@ extern CGFloat const RNSVG_DEFAULT_FONT_SIZE; */ - (CGPathRef)getPath:(CGContextRef) context; -/** - * run hitTest - */ -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event withTransform:(CGAffineTransform)transfrom; - /** * get RNSVGSvgView which ownes current RNSVGNode */ diff --git a/ios/RNSVGNode.m b/ios/RNSVGNode.m index 2dd935cb..a5173cfb 100644 --- a/ios/RNSVGNode.m +++ b/ios/RNSVGNode.m @@ -135,6 +135,7 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; return; } _matrix = matrix; + _invmatrix = CGAffineTransformInvert(matrix); id container = (id)self.superview; [container invalidate]; } @@ -216,12 +217,6 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; return nil; } -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event withTransform:(CGAffineTransform)transfrom -{ - // abstract - return nil; -} - - (RNSVGSvgView *)getSvgView { if (_svgView) { diff --git a/ios/RNSVGRenderable.m b/ios/RNSVGRenderable.m index 7daa992d..11c5b353 100644 --- a/ios/RNSVGRenderable.m +++ b/ios/RNSVGRenderable.m @@ -14,16 +14,6 @@ NSArray *_lastMergedList; NSArray *_attributeList; CGPathRef _hitArea; - CGPathRef _path; -} - -- (void)invalidate -{ - [super invalidate]; - if (_path) { - CGPathRelease(_path); - _path = nil; - } } - (id)init @@ -161,7 +151,7 @@ - (void)dealloc { - CGPathRelease(_path); + CGPathRelease(self.path); CGPathRelease(_hitArea); if (_strokeDasharrayData.array) { free(_strokeDasharrayData.array); @@ -193,9 +183,9 @@ return; } - if (!_path) { - _path = [self getPath:context]; - [self setHitArea:_path]; + if (!self.path) { + self.path = CGPathRetain(CFAutorelease(CGPathCreateCopy([self getPath:context]))); + [self setHitArea:self.path]; } CGPathDrawingMode mode = kCGPathStroke; @@ -211,7 +201,7 @@ mode = evenodd ? kCGPathEOFill : kCGPathFill; } else { CGContextSaveGState(context); - CGContextAddPath(context, _path); + CGContextAddPath(context, self.path); CGContextClip(context); [self.fill paint:context opacity:self.fillOpacity @@ -237,7 +227,7 @@ } if (!fillColor) { - CGContextAddPath(context, _path); + CGContextAddPath(context, self.path); CGContextReplacePathWithStrokedPath(context); CGContextClip(context); } @@ -249,12 +239,12 @@ } else if (!strokeColor) { // draw fill if (fillColor) { - CGContextAddPath(context, _path); + CGContextAddPath(context, self.path); CGContextDrawPath(context, mode); } // draw stroke - CGContextAddPath(context, _path); + CGContextAddPath(context, self.path); CGContextReplacePathWithStrokedPath(context); CGContextClip(context); @@ -266,13 +256,14 @@ } } - CGContextAddPath(context, _path); + CGContextAddPath(context, self.path); CGContextDrawPath(context, mode); } - (void)setHitArea:(CGPathRef)path { CGPathRelease(_hitArea); + _hitArea = nil; if (self.responsible) { // Add path to hitArea CGMutablePathRef hitArea = CGPathCreateMutableCopy(path); @@ -293,11 +284,6 @@ // hitTest delagate - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event -{ - return [self hitTest:point withEvent:event withTransform:CGAffineTransformIdentity]; -} - -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event withTransform:(CGAffineTransform)transform { if (!_hitArea) { return nil; @@ -310,25 +296,18 @@ return self; } - CGAffineTransform matrix = CGAffineTransformConcat(self.matrix, transform); - CGPathRef hitArea = CGPathCreateCopyByTransformingPath(_hitArea, &matrix); - BOOL contains = CGPathContainsPoint(hitArea, nil, point, NO); - CGPathRelease(hitArea); + CGPoint transformed = CGPointApplyAffineTransform(point, self.invmatrix); - if (contains) { - CGPathRef clipPath = [self getClipPath]; - - if (!clipPath) { - return self; - } else { - CGPathRef transformedClipPath = CGPathCreateCopyByTransformingPath(clipPath, &matrix); - BOOL result = CGPathContainsPoint(transformedClipPath, nil, point, self.clipRule == kRNSVGCGFCRuleEvenodd); - CGPathRelease(transformedClipPath); - return result ? self : nil; - } - } else { + if (!CGPathContainsPoint(_hitArea, nil, transformed, NO)) { return nil; } + + CGPathRef clipPath = [self getClipPath]; + if (clipPath && !CGPathContainsPoint(clipPath, nil, transformed, self.clipRule == kRNSVGCGFCRuleEvenodd)) { + return nil; + } + + return self; } - (NSArray *)getAttributeList From 14c3b4f8678884493d96bfd05592e173fe2c7d6c Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 23 Feb 2018 00:49:14 +0200 Subject: [PATCH 08/10] Lazily construct hitTest Region on demand. --- .../com/horcrux/svg/RenderableShadowNode.java | 20 +++++++------------ .../java/com/horcrux/svg/VirtualNode.java | 4 ++-- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java b/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java index 4f97ca3e..594b9a80 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java @@ -208,19 +208,6 @@ abstract public class RenderableShadowNode extends VirtualNode { if (mPath == null) { mPath = getPath(canvas, paint); mPath.setFillType(mFillRule); - - mBox = new RectF(); - mPath.computeBounds(mBox, true); - - mRegion = new Region(); - mRegion.setPath(mPath, - new Region( - (int) Math.floor(mBox.left), - (int) Math.floor(mBox.top), - (int) Math.ceil(mBox.right), - (int) Math.ceil(mBox.bottom) - ) - ); } clip(canvas, paint); @@ -292,6 +279,10 @@ abstract public class RenderableShadowNode extends VirtualNode { } else if (colorType == 1) { Brush brush = getSvgShadowNode().getDefinedBrush(colors.getString(1)); if (brush != null) { + if (mBox == null) { + mBox = new RectF(); + mPath.computeBounds(mBox, true); + } brush.setupPaint(paint, mBox, mScale, opacity); } } @@ -311,6 +302,9 @@ abstract public class RenderableShadowNode extends VirtualNode { int x = Math.round(dst[0]); int y = Math.round(dst[1]); + if (mRegion == null) { + mRegion = getRegion(mPath); + } if (!mRegion.contains(x, y)) { return -1; } diff --git a/android/src/main/java/com/horcrux/svg/VirtualNode.java b/android/src/main/java/com/horcrux/svg/VirtualNode.java index bde0883f..dd0c3952 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/VirtualNode.java @@ -69,8 +69,8 @@ abstract class VirtualNode extends LayoutShadowNode { Path mPath; RectF mBox; Region mRegion; - Path mClipRegionPath; Region mClipRegion; + Path mClipRegionPath; VirtualNode() { setIsLayoutOnly(true); @@ -90,9 +90,9 @@ abstract class VirtualNode extends LayoutShadowNode { @Override public void markUpdated() { super.markUpdated(); + mRegion = null; mPath = null; mBox = null; - mRegion = null; } @Nullable From 47fe6beedfd640b384593d8ab2f7e196b4b20a17 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 23 Feb 2018 01:00:00 +0200 Subject: [PATCH 09/10] Organize import, fix stale width and height cache. --- .../src/main/java/com/horcrux/svg/GroupShadowNode.java | 2 -- .../java/com/horcrux/svg/RenderableShadowNode.java | 10 ++-------- .../main/java/com/horcrux/svg/SvgViewShadowNode.java | 3 +-- android/src/main/java/com/horcrux/svg/VirtualNode.java | 3 ++- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java index 5eadcd22..b8956b74 100644 --- a/android/src/main/java/com/horcrux/svg/GroupShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java @@ -10,10 +10,8 @@ package com.horcrux.svg; import android.graphics.Canvas; -import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; -import android.graphics.Point; import android.graphics.RectF; import com.facebook.react.bridge.ReadableMap; diff --git a/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java b/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java index 594b9a80..18d9fa1b 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java @@ -9,19 +9,12 @@ package com.horcrux.svg; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - import android.graphics.Canvas; import android.graphics.DashPathEffect; -import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; -import android.graphics.Point; import android.graphics.RectF; import android.graphics.Region; -import android.os.Build; -import android.util.Log; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; @@ -34,7 +27,8 @@ import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; -import static android.content.ContentValues.TAG; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * Renderable shadow node diff --git a/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java b/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java index 0495683c..a90e10e1 100644 --- a/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java @@ -49,7 +49,6 @@ public class SvgViewShadowNode extends LayoutShadowNode { private String mbbHeight; private String mAlign; private int mMeetOrSlice; - private Matrix mViewBoxMatrix = new Matrix(); private Matrix mInvViewBoxMatrix = new Matrix(); private boolean mInvertible = true; @@ -156,7 +155,7 @@ public class SvgViewShadowNode extends LayoutShadowNode { if (nested) { canvas.clipRect(eRect); } - mViewBoxMatrix = ViewBox.getTransform(vbRect, eRect, mAlign, mMeetOrSlice); + Matrix mViewBoxMatrix = ViewBox.getTransform(vbRect, eRect, mAlign, mMeetOrSlice); mInvertible = mViewBoxMatrix.invert(mInvViewBoxMatrix); canvas.concat(mViewBoxMatrix); } diff --git a/android/src/main/java/com/horcrux/svg/VirtualNode.java b/android/src/main/java/com/horcrux/svg/VirtualNode.java index dd0c3952..768e17a7 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/VirtualNode.java @@ -13,7 +13,6 @@ import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; -import android.graphics.Point; import android.graphics.RectF; import android.graphics.Region; @@ -90,6 +89,8 @@ abstract class VirtualNode extends LayoutShadowNode { @Override public void markUpdated() { super.markUpdated(); + canvasHeight = -1; + canvasWidth = -1; mRegion = null; mPath = null; mBox = null; From 8ff052ce40773918ab42b46bbe5300b1acda7a27 Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Fri, 23 Feb 2018 01:38:41 +0200 Subject: [PATCH 10/10] Optimize group hitTest --- ios/Elements/RNSVGGroup.m | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ios/Elements/RNSVGGroup.m b/ios/Elements/RNSVGGroup.m index 5b1435a5..c926cbed 100644 --- a/ios/Elements/RNSVGGroup.m +++ b/ios/Elements/RNSVGGroup.m @@ -111,7 +111,7 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { - CGPoint transformed = CGPointApplyAffineTransform(point, CGAffineTransformInvert(self.matrix)); + CGPoint transformed = CGPointApplyAffineTransform(point, self.invmatrix); UIView *hitSelf = [super hitTest:transformed withEvent:event]; if (hitSelf) { @@ -119,11 +119,8 @@ } CGPathRef clip = [self getClipPath]; - if (clip) { - BOOL insideClipPath = CGPathContainsPoint(clip, nil, transformed, self.clipRule == kRNSVGCGFCRuleEvenodd); - if (!insideClipPath) { - return nil; - } + if (clip && !CGPathContainsPoint(clip, nil, transformed, self.clipRule == kRNSVGCGFCRuleEvenodd)) { + return nil; } for (RNSVGNode *node in [self.subviews reverseObjectEnumerator]) {