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.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();
}
}
});
}

View File

@@ -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,7 +73,9 @@ class GroupShadowNode extends RenderableShadowNode {
final SvgViewShadowNode svg = getSvgShadowNode();
final GroupShadowNode self = this;
traverseChildren(new NodeRunnable() {
public void run(VirtualNode node) {
public void run(LayoutShadowNode lNode) {
if (lNode instanceof VirtualNode) {
VirtualNode node = ((VirtualNode)lNode);
if (node instanceof RenderableShadowNode) {
((RenderableShadowNode)node).mergeProperties(self);
}
@@ -90,6 +93,10 @@ class GroupShadowNode extends RenderableShadowNode {
if (node.isResponsible()) {
svg.enableTouchEvents();
}
} else if (lNode instanceof SvgViewShadowNode) {
SvgViewShadowNode svgView = (SvgViewShadowNode)lNode;
svgView.drawChildren(canvas);
}
}
});
popGlyphContext();
@@ -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();
}

View File

@@ -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,13 +166,17 @@ 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) {
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);
@@ -159,6 +186,7 @@ public class SvgViewShadowNode extends LayoutShadowNode {
mResponsible = true;
}
}
}
});
}
@@ -238,7 +266,7 @@ public class SvgViewShadowNode extends LayoutShadowNode {
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.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();
}
}
});
}

View File

@@ -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);
}
}
}

View File

@@ -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 <NativeSvgView
{...props}
bbWidth={w}
bbHeight={h}
{...extractViewBox({ viewBox, preserveAspectRatio })}
ref={ele => {this.root = ele;}}
style={[
@@ -98,7 +103,9 @@ class Svg extends Component{
const NativeSvgView = requireNativeComponent('RNSVGSvgView', null, {
nativeOnly: {
...ViewBoxAttributes
...ViewBoxAttributes,
width: true,
height: true,
}
});

View File

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

View File

@@ -34,8 +34,10 @@
{
[self pushGlyphContext];
RNSVGSvgView* svg = [self getSvgView];
[self traverseSubviews:^(RNSVGNode *node) {
if (node.responsible && !svg.responsible) {
[self traverseSubviews:^(UIView *node) {
if ([node isKindOfClass:[RNSVGNode class]]) {
RNSVGNode* svgNode = (RNSVGNode*)node;
if (svgNode.responsible && !svg.responsible) {
svg.responsible = YES;
}
@@ -43,11 +45,19 @@
[(RNSVGRenderable*)node mergeProperties:self];
}
[node renderTo:context];
[svgNode renderTo:context];
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) {
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) {
if ([node isKindOfClass:[RNSVGNode class]]) {
[node parseReference];
}
return YES;
}];
}

View File

@@ -15,6 +15,8 @@
@interface RNSVGSvgView : UIView <RNSVGContainer>
@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 <ClipPath></ClipPath> content as clipPath template.
@@ -42,4 +46,8 @@
- (CGRect)getContextBounds;
- (void)drawRect:(CGRect)rect;
- (void)drawToContext:(CGContextRef)context withRect:(CGRect)rect;
@end

View File

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