First attempt at nested svg support.

This commit is contained in:
Mikael Sand
2018-02-06 11:18:18 +02:00
parent 824a181a3d
commit ee1d1f78cc
13 changed files with 208 additions and 92 deletions

View File

@@ -12,6 +12,8 @@ package com.horcrux.svg;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Paint; import android.graphics.Paint;
import com.facebook.react.uimanager.LayoutShadowNode;
/** /**
* Shadow node for virtual Defs view * Shadow node for virtual Defs view
*/ */
@@ -20,9 +22,13 @@ class DefsShadowNode extends DefinitionShadowNode {
@Override @Override
public void draw(Canvas canvas, Paint paint, float opacity) { public void draw(Canvas canvas, Paint paint, float opacity) {
NodeRunnable markUpdateSeenRecursive = new NodeRunnable() { NodeRunnable markUpdateSeenRecursive = new NodeRunnable() {
public void run(VirtualNode node) { public void run(LayoutShadowNode node) {
node.markUpdateSeen(); node.markUpdateSeen();
node.traverseChildren(this); if (node instanceof VirtualNode) {
((VirtualNode) node).traverseChildren(this);
} else if (node instanceof SvgViewShadowNode) {
((SvgViewShadowNode) node).traverseChildren(this);
}
} }
}; };
traverseChildren(markUpdateSeenRecursive); traverseChildren(markUpdateSeenRecursive);
@@ -30,8 +36,10 @@ class DefsShadowNode extends DefinitionShadowNode {
void saveDefinition() { void saveDefinition() {
traverseChildren(new NodeRunnable() { traverseChildren(new NodeRunnable() {
public void run(VirtualNode node) { public void run(LayoutShadowNode node) {
node.saveDefinition(); if (node instanceof VirtualNode) {
((VirtualNode)node).saveDefinition();
}
} }
}); });
} }

View File

@@ -17,6 +17,7 @@ import android.graphics.Point;
import android.graphics.RectF; import android.graphics.RectF;
import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.LayoutShadowNode;
import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.ReactShadowNode;
import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.annotations.ReactProp;
@@ -72,23 +73,29 @@ class GroupShadowNode extends RenderableShadowNode {
final SvgViewShadowNode svg = getSvgShadowNode(); final SvgViewShadowNode svg = getSvgShadowNode();
final GroupShadowNode self = this; final GroupShadowNode self = this;
traverseChildren(new NodeRunnable() { traverseChildren(new NodeRunnable() {
public void run(VirtualNode node) { public void run(LayoutShadowNode lNode) {
if (node instanceof RenderableShadowNode) { if (lNode instanceof VirtualNode) {
((RenderableShadowNode)node).mergeProperties(self); VirtualNode node = ((VirtualNode)lNode);
} if (node instanceof RenderableShadowNode) {
((RenderableShadowNode)node).mergeProperties(self);
}
int count = node.saveAndSetupCanvas(canvas); int count = node.saveAndSetupCanvas(canvas);
node.draw(canvas, paint, opacity * mOpacity); node.draw(canvas, paint, opacity * mOpacity);
node.restoreCanvas(canvas, count); node.restoreCanvas(canvas, count);
if (node instanceof RenderableShadowNode) { if (node instanceof RenderableShadowNode) {
((RenderableShadowNode)node).resetProperties(); ((RenderableShadowNode)node).resetProperties();
} }
node.markUpdateSeen(); node.markUpdateSeen();
if (node.isResponsible()) { if (node.isResponsible()) {
svg.enableTouchEvents(); 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(); final Path path = new Path();
traverseChildren(new NodeRunnable() { traverseChildren(new NodeRunnable() {
public void run(VirtualNode node) { public void run(LayoutShadowNode node) {
path.addPath(node.getPath(canvas, paint)); if (node instanceof VirtualNode) {
path.addPath(((VirtualNode)node).getPath(canvas, paint));
}
} }
}); });
@@ -154,8 +163,10 @@ class GroupShadowNode extends RenderableShadowNode {
} }
traverseChildren(new NodeRunnable() { traverseChildren(new NodeRunnable() {
public void run(VirtualNode node) { public void run(LayoutShadowNode node) {
node.saveDefinition(); if (node instanceof VirtualNode) {
((VirtualNode)node).saveDefinition();
}
} }
}); });
} }
@@ -163,7 +174,7 @@ class GroupShadowNode extends RenderableShadowNode {
@Override @Override
public void resetProperties() { public void resetProperties() {
traverseChildren(new NodeRunnable() { traverseChildren(new NodeRunnable() {
public void run(VirtualNode node) { public void run(LayoutShadowNode node) {
if (node instanceof RenderableShadowNode) { if (node instanceof RenderableShadowNode) {
((RenderableShadowNode)node).resetProperties(); ((RenderableShadowNode)node).resetProperties();
} }

View File

@@ -45,6 +45,8 @@ public class SvgViewShadowNode extends LayoutShadowNode {
private float mMinY; private float mMinY;
private float mVbWidth; private float mVbWidth;
private float mVbHeight; private float mVbHeight;
private String mbbWidth;
private String mbbHeight;
private String mAlign; private String mAlign;
private int mMeetOrSlice; private int mMeetOrSlice;
private Matrix mViewBoxMatrix; private Matrix mViewBoxMatrix;
@@ -77,6 +79,18 @@ public class SvgViewShadowNode extends LayoutShadowNode {
markUpdated(); 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") @ReactProp(name = "align")
public void setAlign(String align) { public void setAlign(String align) {
mAlign = align; mAlign = align;
@@ -117,8 +131,7 @@ public class SvgViewShadowNode extends LayoutShadowNode {
(int) getLayoutHeight(), (int) getLayoutHeight(),
Bitmap.Config.ARGB_8888); Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(bitmap); drawChildren(new Canvas(bitmap));
drawChildren(mCanvas);
return bitmap; return bitmap;
} }
@@ -126,11 +139,21 @@ public class SvgViewShadowNode extends LayoutShadowNode {
return mCanvas.getClipBounds(); return mCanvas.getClipBounds();
} }
private void drawChildren(final Canvas canvas) { void drawChildren(final Canvas canvas) {
mCanvas = canvas;
if (mAlign != null) { if (mAlign != null) {
RectF vbRect = getViewBox(); 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); mViewBoxMatrix = ViewBox.getTransform(vbRect, eRect, mAlign, mMeetOrSlice);
canvas.concat(mViewBoxMatrix); canvas.concat(mViewBoxMatrix);
} }
@@ -143,20 +166,25 @@ public class SvgViewShadowNode extends LayoutShadowNode {
traverseChildren(new VirtualNode.NodeRunnable() { traverseChildren(new VirtualNode.NodeRunnable() {
public void run(VirtualNode node) { public void run(LayoutShadowNode node) {
node.saveDefinition(); if (node instanceof VirtualNode) {
((VirtualNode)node).saveDefinition();
}
} }
}); });
traverseChildren(new VirtualNode.NodeRunnable() { traverseChildren(new VirtualNode.NodeRunnable() {
public void run(VirtualNode node) { public void run(LayoutShadowNode lNode) {
int count = node.saveAndSetupCanvas(canvas); if (lNode instanceof VirtualNode) {
node.draw(canvas, paint, 1f); VirtualNode node = (VirtualNode)lNode;
node.restoreCanvas(canvas, count); int count = node.saveAndSetupCanvas(canvas);
node.markUpdateSeen(); node.draw(canvas, paint, 1f);
node.restoreCanvas(canvas, count);
node.markUpdateSeen();
if (node.isResponsible() && !mResponsible) { if (node.isResponsible() && !mResponsible) {
mResponsible = true; mResponsible = true;
}
} }
} }
}); });
@@ -238,7 +266,7 @@ public class SvgViewShadowNode extends LayoutShadowNode {
continue; continue;
} }
runner.run((VirtualNode) child); runner.run((LayoutShadowNode) child);
} }
} }
} }

View File

@@ -15,6 +15,7 @@ import android.graphics.Path;
import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.LayoutShadowNode;
import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.ReactShadowNode;
import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.annotations.ReactProp;
@@ -177,9 +178,10 @@ class TextShadowNode extends GroupShadowNode {
void releaseCachedPath() { void releaseCachedPath() {
traverseChildren(new NodeRunnable() { traverseChildren(new NodeRunnable() {
public void run(VirtualNode node) { public void run(LayoutShadowNode node) {
TextShadowNode text = (TextShadowNode)node; if (node instanceof TextShadowNode) {
text.releaseCachedPath(); ((TextShadowNode)node).releaseCachedPath();
}
} }
}); });
} }

View File

@@ -308,17 +308,17 @@ abstract class VirtualNode extends LayoutShadowNode {
} }
interface NodeRunnable { interface NodeRunnable {
void run(VirtualNode node); void run(LayoutShadowNode node);
} }
void traverseChildren(NodeRunnable runner) { void traverseChildren(NodeRunnable runner) {
for (int i = 0; i < getChildCount(); i++) { for (int i = 0; i < getChildCount(); i++) {
ReactShadowNode child = getChildAt(i); ReactShadowNode child = getChildAt(i);
if (!(child instanceof VirtualNode)) { if (!(child instanceof VirtualNode) && !(child instanceof SvgViewShadowNode)) {
continue; continue;
} }
runner.run((VirtualNode) child); runner.run((LayoutShadowNode) child);
} }
} }
} }

View File

@@ -74,14 +74,19 @@ class Svg extends Component{
if (width && height) { if (width && height) {
dimensions = { dimensions = {
width: +width, width: width[width.length - 1] === '%' ? width : +width,
height: +height, height: height[height.length - 1] === '%' ? height : +height,
flex: 0 flex: 0
}; };
} }
const w = `${width}`;
const h = `${height}`;
return <NativeSvgView return <NativeSvgView
{...props} {...props}
bbWidth={w}
bbHeight={h}
{...extractViewBox({ viewBox, preserveAspectRatio })} {...extractViewBox({ viewBox, preserveAspectRatio })}
ref={ele => {this.root = ele;}} ref={ele => {this.root = ele;}}
style={[ style={[
@@ -98,7 +103,9 @@ class Svg extends Component{
const NativeSvgView = requireNativeComponent('RNSVGSvgView', null, { const NativeSvgView = requireNativeComponent('RNSVGSvgView', null, {
nativeOnly: { nativeOnly: {
...ViewBoxAttributes ...ViewBoxAttributes,
width: true,
height: true,
} }
}); });

View File

@@ -19,7 +19,9 @@
- (void)parseReference - (void)parseReference
{ {
[self traverseSubviews:^(RNSVGNode *node) { [self traverseSubviews:^(RNSVGNode *node) {
[node parseReference]; if ([node isKindOfClass:[RNSVGNode class]]) {
[node parseReference];
}
return YES; return YES;
}]; }];
} }

View File

@@ -34,19 +34,29 @@
{ {
[self pushGlyphContext]; [self pushGlyphContext];
RNSVGSvgView* svg = [self getSvgView]; RNSVGSvgView* svg = [self getSvgView];
[self traverseSubviews:^(RNSVGNode *node) { [self traverseSubviews:^(UIView *node) {
if (node.responsible && !svg.responsible) { if ([node isKindOfClass:[RNSVGNode class]]) {
svg.responsible = YES; RNSVGNode* svgNode = (RNSVGNode*)node;
} if (svgNode.responsible && !svg.responsible) {
svg.responsible = YES;
}
if ([node isKindOfClass:[RNSVGRenderable class]]) { if ([node isKindOfClass:[RNSVGRenderable class]]) {
[(RNSVGRenderable*)node mergeProperties:self]; [(RNSVGRenderable*)node mergeProperties:self];
} }
[node renderTo:context]; [svgNode renderTo:context];
if ([node isKindOfClass:[RNSVGRenderable class]]) { if ([node isKindOfClass:[RNSVGRenderable class]]) {
[(RNSVGRenderable*)node resetProperties]; [(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; return YES;
@@ -89,8 +99,10 @@
{ {
CGMutablePathRef __block path = CGPathCreateMutable(); CGMutablePathRef __block path = CGPathCreateMutable();
[self traverseSubviews:^(RNSVGNode *node) { [self traverseSubviews:^(RNSVGNode *node) {
CGAffineTransform transform = node.matrix; if ([node isKindOfClass:[RNSVGNode class]]) {
CGPathAddPath(path, &transform, [node getPath:context]); CGAffineTransform transform = node.matrix;
CGPathAddPath(path, &transform, [node getPath:context]);
}
return YES; return YES;
}]; }];
@@ -147,7 +159,9 @@
} }
[self traverseSubviews:^(__kindof RNSVGNode *node) { [self traverseSubviews:^(__kindof RNSVGNode *node) {
[node parseReference]; if ([node isKindOfClass:[RNSVGNode class]]) {
[node parseReference];
}
return YES; return YES;
}]; }];
} }

View File

@@ -15,6 +15,8 @@
@interface RNSVGSvgView : UIView <RNSVGContainer> @interface RNSVGSvgView : UIView <RNSVGContainer>
@property (nonatomic, strong) NSString *bbWidth;
@property (nonatomic, strong) NSString *bbHeight;
@property (nonatomic, assign) CGFloat minX; @property (nonatomic, assign) CGFloat minX;
@property (nonatomic, assign) CGFloat minY; @property (nonatomic, assign) CGFloat minY;
@property (nonatomic, assign) CGFloat vbWidth; @property (nonatomic, assign) CGFloat vbWidth;
@@ -22,6 +24,8 @@
@property (nonatomic, strong) NSString *align; @property (nonatomic, strong) NSString *align;
@property (nonatomic, assign) RNSVGVBMOS meetOrSlice; @property (nonatomic, assign) RNSVGVBMOS meetOrSlice;
@property (nonatomic, assign) BOOL responsible; @property (nonatomic, assign) BOOL responsible;
@property (nonatomic, assign) CGRect boundingBox;
/** /**
* define <ClipPath></ClipPath> content as clipPath template. * define <ClipPath></ClipPath> content as clipPath template.
@@ -42,4 +46,8 @@
- (CGRect)getContextBounds; - (CGRect)getContextBounds;
- (void)drawRect:(CGRect)rect;
- (void)drawToContext:(CGContextRef)context withRect:(CGRect)rect;
@end @end

View File

@@ -16,7 +16,6 @@
NSMutableDictionary<NSString *, RNSVGNode *> *_clipPaths; NSMutableDictionary<NSString *, RNSVGNode *> *_clipPaths;
NSMutableDictionary<NSString *, RNSVGNode *> *_templates; NSMutableDictionary<NSString *, RNSVGNode *> *_templates;
NSMutableDictionary<NSString *, RNSVGPainter *> *_painters; NSMutableDictionary<NSString *, RNSVGPainter *> *_painters;
CGRect _boundingBox;
CGAffineTransform _viewBoxTransform; CGAffineTransform _viewBoxTransform;
} }
@@ -83,6 +82,26 @@
_vbHeight = vbHeight; _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 - (void)setAlign:(NSString *)align
{ {
if ([align isEqualToString:_align]) { if ([align isEqualToString:_align]) {
@@ -103,13 +122,7 @@
_meetOrSlice = meetOrSlice; _meetOrSlice = meetOrSlice;
} }
- (void)drawRect:(CGRect)rect - (void)drawToContext:(CGContextRef)context withRect:(CGRect)rect {
{
_clipPaths = nil;
_templates = nil;
_painters = nil;
_boundingBox = rect;
CGContextRef context = UIGraphicsGetCurrentContext();
if (self.align) { if (self.align) {
_viewBoxTransform = [RNSVGViewBox getTransform:CGRectMake(self.minX, self.minY, self.vbWidth, self.vbHeight) _viewBoxTransform = [RNSVGViewBox getTransform:CGRectMake(self.minX, self.minY, self.vbWidth, self.vbHeight)
@@ -119,21 +132,36 @@
CGContextConcatCTM(context, _viewBoxTransform); CGContextConcatCTM(context, _viewBoxTransform);
} }
for (RNSVGNode *node in self.subviews) { for (UIView *node in self.subviews) {
if ([node isKindOfClass:[RNSVGNode class]]) { if ([node isKindOfClass:[RNSVGNode class]]) {
if (node.responsible && !self.responsible) { RNSVGNode *svg = (RNSVGNode *)node;
[svg renderTo:context];
}
}
}
- (void)drawRect:(CGRect)rect
{
_clipPaths = nil;
_templates = nil;
_painters = nil;
_boundingBox = rect;
CGContextRef context = UIGraphicsGetCurrentContext();
for (UIView *node in self.subviews) {
if ([node isKindOfClass:[RNSVGNode class]]) {
RNSVGNode *svg = (RNSVGNode *)node;
if (svg.responsible && !self.responsible) {
self.responsible = YES; self.responsible = YES;
} }
[node parseReference]; [svg parseReference];
} else {
RCTLogWarn(@"Not a RNSVGNode: %@", node.class);
} }
} }
for (RNSVGNode *node in self.subviews) { [self drawToContext:context withRect:rect];
if ([node isKindOfClass:[RNSVGNode class]]) {
[node renderTo:context];
}
}
} }
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event

View File

@@ -103,6 +103,6 @@ extern CGFloat const RNSVG_DEFAULT_FONT_SIZE;
- (void)endTransparencyLayer:(CGContextRef)context; - (void)endTransparencyLayer:(CGContextRef)context;
- (void)traverseSubviews:(BOOL (^)(__kindof RNSVGNode *node))block; - (void)traverseSubviews:(BOOL (^)(__kindof UIView *node))block;
@end @end

View File

@@ -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 ([node isKindOfClass:[RNSVGNode class]]) {
if (!block(node)) { if (!block(node)) {
break; break;
} }
} else if ([node isKindOfClass:[RNSVGSvgView class]]) {
if (!block(node)) {
break;
}
} else {
RCTLogWarn(@"Not a RNSVGNode: %@", node.class);
} }
} }
} }

View File

@@ -20,6 +20,8 @@ RCT_EXPORT_MODULE()
return [RNSVGSvgView new]; 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(minX, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(minY, CGFloat) RCT_EXPORT_VIEW_PROPERTY(minY, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(vbWidth, CGFloat) RCT_EXPORT_VIEW_PROPERTY(vbWidth, CGFloat)