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/DefsShadowNode.java b/android/src/main/java/com/horcrux/svg/DefsShadowNode.java index 8e4fc42e..8cd720a0 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.ReactShadowNode; + /** * 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(ReactShadowNode 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(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 3f0bccea..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; @@ -72,23 +70,31 @@ 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(ReactShadowNode 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); + } else { + lNode.calculateLayout(); } } }); @@ -104,8 +110,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(ReactShadowNode node) { + if (node instanceof VirtualNode) { + path.addPath(((VirtualNode)node).getPath(canvas, paint)); + } } }); @@ -113,22 +121,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--) { @@ -139,7 +151,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(); } @@ -154,8 +166,10 @@ class GroupShadowNode extends RenderableShadowNode { } traverseChildren(new NodeRunnable() { - public void run(VirtualNode node) { - node.saveDefinition(); + public void run(ReactShadowNode node) { + if (node instanceof VirtualNode) { + ((VirtualNode)node).saveDefinition(); + } } }); } @@ -163,7 +177,7 @@ class GroupShadowNode extends RenderableShadowNode { @Override public void resetProperties() { traverseChildren(new NodeRunnable() { - public void run(VirtualNode node) { + public void run(ReactShadowNode node) { if (node instanceof RenderableShadowNode) { ((RenderableShadowNode)node).resetProperties(); } diff --git a/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java b/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java index 886bf557..18d9fa1b 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java @@ -9,15 +9,10 @@ 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; @@ -32,6 +27,9 @@ import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + /** * Renderable shadow node */ @@ -67,8 +65,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; @@ -99,7 +95,6 @@ abstract public class RenderableShadowNode extends VirtualNode { "fillRule " + mFillRule + " unrecognized"); } - mPath = null; markUpdated(); } @@ -204,10 +199,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); } @@ -263,7 +261,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) { @@ -274,55 +271,67 @@ 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); + if (mBox == null) { + mBox = new RectF(); + mPath.computeBounds(mBox, true); + } + 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 == null) { + mRegion = getRegion(mPath); + } + 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/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 4ba7b99d..21d91127 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.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; /** - * 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 { + @SuppressWarnings("unused") public enum Events { @SuppressWarnings("unused") EVENT_DATA_URL("onDataURL"); @@ -100,6 +104,31 @@ public class SvgView extends View { return super.dispatchTouchEvent(ev); } + @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) { + int id = child.getId(); + for (int j = 0; j < node.getChildCount(); j++) { + ReactShadowNodeImpl nodeChild = node.getChildAt(j); + if (nodeChild.getReactTag() != id) { + 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..150e96e2 100644 --- a/android/src/main/java/com/horcrux/svg/SvgViewManager.java +++ b/android/src/main/java/com/horcrux/svg/SvgViewManager.java @@ -12,11 +12,11 @@ package com.horcrux.svg; import android.graphics.Bitmap; import android.util.SparseArray; -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; @@ -24,7 +24,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/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 808ef4ab..a90e10e1 100644 --- a/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java @@ -45,9 +45,12 @@ 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; + private Matrix mInvViewBoxMatrix = new Matrix(); + private boolean mInvertible = true; public SvgViewShadowNode() { mScale = DisplayMetricsHolder.getScreenDisplayMetrics().density; @@ -77,6 +80,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; @@ -96,7 +111,7 @@ public class SvgViewShadowNode extends LayoutShadowNode { @Override public boolean isVirtualAnchor() { - return true; + return false; } @Override @@ -117,8 +132,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,12 +140,23 @@ 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()); - mViewBoxMatrix = ViewBox.getTransform(vbRect, eRect, mAlign, mMeetOrSlice); + 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); + } + Matrix mViewBoxMatrix = ViewBox.getTransform(vbRect, eRect, mAlign, mMeetOrSlice); + mInvertible = mViewBoxMatrix.invert(mInvViewBoxMatrix); canvas.concat(mViewBoxMatrix); } @@ -143,20 +168,27 @@ public class SvgViewShadowNode extends LayoutShadowNode { traverseChildren(new VirtualNode.NodeRunnable() { - public void run(VirtualNode node) { - node.saveDefinition(); + public void run(ReactShadowNode 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(ReactShadowNode 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; + } + } else { + lNode.calculateLayout(); } } }); @@ -187,10 +219,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--) { @@ -198,7 +233,7 @@ public class SvgViewShadowNode extends LayoutShadowNode { continue; } - viewTag = ((VirtualNode) getChildAt(i)).hitTest(point, mViewBoxMatrix); + viewTag = ((VirtualNode) getChildAt(i)).hitTest(transformed); if (viewTag != -1) { break; } @@ -234,11 +269,7 @@ 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((VirtualNode) child); + runner.run(child); } } } 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/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index aa539667..9b4225cf 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -26,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; @@ -177,9 +177,10 @@ class TextShadowNode extends GroupShadowNode { void releaseCachedPath() { traverseChildren(new NodeRunnable() { - public void run(VirtualNode node) { - TextShadowNode text = (TextShadowNode)node; - text.releaseCachedPath(); + 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 4af0085d..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,7 @@ 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; import com.facebook.common.logging.FLog; @@ -45,6 +45,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; @@ -63,7 +65,14 @@ abstract class VirtualNode extends LayoutShadowNode { private float canvasWidth = -1; private GlyphContext glyphContext; + Path mPath; + RectF mBox; + Region mRegion; + Region mClipRegion; + Path mClipRegionPath; + VirtualNode() { + setIsLayoutOnly(true); mScale = DisplayMetricsHolder.getScreenDisplayMetrics().density; } @@ -72,6 +81,21 @@ abstract class VirtualNode extends LayoutShadowNode { return true; } + @Override + public boolean isVirtualAnchor() { + return true; + } + + @Override + public void markUpdated() { + super.markUpdated(); + canvasHeight = -1; + canvasWidth = -1; + mRegion = null; + mPath = null; + mBox = null; + } + @Nullable GroupShadowNode getTextRoot() { VirtualNode node = this; @@ -155,6 +179,7 @@ abstract class VirtualNode extends LayoutShadowNode { @ReactProp(name = "clipPath") public void setClipPath(String clipPath) { + mCachedClipPath = null; mClipPath = clipPath; markUpdated(); } @@ -178,16 +203,19 @@ 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; } - markUpdated(); + super.markUpdated(); } @ReactProp(name = "responsible") @@ -201,7 +229,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) { @@ -232,7 +260,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; @@ -308,17 +336,13 @@ abstract class VirtualNode extends LayoutShadowNode { } interface NodeRunnable { - void run(VirtualNode node); + void run(ReactShadowNode node); } void traverseChildren(NodeRunnable runner) { for (int i = 0; i < getChildCount(); i++) { ReactShadowNode child = getChildAt(i); - if (!(child instanceof VirtualNode)) { - continue; - } - - runner.run((VirtualNode) child); + runner.run(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.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 e8786836..14138de2 100644 --- a/ios/Elements/RNSVGGroup.m +++ b/ios/Elements/RNSVGGroup.m @@ -23,30 +23,40 @@ _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]; - - [self traverseSubviews:^(RNSVGNode *node) { - if (node.responsible && !self.svgView.responsible) { - self.svgView.responsible = YES; - } - if ([node isKindOfClass:[RNSVGRenderable class]]) { - [(RNSVGRenderable*)node mergeProperties:self]; - } + [self traverseSubviews:^(UIView *node) { + if ([node isKindOfClass:[RNSVGNode class]]) { + RNSVGNode* svgNode = (RNSVGNode*)node; + if (svgNode.responsible && !self.svgView.responsible) { + self.svgView.responsible = YES; + } - [node renderTo:context]; + if ([node isKindOfClass:[RNSVGRenderable class]]) { + [(RNSVGRenderable*)node mergeProperties:self]; + } - if ([node isKindOfClass:[RNSVGRenderable class]]) { - [(RNSVGRenderable*)node resetProperties]; + [svgNode renderTo:context rect:rect]; + + 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 { + [node drawRect:rect]; } return YES; @@ -81,42 +91,37 @@ [[self.textRoot 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 { 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; }]; 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, self.invmatrix); + + 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); - - if (!insideClipPath) { - return nil; - } - + if (clip && !CGPathContainsPoint(clip, nil, transformed, self.clipRule == kRNSVGCGFCRuleEvenodd)) { + return nil; } for (RNSVGNode *node in [self.subviews reverseObjectEnumerator]) { @@ -130,13 +135,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; } @@ -148,7 +154,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 5a50797c..17045ec4 100644 --- a/ios/Elements/RNSVGSvgView.m +++ b/ios/Elements/RNSVGSvgView.m @@ -16,8 +16,8 @@ NSMutableDictionary *_clipPaths; NSMutableDictionary *_templates; NSMutableDictionary *_painters; - CGRect _boundingBox; CGAffineTransform _viewBoxTransform; + CGAffineTransform _invviewBoxTransform; } - (instancetype)initWithFrame:(CGRect)frame @@ -58,7 +58,7 @@ if (minX == _minX) { return; } - + [self invalidate]; _minX = minX; } @@ -68,7 +68,7 @@ if (minY == _minY) { return; } - + [self invalidate]; _minY = minY; } @@ -78,7 +78,7 @@ if (vbWidth == _vbWidth) { return; } - + [self invalidate]; _vbWidth = vbWidth; } @@ -88,17 +88,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; } @@ -108,11 +128,32 @@ 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]; + _invviewBoxTransform = CGAffineTransformInvert(_viewBoxTransform); + CGContextConcatCTM(context, _viewBoxTransform); + } + + for (UIView *node in self.subviews) { + if ([node isKindOfClass:[RNSVGNode class]]) { + RNSVGNode *svg = (RNSVGNode *)node; + [svg renderTo:context rect:rect]; + } else { + [node drawRect:rect]; + } + } +} + - (void)drawRect:(CGRect)rect { _clipPaths = nil; @@ -120,48 +161,38 @@ _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]; } } + + [self drawToContext:context withRect:rect]; } - (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; } - + if (event) { node.active = NO; } else if (node.active) { return node; } - - UIView *hitChild = [node hitTest: point withEvent:event withTransform:_viewBoxTransform]; - + + UIView *hitChild = [node hitTest:transformed withEvent:event]; + if (hitChild) { node.active = YES; return (node.responsible || (node != hitChild)) ? hitChild : self; 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 0a8c4e1d..ab2f4af7 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.svgView 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/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 7b5de41e..b0762d47 100644 --- a/ios/RNSVGNode.h +++ b/ios/RNSVGNode.h @@ -32,7 +32,9 @@ 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; /** * RNSVGSvgView which ownes current RNSVGNode @@ -44,14 +46,14 @@ extern CGFloat const RNSVG_DEFAULT_FONT_SIZE; - (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 @@ -73,11 +75,6 @@ extern CGFloat const RNSVG_DEFAULT_FONT_SIZE; */ - (CGPathRef)getPath:(CGContextRef) context; -/** - * run hitTest - */ -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event withTransform:(CGAffineTransform)transfrom; - - (CGFloat)relativeOnWidth:(NSString *)length; - (CGFloat)relativeOnHeight:(NSString *)length; @@ -103,6 +100,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 824c0b04..34609503 100644 --- a/ios/RNSVGNode.m +++ b/ios/RNSVGNode.m @@ -57,6 +57,10 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; { id container = (id)self.superview; [container invalidate]; + if (_path) { + CGPathRelease(_path); + _path = nil; + } } - (RNSVGGroup *)textRoot @@ -64,16 +68,16 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; if (_textRoot) { return _textRoot; } - + RNSVGNode* node = self; while (node != nil) { if ([node isKindOfClass:[RNSVGGroup class]] && [((RNSVGGroup*) node) getGlyphContext] != nil) { _textRoot = (RNSVGGroup*)node; break; } - + UIView* parent = [node superview]; - + if (![node isKindOfClass:[RNSVGNode class]]) { node = nil; } else { @@ -135,8 +139,10 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; if (CGAffineTransformEqualToTransform(matrix, _matrix)) { return; } - [self invalidate]; _matrix = matrix; + _invmatrix = CGAffineTransformInvert(matrix); + id container = (id)self.superview; + [container invalidate]; } - (void)setClipPath:(NSString *)clipPath @@ -164,7 +170,7 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; } } -- (void)renderTo:(CGContextRef)context +- (void)renderTo:(CGContextRef)context rect:(CGRect)rect { // abstract } @@ -176,8 +182,7 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; - (CGPathRef)getClipPath:(CGContextRef)context { - if (self.clipPath) { - CGPathRelease(_cachedClipPath); + if (self.clipPath && !_cachedClipPath) { _cachedClipPath = CGPathRetain([[self.svgView getDefinedClipPath:self.clipPath] getPath:context]); } @@ -204,7 +209,7 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; return nil; } -- (void)renderLayerTo:(CGContextRef)context +- (void)renderLayerTo:(CGContextRef)context rect:(CGRect)rect { // abstract } @@ -217,12 +222,6 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; return nil; } -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event withTransform:(CGAffineTransform)transfrom -{ - // abstract - return nil; -} - - (RNSVGSvgView *)svgView { if (_svgView) { @@ -314,13 +313,11 @@ 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) { - if ([node isKindOfClass:[RNSVGNode class]]) { - if (!block(node)) { - break; - } + for (UIView *node in self.subviews) { + if (!block(node)) { + break; } } } diff --git a/ios/RNSVGRenderable.m b/ios/RNSVGRenderable.m index 5c1a30f2..74414460 100644 --- a/ios/RNSVGRenderable.m +++ b/ios/RNSVGRenderable.m @@ -151,13 +151,14 @@ - (void)dealloc { + CGPathRelease(self.path); CGPathRelease(_hitArea); if (_strokeDasharrayData.array) { free(_strokeDasharrayData.array); } } -- (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,26 +166,28 @@ 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; } - CGPathRef path = [self getPath:context]; - [self setHitArea:path]; - if (self.opacity == 0) { return; } + if (!self.path) { + self.path = CGPathRetain(CFAutorelease(CGPathCreateCopy([self getPath:context]))); + [self setHitArea:self.path]; + } + CGPathDrawingMode mode = kCGPathStroke; BOOL fillColor = NO; [self clip:context]; @@ -198,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 @@ -224,7 +227,7 @@ } if (!fillColor) { - CGContextAddPath(context, path); + CGContextAddPath(context, self.path); CGContextReplacePathWithStrokedPath(context); CGContextClip(context); } @@ -236,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); @@ -253,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); @@ -280,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; @@ -297,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 diff --git a/ios/Text/RNSVGTSpan.m b/ios/Text/RNSVGTSpan.m index 3dd8b08b..d15e34c5 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 4aca896d..eda5ee3f 100644 --- a/ios/Text/RNSVGText.m +++ b/ios/Text/RNSVGText.m @@ -18,14 +18,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); @@ -68,10 +68,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 e2236a91..939dc865 100644 --- a/ios/Text/RNSVGTextPath.m +++ b/ios/Text/RNSVGTextPath.m @@ -208,9 +208,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 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)