diff --git a/README.md b/README.md index 079553e7..25d5c48f 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,15 @@ ```bash npm install react-native-svg --save ``` - + + # NOTICE: + - react-native-svg >= 3.2.0 only supports react-native >= 0.29.0 - react-native-svg >= 4.2.0 only supports react-native >= 0.32.0 - react-native-svg >= 4.3.0 only supports react-native >= 0.33.0 - + - react-native-svg >= 4.4.0 only supports react-native >= 0.38.0 and react >= 15.4.0 + - react-native-svg >= 4.5.0 only supports react-native >= 0.40.0 and react >= 15.4.0 + 2. Link native code ```bash diff --git a/android/src/main/java/com/horcrux/svg/RNSVGCircleShadowNode.java b/android/src/main/java/com/horcrux/svg/CircleShadowNode.java similarity index 87% rename from android/src/main/java/com/horcrux/svg/RNSVGCircleShadowNode.java rename to android/src/main/java/com/horcrux/svg/CircleShadowNode.java index 4e56f84f..c3f3b9c7 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGCircleShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/CircleShadowNode.java @@ -19,7 +19,7 @@ import com.facebook.react.uimanager.annotations.ReactProp; /** * Shadow node for virtual RNSVGPath view */ -public class RNSVGCircleShadowNode extends RNSVGPathShadowNode { +public class CircleShadowNode extends RenderableShadowNode { private String mCx; private String mCy; @@ -43,12 +43,6 @@ public class RNSVGCircleShadowNode extends RNSVGPathShadowNode { markUpdated(); } - @Override - public void draw(Canvas canvas, Paint paint, float opacity) { - mPath = getPath(canvas, paint); - super.draw(canvas, paint, opacity); - } - @Override protected Path getPath(Canvas canvas, Paint paint) { Path path = new Path(); diff --git a/android/src/main/java/com/horcrux/svg/RNSVGClipPathShadowNode.java b/android/src/main/java/com/horcrux/svg/ClipPathShadowNode.java similarity index 81% rename from android/src/main/java/com/horcrux/svg/RNSVGClipPathShadowNode.java rename to android/src/main/java/com/horcrux/svg/ClipPathShadowNode.java index 4bb502ee..45348348 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGClipPathShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/ClipPathShadowNode.java @@ -21,7 +21,7 @@ import com.facebook.react.common.ReactConstants; /** * Shadow node for virtual RNSVGClipPath view */ -public class RNSVGClipPathShadowNode extends RNSVGGroupShadowNode { +public class ClipPathShadowNode extends GroupShadowNode { @Override public void draw(Canvas canvas, Paint paint, float opacity) { @@ -44,10 +44,10 @@ public class RNSVGClipPathShadowNode extends RNSVGGroupShadowNode { } @Override - public void mergeProperties(RNSVGVirtualNode target, ReadableArray mergeList, boolean inherited) {} + public void mergeProperties(VirtualNode target, ReadableArray mergeList, boolean inherited) {} @Override - public void mergeProperties(RNSVGVirtualNode target, ReadableArray mergeList) {} + public void mergeProperties(VirtualNode target, ReadableArray mergeList) {} @Override public void resetProperties() {} diff --git a/android/src/main/java/com/horcrux/svg/RNSVGDefinitionShadowNode.java b/android/src/main/java/com/horcrux/svg/DefinitionShadowNode.java similarity index 78% rename from android/src/main/java/com/horcrux/svg/RNSVGDefinitionShadowNode.java rename to android/src/main/java/com/horcrux/svg/DefinitionShadowNode.java index 846ed0ed..42227170 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGDefinitionShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/DefinitionShadowNode.java @@ -20,7 +20,7 @@ import com.facebook.react.bridge.ReadableArray; /** * Shadow node for virtual Definition type views */ -public class RNSVGDefinitionShadowNode extends RNSVGVirtualNode { +public class DefinitionShadowNode extends VirtualNode { public void draw(Canvas canvas, Paint paint, float opacity) {} @@ -40,10 +40,10 @@ public class RNSVGDefinitionShadowNode extends RNSVGVirtualNode { } @Override - public void mergeProperties(RNSVGVirtualNode target, ReadableArray mergeList, boolean inherited) {} + public void mergeProperties(VirtualNode target, ReadableArray mergeList, boolean inherited) {} @Override - public void mergeProperties(RNSVGVirtualNode target, ReadableArray mergeList) {} + public void mergeProperties(VirtualNode target, ReadableArray mergeList) {} @Override public void resetProperties() {} diff --git a/android/src/main/java/com/horcrux/svg/RNSVGDefsShadowNode.java b/android/src/main/java/com/horcrux/svg/DefsShadowNode.java similarity index 58% rename from android/src/main/java/com/horcrux/svg/RNSVGDefsShadowNode.java rename to android/src/main/java/com/horcrux/svg/DefsShadowNode.java index b2a104c7..0a1fc331 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGDefsShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/DefsShadowNode.java @@ -15,15 +15,23 @@ import android.graphics.Paint; /** * Shadow node for virtual RNSVGPath view */ -public class RNSVGDefsShadowNode extends RNSVGDefinitionShadowNode { +public class DefsShadowNode extends DefinitionShadowNode { @Override public void draw(Canvas canvas, Paint paint, float opacity) { traverseChildren(new NodeRunnable() { - public boolean run(RNSVGVirtualNode node) { + public boolean run(VirtualNode node) { node.saveDefinition(); return true; } }); + NodeRunnable markUpdateSeenRecursive = new NodeRunnable() { + public boolean run(VirtualNode node) { + node.markUpdateSeen(); + node.traverseChildren(this); + return true; + } + }; + traverseChildren(markUpdateSeenRecursive); } } diff --git a/android/src/main/java/com/horcrux/svg/RNSVGEllipseShadowNode.java b/android/src/main/java/com/horcrux/svg/EllipseShadowNode.java similarity index 87% rename from android/src/main/java/com/horcrux/svg/RNSVGEllipseShadowNode.java rename to android/src/main/java/com/horcrux/svg/EllipseShadowNode.java index e4734fe5..120922e4 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGEllipseShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/EllipseShadowNode.java @@ -19,7 +19,7 @@ import com.facebook.react.uimanager.annotations.ReactProp; /** * Shadow node for virtual RNSVGPath view */ -public class RNSVGEllipseShadowNode extends RNSVGPathShadowNode { +public class EllipseShadowNode extends RenderableShadowNode { private String mCx; private String mCy; @@ -50,12 +50,6 @@ public class RNSVGEllipseShadowNode extends RNSVGPathShadowNode { markUpdated(); } - @Override - public void draw(Canvas canvas, Paint paint, float opacity) { - mPath = getPath(canvas, paint); - super.draw(canvas, paint, opacity); - } - @Override protected Path getPath(Canvas canvas, Paint paint) { Path path = new Path(); diff --git a/android/src/main/java/com/horcrux/svg/RNSVGGroupShadowNode.java b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java similarity index 73% rename from android/src/main/java/com/horcrux/svg/RNSVGGroupShadowNode.java rename to android/src/main/java/com/horcrux/svg/GroupShadowNode.java index f9abab09..1e5f0d83 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGGroupShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/GroupShadowNode.java @@ -15,7 +15,6 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.Point; -import com.facebook.react.bridge.ReadableArray; import com.facebook.react.uimanager.ReactShadowNode; @@ -24,21 +23,21 @@ import javax.annotation.Nullable; /** * Shadow node for virtual RNSVGGroup view */ -public class RNSVGGroupShadowNode extends RNSVGPathShadowNode { +public class GroupShadowNode extends RenderableShadowNode { public void draw(final Canvas canvas, final Paint paint, final float opacity) { - final RNSVGSvgViewShadowNode svg = getSvgShadowNode(); - final RNSVGVirtualNode self = this; + final SvgViewShadowNode svg = getSvgShadowNode(); + final VirtualNode self = this; if (opacity > MIN_OPACITY_FOR_DRAW) { int count = saveAndSetupCanvas(canvas); clip(canvas, paint); traverseChildren(new NodeRunnable() { - public boolean run(RNSVGVirtualNode node) { + public boolean run(VirtualNode node) { node.setupDimensions(canvas); - node.mergeProperties(self, mOwnedPropList, true); + node.mergeProperties(self, mAttributeList, true); node.draw(canvas, paint, opacity * mOpacity); node.markUpdateSeen(); @@ -58,7 +57,7 @@ public class RNSVGGroupShadowNode extends RNSVGPathShadowNode { final Path path = new Path(); traverseChildren(new NodeRunnable() { - public boolean run(RNSVGVirtualNode node) { + public boolean run(VirtualNode node) { node.setupDimensions(canvas); path.addPath(node.getPath(canvas, paint)); return true; @@ -80,11 +79,11 @@ public class RNSVGGroupShadowNode extends RNSVGPathShadowNode { for (int i = getChildCount() - 1; i >= 0; i--) { ReactShadowNode child = getChildAt(i); - if (!(child instanceof RNSVGVirtualNode)) { + if (!(child instanceof VirtualNode)) { continue; } - RNSVGVirtualNode node = (RNSVGVirtualNode) child; + VirtualNode node = (VirtualNode) child; int viewTag = node.hitTest(point, combinedMatrix); if (viewTag != -1) { @@ -101,27 +100,17 @@ public class RNSVGGroupShadowNode extends RNSVGPathShadowNode { } traverseChildren(new NodeRunnable() { - public boolean run(RNSVGVirtualNode node) { + public boolean run(VirtualNode node) { node.saveDefinition(); return true; } }); } - @Override - public void mergeProperties(final RNSVGVirtualNode target, final ReadableArray mergeList) { - traverseChildren(new NodeRunnable() { - public boolean run(RNSVGVirtualNode node) { - node.mergeProperties(target, mergeList); - return true; - } - }); - } - @Override public void resetProperties() { traverseChildren(new NodeRunnable() { - public boolean run(RNSVGVirtualNode node) { + public boolean run(VirtualNode node) { node.resetProperties(); return true; } diff --git a/android/src/main/java/com/horcrux/svg/RNSVGImageShadowNode.java b/android/src/main/java/com/horcrux/svg/ImageShadowNode.java similarity index 93% rename from android/src/main/java/com/horcrux/svg/RNSVGImageShadowNode.java rename to android/src/main/java/com/horcrux/svg/ImageShadowNode.java index 47f57252..abb3437c 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGImageShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/ImageShadowNode.java @@ -41,7 +41,7 @@ import javax.annotation.Nullable; /** * Shadow node for virtual RNSVGPath view */ -public class RNSVGImageShadowNode extends RNSVGPathShadowNode { +public class ImageShadowNode extends RenderableShadowNode { private String mX; private String mY; @@ -107,8 +107,7 @@ public class RNSVGImageShadowNode extends RNSVGPathShadowNode { @Override public void draw(final Canvas canvas, final Paint paint, final float opacity) { - mPath = new Path(); - mPath.addRect(new RectF(getRect()), Path.Direction.CW); + mPath = getPath(canvas, paint); if (!mLoading.get()) { final ImageRequest request = ImageRequestBuilder.newBuilderWithSource(mUri).build(); @@ -116,12 +115,19 @@ public class RNSVGImageShadowNode extends RNSVGPathShadowNode { if (Fresco.getImagePipeline().isInBitmapMemoryCache(request)) { tryRender(request, canvas, paint, opacity * mOpacity); } else { - loadBitmap(request, canvas, paint); + loadBitmap(request); } } } - private void loadBitmap(ImageRequest request, final Canvas canvas, final Paint paint) { + @Override + protected Path getPath(Canvas canvas, Paint paint) { + Path path = new Path(); + path.addRect(new RectF(getRect()), Path.Direction.CW); + return path; + } + + private void loadBitmap(ImageRequest request) { final DataSource> dataSource = Fresco.getImagePipeline().fetchDecodedImage(request, getThemedContext()); @@ -129,7 +135,8 @@ public class RNSVGImageShadowNode extends RNSVGPathShadowNode { @Override public void onNewResultImpl(Bitmap bitmap) { mLoading.set(false); - getSvgShadowNode().drawOutput(); + SvgViewShadowNode shadowNode = getSvgShadowNode(); + shadowNode.markUpdated(); } @Override @@ -178,7 +185,7 @@ public class RNSVGImageShadowNode extends RNSVGPathShadowNode { renderRect = new RectF(0, 0, (int)rectWidth, (int)(rectWidth / mImageRatio)); } - RNSVGViewBoxShadowNode viewBox = new RNSVGViewBoxShadowNode(); + ViewBoxShadowNode viewBox = new ViewBoxShadowNode(); viewBox.setMinX("0"); viewBox.setMinY("0"); viewBox.setVbWidth(renderRect.width() / mScale + ""); diff --git a/android/src/main/java/com/horcrux/svg/RNSVGLineShadowNode.java b/android/src/main/java/com/horcrux/svg/LineShadowNode.java similarity index 86% rename from android/src/main/java/com/horcrux/svg/RNSVGLineShadowNode.java rename to android/src/main/java/com/horcrux/svg/LineShadowNode.java index 92899eaf..03470dd0 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGLineShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/LineShadowNode.java @@ -17,7 +17,7 @@ import com.facebook.react.uimanager.annotations.ReactProp; /** * Shadow node for virtual RNSVGPath view */ -public class RNSVGLineShadowNode extends RNSVGPathShadowNode { +public class LineShadowNode extends RenderableShadowNode { private String mX1; private String mY1; @@ -48,12 +48,6 @@ public class RNSVGLineShadowNode extends RNSVGPathShadowNode { markUpdated(); } - @Override - public void draw(Canvas canvas, Paint paint, float opacity) { - mPath = getPath(canvas, paint); - super.draw(canvas, paint, opacity); - } - @Override protected Path getPath(Canvas canvas, Paint paint) { Path path = new Path(); diff --git a/android/src/main/java/com/horcrux/svg/RNSVGLinearGradientShadowNode.java b/android/src/main/java/com/horcrux/svg/LinearGradientShadowNode.java similarity index 95% rename from android/src/main/java/com/horcrux/svg/RNSVGLinearGradientShadowNode.java rename to android/src/main/java/com/horcrux/svg/LinearGradientShadowNode.java index 314e435a..241c6d73 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGLinearGradientShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/LinearGradientShadowNode.java @@ -17,7 +17,7 @@ import com.facebook.react.uimanager.annotations.ReactProp; /** * Shadow node for virtual LinearGradient definition view */ -public class RNSVGLinearGradientShadowNode extends RNSVGDefinitionShadowNode { +public class LinearGradientShadowNode extends DefinitionShadowNode { private String mX1; private String mY1; diff --git a/android/src/main/java/com/horcrux/svg/PathShadowNode.java b/android/src/main/java/com/horcrux/svg/PathShadowNode.java new file mode 100644 index 00000000..ad0f48e0 --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/PathShadowNode.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2015-present, Horcrux. + * All rights reserved. + * + * This source code is licensed under the MIT-style license found in the + * LICENSE file in the root directory of this source tree. + */ + + +package com.horcrux.svg; + +import javax.annotation.Nullable; + +import android.graphics.Bitmap; +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.Color; +import android.util.Log; + +import com.facebook.common.logging.FLog; +import com.facebook.react.bridge.JSApplicationIllegalArgumentException; +import com.facebook.react.bridge.JavaOnlyArray; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.WritableArray; +import com.facebook.react.common.ReactConstants; +import com.facebook.react.uimanager.annotations.ReactProp; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Shadow node for virtual RNSVGPath view + */ +public class PathShadowNode extends RenderableShadowNode { + + private Path mPath; + + @ReactProp(name = "d") + public void setD(String d) { + PropHelper.PathParser parser = new PropHelper.PathParser(d, mScale); + mPath = parser.getPath(); + markUpdated(); + } + + @Override + protected Path getPath(Canvas canvas, Paint paint) { + return mPath; + } +} diff --git a/android/src/main/java/com/horcrux/svg/PropHelper.java b/android/src/main/java/com/horcrux/svg/PropHelper.java index 69965d1b..52472629 100644 --- a/android/src/main/java/com/horcrux/svg/PropHelper.java +++ b/android/src/main/java/com/horcrux/svg/PropHelper.java @@ -10,15 +10,18 @@ package com.horcrux.svg; import android.graphics.Color; +import android.graphics.Path; import android.graphics.RectF; import android.graphics.Paint; import android.graphics.RadialGradient; import android.graphics.LinearGradient; import android.graphics.Shader; import android.graphics.Matrix; +import android.util.Log; import javax.annotation.Nullable; +import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.ReadableArray; import java.util.regex.Matcher; @@ -35,7 +38,7 @@ import java.util.regex.Pattern; * @return a {@code float[]} if converted successfully, or {@code null} if {@param value} was * {@code null}. */ - /*package*/ + static @Nullable float[] toFloatArray(@Nullable ReadableArray value) { @@ -57,7 +60,7 @@ import java.util.regex.Pattern; * @param into output array * @return number of items copied from input to the output array */ - /*package*/ + static int toFloatArray(ReadableArray value, float[] into) { int length = value.size() > into.length ? into.length : value.size(); for (int i = 0; i < length; i++) { @@ -76,7 +79,7 @@ import java.util.regex.Pattern; * @param offset offset number * @return actual float based on relative number */ - /*package*/ + static float fromPercentageToFloat(String percentage, float relative, float offset, float scale) { Matcher matched = Pattern.compile("^(\\-?\\d+(?:\\.\\d+)?)%$").matcher(percentage); if (matched.matches()) { @@ -93,7 +96,7 @@ import java.util.regex.Pattern; * @return string is percentage-like or not. */ - /*package*/ + static boolean isPercentage(String string) { Pattern pattern = Pattern.compile("^(\\-?\\d+(?:\\.\\d+)?)%$"); return pattern.matcher(string).matches(); @@ -102,7 +105,7 @@ import java.util.regex.Pattern; /** * */ - /*package*/ static class RNSVGBrush { + static class RNSVGBrush { private GradientType mType = GradientType.LINEAR_GRADIENT; private ReadableArray mPoints; @@ -130,10 +133,10 @@ import java.util.regex.Pattern; for (int i = 0; i < stopsCount; i++) { stops[i] = (float) value.getDouble(startStops + i); stopsColors[i] = Color.argb( - (int) (value.getDouble(i * 4 + 3) * 255 * opacity), - (int) (value.getDouble(i * 4) * 255), - (int) (value.getDouble(i * 4 + 1) * 255), - (int) (value.getDouble(i * 4 + 2) * 255)); + (int) (value.getDouble(i * 4 + 3) * 255 * opacity), + (int) (value.getDouble(i * 4) * 255), + (int) (value.getDouble(i * 4 + 1) * 255), + (int) (value.getDouble(i * 4 + 2) * 255)); } } @@ -158,14 +161,14 @@ import java.util.regex.Pattern; float x2 = PropHelper.fromPercentageToFloat(mPoints.getString(2), width, offsetX, scale); float y2 = PropHelper.fromPercentageToFloat(mPoints.getString(3), height, offsetY, scale); paint.setShader( - new LinearGradient( - x1, - y1, - x2, - y2, - stopsColors, - stops, - Shader.TileMode.CLAMP)); + new LinearGradient( + x1, + y1, + x2, + y2, + stopsColors, + stops, + Shader.TileMode.CLAMP)); } else { float rx = PropHelper.fromPercentageToFloat(mPoints.getString(2), width, 0f, scale); float ry = PropHelper.fromPercentageToFloat(mPoints.getString(3), height, 0f, scale); @@ -175,12 +178,12 @@ import java.util.regex.Pattern; //float fx = PropHelper.fromPercentageToFloat(mPoints.getString(0), width, offsetX) * scale; //float fy = PropHelper.fromPercentageToFloat(mPoints.getString(1), height, offsetY) * scale / (ry / rx); Shader radialGradient = new RadialGradient( - cx, - cy, - rx, - stopsColors, - stops, - Shader.TileMode.CLAMP + cx, + cy, + rx, + stopsColors, + stops, + Shader.TileMode.CLAMP ); Matrix radialMatrix = new Matrix(); @@ -190,4 +193,401 @@ import java.util.regex.Pattern; } } } + + static class PathParser { + static private Pattern PATH_REG_EXP = Pattern.compile("[a-df-z]|[\\-+]?(?:[\\d.]e[\\-+]?|[^\\s\\-+,a-z])+", Pattern.CASE_INSENSITIVE); + static private Pattern DECIMAL_REG_EXP = Pattern.compile("(\\.\\d+)(?=\\-?\\.)"); + + private Matcher mMatcher; + private Path mPath; + private String mString; + private float mPenX = 0f; + private float mPenY = 0f; + private float mPenDownX; + private float mPenDownY; + private float mPivotX = 0f; + private float mPivotY = 0f; + private float mScale = 1f; + private boolean mValid = true; + private boolean mPendDownSet = false; + + private String mLastCommand; + private String mLastValue; + + public PathParser(String d, float scale) { + mScale = scale; + mString = d; + mPath = new Path(); + mMatcher = PATH_REG_EXP.matcher(DECIMAL_REG_EXP.matcher(mString).replaceAll("$1,")); + + while (mMatcher.find() && mValid) { + executeCommand(mMatcher.group()); + } + } + + private void executeCommand(String command) { + switch (command) { + // moveTo command + case "m": + move(getNextFloat(), getNextFloat()); + break; + case "M": + moveTo(getNextFloat(), getNextFloat()); + break; + + // lineTo command + case "l": + line(getNextFloat(), getNextFloat()); + break; + case "L": + lineTo(getNextFloat(), getNextFloat()); + break; + + // horizontalTo command + case "h": + line(getNextFloat(), 0); + break; + case "H": + lineTo(getNextFloat(), mPenY); + break; + + // verticalTo command + case "v": + line(0, getNextFloat()); + break; + case "V": + lineTo(mPenX, getNextFloat()); + break; + + // curveTo command + case "c": + curve(getNextFloat(), getNextFloat(), getNextFloat(), getNextFloat(), getNextFloat(), getNextFloat()); + break; + case "C": + curveTo(getNextFloat(), getNextFloat(), getNextFloat(), getNextFloat(), getNextFloat(), getNextFloat()); + break; + + // smoothCurveTo command + case "s": + smoothCurve(getNextFloat(), getNextFloat(), getNextFloat(), getNextFloat()); + break; + case "S": + smoothCurveTo(getNextFloat(), getNextFloat(), getNextFloat(), getNextFloat()); + break; + + // quadraticBezierCurveTo command + case "q": + quadraticBezierCurve(getNextFloat(), getNextFloat(), getNextFloat(), getNextFloat()); + break; + case "Q": + quadraticBezierCurveTo(getNextFloat(), getNextFloat(), getNextFloat(), getNextFloat()); + break; + + // smoothQuadraticBezierCurveTo command + case "t": + smoothQuadraticBezierCurve(getNextFloat(), getNextFloat()); + break; + case "T": + smoothQuadraticBezierCurveTo(getNextFloat(), getNextFloat()); + break; + + // arcTo command + case "a": + arc(getNextFloat(), getNextFloat(), getNextFloat(), getNextBoolean(), getNextBoolean(), getNextFloat(), getNextFloat()); + break; + case "A": + arcTo(getNextFloat(), getNextFloat(), getNextFloat(), getNextBoolean(), getNextBoolean(), getNextFloat(), getNextFloat()); + break; + + // close command + case "Z": + case "z": + close(); + break; + default: + mLastValue = command; + executeCommand(mLastCommand); + return; + } + + mLastCommand = command; + + if (command.equals("m")) { + mLastCommand = "l"; + } else if (command.equals("M")) { + mLastCommand = "L"; + } + } + + public Path getPath() { + return mPath; + } + + private boolean getNextBoolean() { + if (mMatcher.find()) { + return mMatcher.group().equals("1"); + } else { + mValid = false; + mPath = new Path(); + return false; + } + } + + private float getNextFloat() { + if (mLastValue != null) { + String lastValue = mLastValue; + mLastValue = null; + return Float.parseFloat(lastValue); + } else if (mMatcher.find()) { + return Float.parseFloat(mMatcher.group()); + } else { + mValid = false; + mPath = new Path(); + return 0; + } + } + private void move(float x, float y) { + moveTo(x + mPenX, y + mPenY); + } + + private void moveTo(float x, float y) { + mPivotX = mPenX = x; + mPivotY = mPenY = y; + mPath.moveTo(x * mScale, y * mScale); + } + + private void line(float x, float y) { + lineTo(x + mPenX, y + mPenY); + } + + private void lineTo(float x, float y) { + setPenDown(); + mPivotX = mPenX = x; + mPivotY = mPenY = y; + mPath.lineTo(x * mScale, y * mScale); + } + + private void curve(float c1x, float c1y, float c2x, float c2y, float ex, float ey) { + curveTo(c1x + mPenX, c1y + mPenY, c2x + mPenX, c2y + mPenY, ex + mPenX, ey + mPenY); + } + + private void curveTo(float c1x, float c1y, float c2x, float c2y, float ex, float ey) { + mPivotX = c2x; + mPivotY = c2y; + cubicTo(c1x, c1y, c2x, c2y, ex, ey); + } + + private void cubicTo(float c1x, float c1y, float c2x, float c2y, float ex, float ey) { + setPenDown(); + mPenX = ex; + mPenY = ey; + mPath.cubicTo(c1x * mScale, c1y * mScale, c2x * mScale, c2y * mScale, ex * mScale, ey * mScale); + } + + private void smoothCurve(float c1x, float c1y, float ex, float ey) { + smoothCurveTo(c1x + mPenX, c1y + mPenY, ex + mPenX, ey + mPenY); + } + + private void smoothCurveTo(float c1x, float c1y, float ex, float ey) { + float c2x = c1x; + float c2y = c1y; + c1x = (mPenX * 2) - mPivotX; + c1y = (mPenY * 2) - mPivotY; + mPivotX = c2x; + mPivotY = c2y; + cubicTo(c1x, c1y, c2x, c2y, ex, ey); + } + + private void quadraticBezierCurve(float c1x, float c1y, float c2x, float c2y) { + quadraticBezierCurveTo(c1x + mPenX, c1y + mPenY, c2x + mPenX, c2y + mPenY); + } + + private void quadraticBezierCurveTo(float c1x, float c1y, float c2x, float c2y) { + mPivotX = c1x; + mPivotY = c1y; + float ex = c2x; + float ey = c2y; + c2x = (ex + c1x * 2) / 3; + c2y = (ey + c1y * 2) / 3; + c1x = (mPenX + c1x * 2) / 3; + c1y = (mPenY + c1y * 2) / 3; + cubicTo(c1x, c1y, c2x, c2y, ex, ey); + } + + private void smoothQuadraticBezierCurve(float c1x, float c1y) { + smoothQuadraticBezierCurveTo(c1x + mPenX, c1y + mPenY); + } + + private void smoothQuadraticBezierCurveTo(float c1x, float c1y) { + float c2x = c1x; + float c2y = c1y; + c1x = (mPenX * 2) - mPivotX; + c1y = (mPenY * 2) - mPivotY; + quadraticBezierCurveTo(c1x, c1y, c2x, c2y); + } + + private void arc(float rx, float ry, float rotation, boolean outer, boolean clockwise, float x, float y) { + arcTo(rx, ry, rotation, outer, clockwise, x + mPenX, y + mPenY); + } + + private void arcTo(float rx, float ry, float rotation, boolean outer, boolean clockwise, float x, float y) { + float tX = mPenX; + float tY = mPenY; + + ry = Math.abs(ry == 0 ? (rx == 0 ? (y - tY) : rx) : ry); + rx = Math.abs(rx == 0 ? (x - tX) : rx); + + if (rx == 0 || ry == 0 || (x == tX && y == tY)) { + lineTo(x, y); + return; + } + + float rad = (float) Math.toRadians(rotation); + float cos = (float) Math.cos(rad); + float sin = (float) Math.sin(rad); + x -= tX; + y -= tY; + + // Ellipse Center + float cx = cos * x / 2 + sin * y / 2; + float cy = -sin * x / 2 + cos * y / 2; + float rxry = rx * rx * ry * ry; + float rycx = ry * ry * cx * cx; + float rxcy = rx * rx * cy * cy; + float a = rxry - rxcy - rycx; + + if (a < 0){ + a = (float)Math.sqrt(1 - a / rxry); + rx *= a; + ry *= a; + cx = x / 2; + cy = y / 2; + } else { + a = (float)Math.sqrt(a / (rxcy + rycx)); + + if (outer == clockwise) { + a = -a; + } + float cxd = -a * cy * rx / ry; + float cyd = a * cx * ry / rx; + cx = cos * cxd - sin * cyd + x / 2; + cy = sin * cxd + cos * cyd + y / 2; + } + + // Rotation + Scale Transform + float xx = cos / rx; + float yx = sin / rx; + float xy = -sin / ry; + float yy = cos / ry; + + // Start and End Angle + float sa = (float) Math.atan2(xy * -cx + yy * -cy, xx * -cx + yx * -cy); + float ea = (float) Math.atan2(xy * (x - cx) + yy * (y - cy), xx * (x - cx) + yx * (y - cy)); + + cx += tX; + cy += tY; + x += tX; + y += tY; + + setPenDown(); + + mPenX = mPivotX = x; + mPenY = mPivotY = y; + + if (rx != ry || rad != 0f) { + arcToBezier(cx, cy, rx, ry, sa, ea, clockwise, rad); + } else { + + float start = (float) Math.toDegrees(sa); + float end = (float) Math.toDegrees(ea); + float sweep = Math.abs((start - end) % 360); + + if (outer) { + if (sweep < 180) { + sweep = 360 - sweep; + } + } else { + if (sweep > 180) { + sweep = 360 - sweep; + } + } + + if (!clockwise) { + sweep = -sweep; + } + + RectF oval = new RectF( + (cx - rx) * mScale, + (cy - rx) * mScale, + (cx + rx) * mScale, + (cy + rx) * mScale); + + mPath.arcTo(oval, start, sweep); + } + } + + private void close() { + if (mPendDownSet) { + mPenX = mPenDownX; + mPenY = mPenDownY; + mPendDownSet = false; + mPath.close(); + } + } + + private void arcToBezier(float cx, float cy, float rx, float ry, float sa, float ea, boolean clockwise, float rad) { + // Inverse Rotation + Scale Transform + float cos = (float) Math.cos(rad); + float sin = (float) Math.sin(rad); + float xx = cos * rx; + float yx = -sin * ry; + float xy = sin * rx; + float yy = cos * ry; + + // Bezier Curve Approximation + float arc = ea - sa; + if (arc < 0 && clockwise) { + arc += Math.PI * 2; + } else if (arc > 0 && !clockwise) { + arc -= Math.PI * 2; + } + + int n = (int) Math.ceil(Math.abs(arc / (Math.PI / 2))); + + float step = arc / n; + float k = (4 / 3) * (float) Math.tan(step / 4); + + float x = (float) Math.cos(sa); + float y = (float) Math.sin(sa); + + for (int i = 0; i < n; i++){ + float cp1x = x - k * y; + float cp1y = y + k * x; + + sa += step; + x = (float) Math.cos(sa); + y = (float) Math.sin(sa); + + float cp2x = x + k * y; + float cp2y = y - k * x; + + mPath.cubicTo( + (cx + xx * cp1x + yx * cp1y) * mScale, + (cy + xy * cp1x + yy * cp1y) * mScale, + (cx + xx * cp2x + yx * cp2y) * mScale, + (cy + xy * cp2x + yy * cp2y) * mScale, + (cx + xx * x + yx * y) * mScale, + (cy + xy * x + yy * y) * mScale + ); + } + } + + private void setPenDown() { + if (!mPendDownSet) { + mPenDownX = mPenX; + mPenDownY = mPenY; + mPendDownSet = true; + } + } + } } diff --git a/android/src/main/java/com/horcrux/svg/RNSvgPackage.java b/android/src/main/java/com/horcrux/svg/RNSvgPackage.java deleted file mode 100644 index 299ae8f2..00000000 --- a/android/src/main/java/com/horcrux/svg/RNSvgPackage.java +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) 2015-present, Horcrux. - * All rights reserved. - * - * This source code is licensed under the MIT-style license found in the - * LICENSE file in the root directory of this source tree. - */ - - -package com.horcrux.svg; - -import com.facebook.react.ReactPackage; -import com.facebook.react.bridge.JavaScriptModule; -import com.facebook.react.bridge.NativeModule; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.uimanager.ViewManager; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - - -public class RNSvgPackage implements ReactPackage { - - @Override - public List createViewManagers(ReactApplicationContext reactContext) { - return Arrays.asList( - RNSVGRenderableViewManager.createRNSVGGroupViewManager(), - RNSVGRenderableViewManager.createRNSVGPathViewManager(), - RNSVGRenderableViewManager.createRNSVGCircleViewManager(), - RNSVGRenderableViewManager.createRNSVGEllipseViewManager(), - RNSVGRenderableViewManager.createRNSVGLineViewManager(), - RNSVGRenderableViewManager.createRNSVGRectViewManager(), - RNSVGRenderableViewManager.createRNSVGTextViewManager(), - RNSVGRenderableViewManager.createRNSVGImageViewManager(), - RNSVGRenderableViewManager.createRNSVGClipPathViewManager(), - RNSVGRenderableViewManager.createRNSVGDefsViewManager(), - RNSVGRenderableViewManager.createRNSVGUseViewManager(), - RNSVGRenderableViewManager.createRNSVGViewBoxViewManager(), - RNSVGRenderableViewManager.createRNSVGLinearGradientManager(), - RNSVGRenderableViewManager.createRNSVGRadialGradientManager(), - RNSVGRenderableViewManager.createRNSVGSpanManager(), - new RNSVGSvgViewManager()); - } - - @Override - public List> createJSModules() { - return Collections.emptyList(); - } - - @Override - public List createNativeModules(ReactApplicationContext reactContext) { - return Collections.emptyList(); - } -} diff --git a/android/src/main/java/com/horcrux/svg/RNSVGRadialGradientShadowNode.java b/android/src/main/java/com/horcrux/svg/RadialGradientShadowNode.java similarity index 96% rename from android/src/main/java/com/horcrux/svg/RNSVGRadialGradientShadowNode.java rename to android/src/main/java/com/horcrux/svg/RadialGradientShadowNode.java index ea168aea..966414c0 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGRadialGradientShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RadialGradientShadowNode.java @@ -17,7 +17,7 @@ import com.facebook.react.uimanager.annotations.ReactProp; /** * Shadow node for virtual LinearGradient definition view */ -public class RNSVGRadialGradientShadowNode extends RNSVGDefinitionShadowNode { +public class RadialGradientShadowNode extends DefinitionShadowNode { private String mFx; private String mFy; private String mRx; diff --git a/android/src/main/java/com/horcrux/svg/RNSVGRectShadowNode.java b/android/src/main/java/com/horcrux/svg/RectShadowNode.java similarity index 91% rename from android/src/main/java/com/horcrux/svg/RNSVGRectShadowNode.java rename to android/src/main/java/com/horcrux/svg/RectShadowNode.java index 397a5542..961e0d6d 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGRectShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RectShadowNode.java @@ -18,7 +18,7 @@ import com.facebook.react.uimanager.annotations.ReactProp; /** * Shadow node for virtual RNSVGPath view */ -public class RNSVGRectShadowNode extends RNSVGPathShadowNode { +public class RectShadowNode extends RenderableShadowNode { private String mX; private String mY; @@ -66,12 +66,6 @@ public class RNSVGRectShadowNode extends RNSVGPathShadowNode { markUpdated(); } - @Override - public void draw(Canvas canvas, Paint paint, float opacity) { - mPath = getPath(canvas, paint); - super.draw(canvas, paint, opacity); - } - @Override protected Path getPath(Canvas canvas, Paint paint) { Path path = new Path(); diff --git a/android/src/main/java/com/horcrux/svg/RNSVGPathShadowNode.java b/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java similarity index 78% rename from android/src/main/java/com/horcrux/svg/RNSVGPathShadowNode.java rename to android/src/main/java/com/horcrux/svg/RenderableShadowNode.java index 72a135b1..7d9e7fa3 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGPathShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RenderableShadowNode.java @@ -21,8 +21,10 @@ import android.graphics.Point; import android.graphics.RectF; import android.graphics.Color; +import android.util.Log; import com.facebook.common.logging.FLog; +import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.JavaOnlyArray; import com.facebook.react.bridge.ReadableArray; @@ -38,47 +40,43 @@ import java.util.regex.Pattern; /** * Shadow node for virtual RNSVGPath view */ -public class RNSVGPathShadowNode extends RNSVGVirtualNode { +abstract public class RenderableShadowNode extends VirtualNode { + // strokeLinecap private static final int CAP_BUTT = 0; private static final int CAP_ROUND = 1; private static final int CAP_SQUARE = 2; + // strokeLinejoin private static final int JOIN_BEVEL = 2; private static final int JOIN_MITER = 0; private static final int JOIN_ROUND = 1; + // fillRule private static final int FILL_RULE_EVENODD = 0; private static final int FILL_RULE_NONZERO = 1; public @Nullable ReadableArray mStroke; public @Nullable float[] mStrokeDasharray; + public float mStrokeWidth = 1; public float mStrokeOpacity = 1; public float mStrokeMiterlimit = 4; public float mStrokeDashoffset = 0; + public Paint.Cap mStrokeLinecap = Paint.Cap.ROUND; public Paint.Join mStrokeLinejoin = Paint.Join.ROUND; public @Nullable ReadableArray mFill; public float mFillOpacity = 1; public Path.FillType mFillRule = Path.FillType.WINDING; - private boolean mFillRuleSet; protected Path mPath; - private float[] mD; - private ArrayList mChangedList; + private ReadableArray mLastMergedList; private ArrayList mOriginProperties; protected ReadableArray mPropList = new JavaOnlyArray(); - protected WritableArray mOwnedPropList = new JavaOnlyArray(); - - @ReactProp(name = "d") - public void setPath(@Nullable ReadableArray shapePath) { - mD = PropHelper.toFloatArray(shapePath); - setupPath(); - markUpdated(); - } + protected WritableArray mAttributeList = new JavaOnlyArray(); @ReactProp(name = "fill") public void setFill(@Nullable ReadableArray fill) { @@ -102,11 +100,10 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode { break; default: throw new JSApplicationIllegalArgumentException( - "fillRule " + mFillRule + " unrecognized"); + "fillRule " + mFillRule + " unrecognized"); } - mFillRuleSet = true; - setupPath(); + mPath = null; markUpdated(); } @@ -140,7 +137,7 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode { markUpdated(); } - @ReactProp(name = "strokeWidth", defaultFloat = 1f) + @ReactProp(name = "strokeWidth", defaultFloat = 0f) public void setStrokeWidth(float strokeWidth) { mStrokeWidth = strokeWidth; markUpdated(); @@ -166,7 +163,7 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode { break; default: throw new JSApplicationIllegalArgumentException( - "strokeLinecap " + mStrokeLinecap + " unrecognized"); + "strokeLinecap " + mStrokeLinecap + " unrecognized"); } markUpdated(); } @@ -185,37 +182,40 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode { break; default: throw new JSApplicationIllegalArgumentException( - "strokeLinejoin " + mStrokeLinejoin + " unrecognized"); + "strokeLinejoin " + mStrokeLinejoin + " unrecognized"); } markUpdated(); } @ReactProp(name = "propList") public void setPropList(@Nullable ReadableArray propList) { - WritableArray copy = new JavaOnlyArray(); - if (propList != null) { + WritableArray copy = Arguments.createArray(); for (int i = 0; i < propList.size(); i++) { String fieldName = propertyNameToFieldName(propList.getString(i)); copy.pushString(fieldName); - mOwnedPropList.pushString(fieldName); + mAttributeList.pushString(fieldName); } - + mPropList = copy; } - mPropList = copy; markUpdated(); } @Override public void draw(Canvas canvas, Paint paint, float opacity) { + if (mPath == null) { + mPath = getPath(canvas, paint); + mPath.setFillType(mFillRule); + } + opacity *= mOpacity; if (opacity > MIN_OPACITY_FOR_DRAW) { int count = saveAndSetupCanvas(canvas); if (mPath == null) { throw new JSApplicationIllegalArgumentException( - "Paths should have a valid path (d) prop"); + "Paths should have a valid path (d) prop"); } clip(canvas, paint); @@ -231,32 +231,6 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode { } } - private void setupPath() { - // init path after both fillRule and path have been set - if (mFillRuleSet && mD != null) { - mPath = new Path(); - mPath.setFillType(mFillRule); - super.createPath(mD, mPath); - } - } - - /** - * sorting stops and stopsColors from array - */ - private static void parseGradientStops(ReadableArray value, int stopsCount, float[] stops, int[] stopsColors, int startColorsPosition) { - int startStops = value.size() - stopsCount; - for (int i = 0; i < stopsCount; i++) { - stops[i] = (float)value.getDouble(startStops + i); - stopsColors[i] = Color.argb( - (int) (value.getDouble(startColorsPosition + i * 4 + 3) * 255), - (int) (value.getDouble(startColorsPosition + i * 4) * 255), - (int) (value.getDouble(startColorsPosition + i * 4 + 1) * 255), - (int) (value.getDouble(startColorsPosition + i * 4 + 2) * 255)); - - } - } - - /** * Sets up paint according to the props set on a shadow view. Returns {@code true} * if the fill should be drawn, {@code false} if not. @@ -303,10 +277,10 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode { if (colorType == 0) { // solid color paint.setARGB( - (int) (colors.size() > 4 ? colors.getDouble(4) * opacity * 255 : opacity * 255), - (int) (colors.getDouble(1) * 255), - (int) (colors.getDouble(2) * 255), - (int) (colors.getDouble(3) * 255)); + (int) (colors.size() > 4 ? colors.getDouble(4) * opacity * 255 : opacity * 255), + (int) (colors.getDouble(1) * 255), + (int) (colors.getDouble(2) * 255), + (int) (colors.getDouble(3) * 255)); } else if (colorType == 1) { if (box == null) { box = new RectF(); @@ -323,17 +297,15 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode { } - @Override - protected Path getPath(Canvas canvas, Paint paint) { - return mPath; - } + + abstract protected Path getPath(Canvas canvas, Paint paint); @Override public int hitTest(Point point, @Nullable Matrix matrix) { Bitmap bitmap = Bitmap.createBitmap( - mCanvasWidth, - mCanvasHeight, - Bitmap.Config.ARGB_8888); + mCanvasWidth, + mCanvasHeight, + Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); @@ -389,21 +361,15 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode { } @Override - public void mergeProperties(RNSVGVirtualNode target, ReadableArray mergeList, boolean inherited) { + public void mergeProperties(VirtualNode target, ReadableArray mergeList, boolean inherited) { + mLastMergedList = mergeList; + if (mergeList.size() == 0) { return; } - if (!inherited) { - mOriginProperties = new ArrayList<>(); - mChangedList = new ArrayList<>(); - } - - WritableArray propList = new JavaOnlyArray(); - for (int i = 0; i < mPropList.size(); i++) { - propList.pushString(mPropList.getString(i)); - } - mOwnedPropList = propList; + mOriginProperties = new ArrayList<>(); + resetAttributeList(); for (int i = 0, size = mergeList.size(); i < size; i++) { try { @@ -413,12 +379,12 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode { if (inherited) { if (!hasOwnProperty(fieldName)) { + mAttributeList.pushString(fieldName); + mOriginProperties.add(field.get(this)); field.set(this, value); - propList.pushString(fieldName); } } else { mOriginProperties.add(field.get(this)); - mChangedList.add(fieldName); field.set(this, value); } } catch (Exception e) { @@ -428,25 +394,33 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode { } @Override - public void mergeProperties(RNSVGVirtualNode target, ReadableArray mergeList) { + public void mergeProperties(VirtualNode target, ReadableArray mergeList) { mergeProperties(target, mergeList, false); } @Override public void resetProperties() { - if (mChangedList != null) { + if (mLastMergedList != null) { try { - for (int i = mChangedList.size() - 1; i >= 0; i--) { - Field field = getClass().getField(mChangedList.get(i)); + for (int i = mLastMergedList.size() - 1; i >= 0; i--) { + Field field = getClass().getField(mLastMergedList.getString(i)); field.set(this, mOriginProperties.get(i)); } } catch (Exception e) { throw new IllegalStateException(e); } - mChangedList = null; + mLastMergedList = null; mOriginProperties = null; } + resetAttributeList(); + } + + private void resetAttributeList() { + mAttributeList = Arguments.createArray(); + for (int i = 0; i < mPropList.size(); i++) { + mAttributeList.pushString(mPropList.getString(i)); + } } // convert propertyName something like fillOpacity to fieldName like mFillOpacity @@ -462,8 +436,8 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode { } private boolean hasOwnProperty(String propName) { - for (int i = mOwnedPropList.size() - 1; i >= 0; i--) { - if (mOwnedPropList.getString(i).equals(propName)) { + for (int i = mAttributeList.size() - 1; i >= 0; i--) { + if (mAttributeList.getString(i).equals(propName)) { return true; } } diff --git a/android/src/main/java/com/horcrux/svg/RNSVGRenderableViewManager.java b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java similarity index 50% rename from android/src/main/java/com/horcrux/svg/RNSVGRenderableViewManager.java rename to android/src/main/java/com/horcrux/svg/RenderableViewManager.java index cc83b74f..d52b6f96 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGRenderableViewManager.java +++ b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java @@ -17,10 +17,10 @@ import com.facebook.react.uimanager.ViewManager; /** * ViewManager for all shadowed RNSVG views: Group, Path and Text. Since these never get rendered - * into native views and don't need any logic (all the logic is in {@link RNSVGSvgView}), this + * into native views and don't need any logic (all the logic is in {@link SvgView}), this * "stubbed" ViewManager is used for all of them. */ -public class RNSVGRenderableViewManager extends ViewManager { +public class RenderableViewManager extends ViewManager { /* package */ static final String CLASS_GROUP = "RNSVGGroup"; /* package */ static final String CLASS_PATH = "RNSVGPath"; @@ -41,70 +41,70 @@ public class RNSVGRenderableViewManager extends ViewManager getShadowNodeClass() { switch (mClassName) { case CLASS_GROUP: - return RNSVGGroupShadowNode.class; + return GroupShadowNode.class; case CLASS_PATH: - return RNSVGPathShadowNode.class; + return PathShadowNode.class; case CLASS_CIRCLE: - return RNSVGCircleShadowNode.class; + return CircleShadowNode.class; case CLASS_ELLIPSE: - return RNSVGEllipseShadowNode.class; + return EllipseShadowNode.class; case CLASS_LINE: - return RNSVGLineShadowNode.class; + return LineShadowNode.class; case CLASS_RECT: - return RNSVGRectShadowNode.class; + return RectShadowNode.class; case CLASS_TEXT: - return RNSVGTextShadowNode.class; + return TextShadowNode.class; case CLASS_IMAGE: - return RNSVGImageShadowNode.class; + return ImageShadowNode.class; case CLASS_CLIP_PATH: - return RNSVGClipPathShadowNode.class; + return ClipPathShadowNode.class; case CLASS_DEFS: - return RNSVGDefsShadowNode.class; + return DefsShadowNode.class; case CLASS_USE: - return RNSVGUseShadowNode.class; + return UseShadowNode.class; case CLASS_VIEW_BOX: - return RNSVGViewBoxShadowNode.class; + return ViewBoxShadowNode.class; case CLASS_LINEAR_GRADIENT: - return RNSVGLinearGradientShadowNode.class; + return LinearGradientShadowNode.class; case CLASS_RADIAL_GRADIENT: - return RNSVGRadialGradientShadowNode.class; + return RadialGradientShadowNode.class; case CLASS_SPAN: - return RNSVGSpanShadowNode.class; + return SpanShadowNode.class; default: throw new IllegalStateException("Unexpected type " + mClassName); } @@ -195,4 +195,8 @@ public class RNSVGRenderableViewManager extends ViewManager mTagToShadowNode = new SparseArray<>(); + private static final SparseArray mTagToSvgView = new SparseArray<>(); + + static void registerShadowNode(SvgViewShadowNode shadowNode) { + mTagToShadowNode.put(shadowNode.getReactTag(), shadowNode); + } + + static void registerSvgView(SvgView svg) { + mTagToSvgView.put(svg.getId(), svg); + } + + static void unregisterInstance(int tag) { + mTagToShadowNode.remove(tag); + mTagToSvgView.remove(tag); + } + + static SvgView getSvgViewByTag(int tag) { + return mTagToSvgView.get(tag); + } + + static SvgViewShadowNode getShadowNodeByTag(int tag) { + return mTagToShadowNode.get(tag); + } +} diff --git a/android/src/main/java/com/horcrux/svg/SvgPackage.java b/android/src/main/java/com/horcrux/svg/SvgPackage.java new file mode 100644 index 00000000..5081de14 --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/SvgPackage.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2015-present, Horcrux. + * All rights reserved. + * + * This source code is licensed under the MIT-style license found in the + * LICENSE file in the root directory of this source tree. + */ + + +package com.horcrux.svg; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.JavaScriptModule; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + + +public class SvgPackage implements ReactPackage { + + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return Arrays.asList( + RenderableViewManager.createGroupViewManager(), + RenderableViewManager.createPathViewManager(), + RenderableViewManager.createCircleViewManager(), + RenderableViewManager.createEllipseViewManager(), + RenderableViewManager.createLineViewManager(), + RenderableViewManager.createRectViewManager(), + RenderableViewManager.createTextViewManager(), + RenderableViewManager.createImageViewManager(), + RenderableViewManager.createClipPathViewManager(), + RenderableViewManager.createDefsViewManager(), + RenderableViewManager.createUseViewManager(), + RenderableViewManager.createViewBoxViewManager(), + RenderableViewManager.createLinearGradientManager(), + RenderableViewManager.createRadialGradientManager(), + RenderableViewManager.createSpanManager(), + new SvgViewManager()); + } + + @Override + public List> createJSModules() { + return Collections.emptyList(); + } + + @Override + public List createNativeModules(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } +} diff --git a/android/src/main/java/com/horcrux/svg/RNSVGSvgView.java b/android/src/main/java/com/horcrux/svg/SvgView.java similarity index 86% rename from android/src/main/java/com/horcrux/svg/RNSVGSvgView.java rename to android/src/main/java/com/horcrux/svg/SvgView.java index f25ca37f..0583aae6 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGSvgView.java +++ b/android/src/main/java/com/horcrux/svg/SvgView.java @@ -9,11 +9,14 @@ package com.horcrux.svg; +import android.graphics.Bitmap; +import android.graphics.Canvas; import android.graphics.Point; import android.util.Log; +import android.util.SparseArray; import android.view.MotionEvent; -import android.view.View; import android.view.TextureView; +import android.view.View; import com.facebook.react.ReactRootView; import com.facebook.react.bridge.Arguments; @@ -26,10 +29,12 @@ import com.facebook.react.uimanager.events.TouchEventCoalescingKeyHelper; import com.facebook.react.uimanager.events.TouchEventType; import com.facebook.react.uimanager.events.EventDispatcher; +import javax.annotation.Nullable; + /** - * Custom {@link View} implementation that draws an RNSVGSvg React view and its \children. + * Custom {@link View} implementation that draws an RNSVGSvg React view and its \childrn. */ -public class RNSVGSvgView extends TextureView { +public class SvgView extends View { public enum Events { EVENT_DATA_URL("onDataURL"); @@ -45,6 +50,7 @@ public class RNSVGSvgView extends TextureView { } } + private @Nullable Bitmap mBitmap; private RCTEventEmitter mEventEmitter; private EventDispatcher mEventDispatcher; private int mTargetTag; @@ -52,15 +58,36 @@ public class RNSVGSvgView extends TextureView { private final TouchEventCoalescingKeyHelper mTouchEventCoalescingKeyHelper = new TouchEventCoalescingKeyHelper(); - public RNSVGSvgView(ReactContext reactContext) { + public SvgView(ReactContext reactContext) { super(reactContext); - setOpaque(false); mEventEmitter = reactContext.getJSModule(RCTEventEmitter.class); mEventDispatcher = reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher(); } - private RNSVGSvgViewShadowNode getShadowNode() { - return RNSVGSvgViewShadowNode.getShadowNodeByTag(getId()); + @Override + public void setId(int id) { + super.setId(id); + SvgInstancesManager.registerSvgView(this); + } + + public void setBitmap(Bitmap bitmap) { + if (mBitmap != null) { + mBitmap.recycle(); + } + mBitmap = bitmap; + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (mBitmap != null) { + canvas.drawBitmap(mBitmap, 0, 0, null); + } + } + + private SvgViewShadowNode getShadowNode() { + return SvgInstancesManager.getShadowNodeByTag(getId()); } @Override @@ -165,4 +192,6 @@ public class RNSVGSvgView extends TextureView { event.putString("base64", getShadowNode().getBase64()); mEventEmitter.receiveEvent(getId(), Events.EVENT_DATA_URL.toString(), event); } + + } diff --git a/android/src/main/java/com/horcrux/svg/RNSVGSvgViewManager.java b/android/src/main/java/com/horcrux/svg/SvgViewManager.java similarity index 54% rename from android/src/main/java/com/horcrux/svg/RNSVGSvgViewManager.java rename to android/src/main/java/com/horcrux/svg/SvgViewManager.java index 5e585548..58094cfb 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGSvgViewManager.java +++ b/android/src/main/java/com/horcrux/svg/SvgViewManager.java @@ -9,6 +9,11 @@ package com.horcrux.svg; +import android.graphics.Bitmap; + +import com.facebook.yoga.YogaMeasureMode; +import com.facebook.yoga.YogaMeasureFunction; +import com.facebook.yoga.YogaNodeAPI; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.common.MapBuilder; import com.facebook.react.uimanager.BaseViewManager; @@ -20,13 +25,24 @@ import java.util.Map; import javax.annotation.Nullable; /** - * ViewManager for RNSVGSvgView React views. Renders as a {@link RNSVGSvgView} and handles + * 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. */ -public class RNSVGSvgViewManager extends BaseViewManager { +public class SvgViewManager extends BaseViewManager { private static final String REACT_CLASS = "RNSVGSvgView"; private static final int COMMAND_TO_DATA_URL = 100; + private static final YogaMeasureFunction MEASURE_FUNCTION = new YogaMeasureFunction() { + @Override + public long measure( + YogaNodeAPI node, + float width, + YogaMeasureMode widthMode, + float height, + YogaMeasureMode heightMode) { + throw new IllegalStateException("SurfaceView should have explicit width and height set"); + } + }; @Override public String getName() { @@ -34,23 +50,30 @@ public class RNSVGSvgViewManager extends BaseViewManager getShadowNodeClass() { - return RNSVGSvgViewShadowNode.class; + public Class getShadowNodeClass() { + return SvgViewShadowNode.class; } @Override - public RNSVGSvgViewShadowNode createShadowNodeInstance() { - return new RNSVGSvgViewShadowNode(); + public SvgViewShadowNode createShadowNodeInstance() { + SvgViewShadowNode node = new SvgViewShadowNode(); + node.setMeasureFunction(MEASURE_FUNCTION); + return node; } @Override - protected RNSVGSvgView createViewInstance(ThemedReactContext reactContext) { - return new RNSVGSvgView(reactContext); + public void onDropViewInstance(SvgView view) { + SvgInstancesManager.unregisterInstance(view.getId()); } @Override - public void updateExtraData(RNSVGSvgView root, Object extraData) { - root.setSurfaceTextureListener((RNSVGSvgViewShadowNode) extraData); + protected SvgView createViewInstance(ThemedReactContext reactContext) { + return new SvgView(reactContext); + } + + @Override + public void updateExtraData(SvgView root, Object extraData) { + root.setBitmap((Bitmap) extraData); } @Override @@ -69,14 +92,14 @@ public class RNSVGSvgViewManager extends BaseViewManager getExportedCustomDirectEventTypeConstants() { MapBuilder.Builder builder = MapBuilder.builder(); - for (RNSVGSvgView.Events event : RNSVGSvgView.Events.values()) { + for (SvgView.Events event : SvgView.Events.values()) { builder.put(event.toString(), MapBuilder.of("registrationName", event.toString())); } return builder.build(); } @Override - public void receiveCommand(RNSVGSvgView root, int commandId, @Nullable ReadableArray args) { + public void receiveCommand(SvgView root, int commandId, @Nullable ReadableArray args) { super.receiveCommand(root, commandId, args); switch (commandId) { diff --git a/android/src/main/java/com/horcrux/svg/RNSVGSvgViewShadowNode.java b/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java similarity index 53% rename from android/src/main/java/com/horcrux/svg/RNSVGSvgViewShadowNode.java rename to android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java index f0d2ac8e..4e63d818 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGSvgViewShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java @@ -13,13 +13,15 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Point; -import android.util.Base64; -import android.util.SparseArray; -import android.view.TextureView; -import android.graphics.Color; -import android.view.Surface; -import android.graphics.PorterDuff; import android.graphics.SurfaceTexture; +import android.support.annotation.Nullable; +import android.util.Base64; +import android.util.Log; +import android.util.SparseArray; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.view.Surface; +import android.view.TextureView; import com.facebook.common.logging.FLog; import com.facebook.react.common.ReactConstants; @@ -27,7 +29,6 @@ import com.facebook.react.uimanager.LayoutShadowNode; import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.UIViewOperationQueue; -import javax.annotation.Nullable; import java.io.ByteArrayOutputStream; import java.util.HashMap; import java.util.Map; @@ -35,19 +36,12 @@ import java.util.Map; /** * Shadow node for RNSVG virtual tree root - RNSVGSvgView */ -public class RNSVGSvgViewShadowNode extends LayoutShadowNode implements TextureView.SurfaceTextureListener { - - private static final SparseArray mTagToShadowNode = new SparseArray<>(); - - public static RNSVGSvgViewShadowNode getShadowNodeByTag(int tag) { - return mTagToShadowNode.get(tag); - } - - private @Nullable Surface mSurface; +public class SvgViewShadowNode extends LayoutShadowNode { private boolean mResponsible = false; - private static final Map mDefinedClipPaths = new HashMap<>(); - private static final Map mDefinedTemplates = new HashMap<>(); - private static final Map mDefinedBrushes = new HashMap<>(); + + private final Map mDefinedClipPaths = new HashMap<>(); + private final Map mDefinedTemplates = new HashMap<>(); + private final Map mDefinedBrushes = new HashMap<>(); @Override public boolean isVirtual() { @@ -62,27 +56,24 @@ public class RNSVGSvgViewShadowNode extends LayoutShadowNode implements TextureV @Override public void onCollectExtraUpdates(UIViewOperationQueue uiUpdater) { super.onCollectExtraUpdates(uiUpdater); - drawOutput(); - uiUpdater.enqueueUpdateExtraData(getReactTag(), this); + uiUpdater.enqueueUpdateExtraData(getReactTag(), drawOutput()); } - public void drawOutput() { - if (mSurface == null || !mSurface.isValid()) { - markChildrenUpdatesSeen(this); - return; - } + @Override + public void setReactTag(int reactTag) { + super.setReactTag(reactTag); + SvgInstancesManager.registerShadowNode(this); + } - try { - Canvas canvas = mSurface.lockCanvas(null); - drawChildren(canvas); + public Object drawOutput() { + Bitmap bitmap = Bitmap.createBitmap( + (int) getLayoutWidth(), + (int) getLayoutHeight(), + Bitmap.Config.ARGB_8888); - if (mSurface != null) { - mSurface.unlockCanvasAndPost(canvas); - } - - } catch (IllegalArgumentException | IllegalStateException e) { - FLog.e(ReactConstants.TAG, e.getClass().getSimpleName() + " in Svg.unlockCanvasAndPost"); - } + Canvas canvas = new Canvas(bitmap); + drawChildren(canvas); + return bitmap; } private void drawChildren(Canvas canvas) { @@ -90,11 +81,11 @@ public class RNSVGSvgViewShadowNode extends LayoutShadowNode implements TextureV Paint paint = new Paint(); for (int i = 0; i < getChildCount(); i++) { - if (!(getChildAt(i) instanceof RNSVGVirtualNode)) { + if (!(getChildAt(i) instanceof VirtualNode)) { continue; } - RNSVGVirtualNode child = (RNSVGVirtualNode) getChildAt(i); + VirtualNode child = (VirtualNode) getChildAt(i); child.setupDimensions(canvas); child.saveDefinition(); child.draw(canvas, paint, 1f); @@ -106,19 +97,11 @@ public class RNSVGSvgViewShadowNode extends LayoutShadowNode implements TextureV } } - private void markChildrenUpdatesSeen(ReactShadowNode shadowNode) { - for (int i = 0; i < shadowNode.getChildCount(); i++) { - ReactShadowNode child = shadowNode.getChildAt(i); - child.markUpdateSeen(); - markChildrenUpdatesSeen(child); - } - } - public String getBase64() { Bitmap bitmap = Bitmap.createBitmap( - (int) getLayoutWidth(), - (int) getLayoutHeight(), - Bitmap.Config.ARGB_8888); + (int) getLayoutWidth(), + (int) getLayoutHeight(), + Bitmap.Config.ARGB_8888); drawChildren(new Canvas(bitmap)); ByteArrayOutputStream stream = new ByteArrayOutputStream(); @@ -128,27 +111,6 @@ public class RNSVGSvgViewShadowNode extends LayoutShadowNode implements TextureV return Base64.encodeToString(bitmapBytes, Base64.DEFAULT); } - @Override - public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { - mSurface = new Surface(surface); - mTagToShadowNode.put(getReactTag(), this); - drawOutput(); - } - - @Override - public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { - mTagToShadowNode.remove(getReactTag()); - surface.release(); - mSurface = null; - return true; - } - - @Override - public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {} - - @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) {} - public void enableTouchEvents() { if (!mResponsible) { mResponsible = true; @@ -163,11 +125,11 @@ public class RNSVGSvgViewShadowNode extends LayoutShadowNode implements TextureV int count = getChildCount(); int viewTag = -1; for (int i = count - 1; i >= 0; i--) { - if (!(getChildAt(i) instanceof RNSVGVirtualNode)) { + if (!(getChildAt(i) instanceof VirtualNode)) { continue; } - viewTag = ((RNSVGVirtualNode) getChildAt(i)).hitTest(point); + viewTag = ((VirtualNode) getChildAt(i)).hitTest(point); if (viewTag != -1) { break; } @@ -176,19 +138,19 @@ public class RNSVGSvgViewShadowNode extends LayoutShadowNode implements TextureV return viewTag; } - public void defineClipPath(RNSVGVirtualNode clipPath, String clipPathRef) { + public void defineClipPath(VirtualNode clipPath, String clipPathRef) { mDefinedClipPaths.put(clipPathRef, clipPath); } - public RNSVGVirtualNode getDefinedClipPath(String clipPathRef) { + public VirtualNode getDefinedClipPath(String clipPathRef) { return mDefinedClipPaths.get(clipPathRef); } - public void defineTemplate(RNSVGVirtualNode template, String templateRef) { + public void defineTemplate(VirtualNode template, String templateRef) { mDefinedTemplates.put(templateRef, template); } - public RNSVGVirtualNode getDefinedTemplate(String templateRef) { + public VirtualNode getDefinedTemplate(String templateRef) { return mDefinedTemplates.get(templateRef); } diff --git a/android/src/main/java/com/horcrux/svg/RNSVGTextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java similarity index 99% rename from android/src/main/java/com/horcrux/svg/RNSVGTextShadowNode.java rename to android/src/main/java/com/horcrux/svg/TextShadowNode.java index de443749..f25585c0 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGTextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -24,6 +24,7 @@ import com.facebook.react.uimanager.annotations.ReactProp; /** * Shadow node for virtual RNSVGText view */ + public class RNSVGTextShadowNode extends RNSVGGroupShadowNode { private float mOffsetX = 0; diff --git a/android/src/main/java/com/horcrux/svg/RNSVGUseShadowNode.java b/android/src/main/java/com/horcrux/svg/UseShadowNode.java similarity index 82% rename from android/src/main/java/com/horcrux/svg/RNSVGUseShadowNode.java rename to android/src/main/java/com/horcrux/svg/UseShadowNode.java index 21e61eaf..40931d00 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGUseShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/UseShadowNode.java @@ -11,6 +11,7 @@ package com.horcrux.svg; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.Path; import com.facebook.common.logging.FLog; import com.facebook.react.common.ReactConstants; @@ -19,7 +20,7 @@ import com.facebook.react.uimanager.annotations.ReactProp; /** * Shadow node for virtual RNSVGPath view */ -public class RNSVGUseShadowNode extends RNSVGPathShadowNode { +public class UseShadowNode extends RenderableShadowNode { private String mHref; private String mWidth; @@ -53,13 +54,13 @@ public class RNSVGUseShadowNode extends RNSVGPathShadowNode { @Override public void draw(Canvas canvas, Paint paint, float opacity) { - RNSVGVirtualNode template = getSvgShadowNode().getDefinedTemplate(mHref); + VirtualNode template = getSvgShadowNode().getDefinedTemplate(mHref); if (template != null) { int count = saveAndSetupCanvas(canvas); clip(canvas, paint); - template.mergeProperties(this, mOwnedPropList); + template.mergeProperties(this, mAttributeList, true); template.draw(canvas, paint, opacity * mOpacity); template.resetProperties(); @@ -70,4 +71,10 @@ public class RNSVGUseShadowNode extends RNSVGPathShadowNode { "template named: " + mHref + " is not defined."); } } + + @Override + protected Path getPath(Canvas canvas, Paint paint) { + // todo: + return new Path(); + } } diff --git a/android/src/main/java/com/horcrux/svg/RNSVGViewBoxShadowNode.java b/android/src/main/java/com/horcrux/svg/ViewBoxShadowNode.java similarity index 94% rename from android/src/main/java/com/horcrux/svg/RNSVGViewBoxShadowNode.java rename to android/src/main/java/com/horcrux/svg/ViewBoxShadowNode.java index 1c344f40..deadb22b 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGViewBoxShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/ViewBoxShadowNode.java @@ -19,7 +19,7 @@ import com.facebook.react.uimanager.annotations.ReactProp; /** * Shadow node for virtual RNSVGPath view */ -public class RNSVGViewBoxShadowNode extends RNSVGGroupShadowNode { +public class ViewBoxShadowNode extends GroupShadowNode { private static final int MOS_MEET = 0; private static final int MOS_SLICE = 1; @@ -174,11 +174,11 @@ public class RNSVGViewBoxShadowNode extends RNSVGGroupShadowNode { } @Override - public void mergeProperties(RNSVGVirtualNode target, ReadableArray mergeList) { - if (target instanceof RNSVGUseShadowNode) { + public void mergeProperties(VirtualNode target, ReadableArray mergeList, boolean inherited) { + if (target instanceof UseShadowNode) { mFromSymbol = true; - mBoxWidth = ((RNSVGUseShadowNode)target).getWidth(); - mBoxHeight = ((RNSVGUseShadowNode)target).getHeight(); + mBoxWidth = ((UseShadowNode)target).getWidth(); + mBoxHeight = ((UseShadowNode)target).getHeight(); } } diff --git a/android/src/main/java/com/horcrux/svg/RNSVGVirtualNode.java b/android/src/main/java/com/horcrux/svg/VirtualNode.java similarity index 77% rename from android/src/main/java/com/horcrux/svg/RNSVGVirtualNode.java rename to android/src/main/java/com/horcrux/svg/VirtualNode.java index a61db3a2..ea64da33 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGVirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/VirtualNode.java @@ -17,8 +17,10 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; +import com.facebook.common.logging.FLog; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.common.ReactConstants; import com.facebook.react.uimanager.DisplayMetricsHolder; import com.facebook.react.uimanager.LayoutShadowNode; import com.facebook.react.uimanager.ReactShadowNode; @@ -26,7 +28,7 @@ import com.facebook.react.uimanager.annotations.ReactProp; import javax.annotation.Nullable; -public abstract class RNSVGVirtualNode extends LayoutShadowNode { +public abstract class VirtualNode extends LayoutShadowNode { protected static final float MIN_OPACITY_FOR_DRAW = 0.01f; @@ -35,8 +37,7 @@ public abstract class RNSVGVirtualNode extends LayoutShadowNode { protected float mOpacity = 1f; protected Matrix mMatrix = new Matrix(); - protected @Nullable Path mClipPath; - protected @Nullable String mClipPathRef; + protected @Nullable String mClipPath; private static final int PATH_TYPE_CLOSE = 1; private static final int PATH_TYPE_CURVETO = 3; @@ -47,10 +48,7 @@ public abstract class RNSVGVirtualNode extends LayoutShadowNode { private static final int CLIP_RULE_NONZERO = 1; protected final float mScale; - private float[] mClipData; private int mClipRule; - private boolean mClipRuleSet; - private boolean mClipDataSet; protected boolean mResponsible; protected int mCanvasX; protected int mCanvasY; @@ -58,9 +56,9 @@ public abstract class RNSVGVirtualNode extends LayoutShadowNode { protected int mCanvasHeight; protected String mName; - private RNSVGSvgViewShadowNode mSvgShadowNode; + private SvgViewShadowNode mSvgShadowNode; - public RNSVGVirtualNode() { + public VirtualNode() { mScale = DisplayMetricsHolder.getScreenDisplayMetrics().density; } @@ -96,14 +94,6 @@ public abstract class RNSVGVirtualNode extends LayoutShadowNode { canvas.restoreToCount(count); } - @ReactProp(name = "clipPath") - public void setClipPath(@Nullable ReadableArray clipPath) { - mClipData = PropHelper.toFloatArray(clipPath); - mClipDataSet = true; - setupClip(); - markUpdated(); - } - @ReactProp(name = "name") public void setName(String name) { mName = name; @@ -111,17 +101,15 @@ public abstract class RNSVGVirtualNode extends LayoutShadowNode { } - @ReactProp(name = "clipPathRef") - public void setClipPathRef(String clipPathRef) { - mClipPathRef = clipPathRef; + @ReactProp(name = "clipPath") + public void setClipPath(String clipPath) { + mClipPath = clipPath; markUpdated(); } @ReactProp(name = "clipRule", defaultInt = CLIP_RULE_NONZERO) - public void setClipRule(int clipRule) { + public void clipRule(int clipRule) { mClipRule = clipRule; - mClipRuleSet = true; - setupClip(); markUpdated(); } @@ -138,7 +126,7 @@ public abstract class RNSVGVirtualNode extends LayoutShadowNode { if (matrixSize == 6) { setupMatrix(); } else if (matrixSize != -1) { - throw new JSApplicationIllegalArgumentException("Transform matrices must be of size 6"); + FLog.w(ReactConstants.TAG, "RNSVG: Transform matrices must be of size 6"); } } else { mMatrix = null; @@ -153,24 +141,6 @@ public abstract class RNSVGVirtualNode extends LayoutShadowNode { markUpdated(); } - private void setupClip() { - if (mClipDataSet && mClipRuleSet) { - mClipPath = new Path(); - - switch (mClipRule) { - case CLIP_RULE_EVENODD: - mClipPath.setFillType(Path.FillType.EVEN_ODD); - break; - case CLIP_RULE_NONZERO: - break; - default: - throw new JSApplicationIllegalArgumentException( - "clipRule " + mClipRule + " unrecognized"); - } - createPath(mClipData, mClipPath); - } - } - protected void setupMatrix() { sRawMatrix[0] = sMatrixData[0]; sRawMatrix[1] = sMatrixData[2]; @@ -226,13 +196,28 @@ public abstract class RNSVGVirtualNode extends LayoutShadowNode { } protected @Nullable Path getClipPath(Canvas canvas, Paint paint) { - Path clip = mClipPath; - if (clip == null && mClipPathRef != null) { - RNSVGVirtualNode node = getSvgShadowNode().getDefinedClipPath(mClipPathRef); - clip = node.getPath(canvas, paint); + if (mClipPath != null) { + VirtualNode node = getSvgShadowNode().getDefinedClipPath(mClipPath); + + if (node != null) { + Path clipPath = node.getPath(canvas, paint); + switch (mClipRule) { + case CLIP_RULE_EVENODD: + clipPath.setFillType(Path.FillType.EVEN_ODD); + break; + case CLIP_RULE_NONZERO: + break; + default: + FLog.w(ReactConstants.TAG, "RNSVG: clipRule: " + mClipRule + " unrecognized"); + } + + return clipPath; + } else { + FLog.w(ReactConstants.TAG, "RNSVG: Undefined clipPath: " + mClipPath); + } } - return clip; + return null; } protected void clip(Canvas canvas, Paint paint) { @@ -255,21 +240,21 @@ public abstract class RNSVGVirtualNode extends LayoutShadowNode { abstract protected Path getPath(Canvas canvas, Paint paint); - protected RNSVGSvgViewShadowNode getSvgShadowNode() { + protected SvgViewShadowNode getSvgShadowNode() { if (mSvgShadowNode != null) { return mSvgShadowNode; } ReactShadowNode parent = getParent(); - while (!(parent instanceof RNSVGSvgViewShadowNode)) { + while (!(parent instanceof SvgViewShadowNode)) { if (parent == null) { return null; } else { parent = parent.getParent(); } } - mSvgShadowNode = (RNSVGSvgViewShadowNode) parent; + mSvgShadowNode = (SvgViewShadowNode) parent; return mSvgShadowNode; } @@ -290,24 +275,24 @@ public abstract class RNSVGVirtualNode extends LayoutShadowNode { } } - abstract public void mergeProperties(RNSVGVirtualNode target, ReadableArray mergeList, boolean inherited); + abstract public void mergeProperties(VirtualNode target, ReadableArray mergeList, boolean inherited); - abstract public void mergeProperties(RNSVGVirtualNode target, ReadableArray mergeList); + abstract public void mergeProperties(VirtualNode target, ReadableArray mergeList); abstract public void resetProperties(); protected interface NodeRunnable { - boolean run(RNSVGVirtualNode node); + boolean run(VirtualNode node); } protected void traverseChildren(NodeRunnable runner) { for (int i = 0; i < getChildCount(); i++) { ReactShadowNode child = getChildAt(i); - if (!(child instanceof RNSVGVirtualNode)) { + if (!(child instanceof VirtualNode)) { continue; } - if (!runner.run((RNSVGVirtualNode) child)) { + if (!runner.run((VirtualNode) child)) { break; } } diff --git a/elements/Circle.js b/elements/Circle.js index 02460c3c..926d62ee 100644 --- a/elements/Circle.js +++ b/elements/Circle.js @@ -1,5 +1,5 @@ import React from 'react'; -import createReactNativeComponentClass from 'react/lib/createReactNativeComponentClass'; +import createReactNativeComponentClass from 'react-native/Libraries/Renderer/src/renderers/native/createReactNativeComponentClass'; import Shape from './Shape'; import {CircleAttributes} from '../lib/attributes'; import {pathProps, numberProp} from '../lib/props'; diff --git a/elements/ClipPath.js b/elements/ClipPath.js index 8b1728ba..7c483c86 100644 --- a/elements/ClipPath.js +++ b/elements/ClipPath.js @@ -1,5 +1,5 @@ import React, {Component, PropTypes} from 'react'; -import createReactNativeComponentClass from 'react/lib/createReactNativeComponentClass'; +import createReactNativeComponentClass from 'react-native/Libraries/Renderer/src/renderers/native/createReactNativeComponentClass'; import {ClipPathAttributes} from '../lib/attributes'; class ClipPath extends Component{ diff --git a/elements/Defs.js b/elements/Defs.js index e07dafec..1d39f43c 100644 --- a/elements/Defs.js +++ b/elements/Defs.js @@ -1,7 +1,7 @@ import React, { Component, } from 'react'; -import createReactNativeComponentClass from 'react/lib/createReactNativeComponentClass'; +import createReactNativeComponentClass from 'react-native/Libraries/Renderer/src/renderers/native/createReactNativeComponentClass'; class Defs extends Component { static displayName = 'Defs'; diff --git a/elements/Ellipse.js b/elements/Ellipse.js index 9cd66c1f..70c2e0fe 100644 --- a/elements/Ellipse.js +++ b/elements/Ellipse.js @@ -1,5 +1,5 @@ import React from 'react'; -import createReactNativeComponentClass from 'react/lib/createReactNativeComponentClass'; +import createReactNativeComponentClass from 'react-native/Libraries/Renderer/src/renderers/native/createReactNativeComponentClass'; import Shape from './Shape'; import {pathProps, numberProp} from '../lib/props'; import {EllipseAttributes} from '../lib/attributes'; diff --git a/elements/G.js b/elements/G.js index 01a281c8..804a0c71 100644 --- a/elements/G.js +++ b/elements/G.js @@ -1,5 +1,5 @@ import React from 'react'; -import createReactNativeComponentClass from 'react/lib/createReactNativeComponentClass'; +import createReactNativeComponentClass from 'react-native/Libraries/Renderer/src/renderers/native/createReactNativeComponentClass'; import Shape from './Shape'; import {transformProps} from '../lib/props'; import {GroupAttributes} from '../lib/attributes'; diff --git a/elements/Image.js b/elements/Image.js index fb7f1d6f..f5fa6e0e 100644 --- a/elements/Image.js +++ b/elements/Image.js @@ -1,5 +1,5 @@ import React, {PropTypes} from 'react'; -import createReactNativeComponentClass from 'react/lib/createReactNativeComponentClass'; +import createReactNativeComponentClass from 'react-native/Libraries/Renderer/src/renderers/native/createReactNativeComponentClass'; import {ImageAttributes} from '../lib/attributes'; import {numberProp, touchableProps, responderProps} from '../lib/props'; import Shape from './Shape'; diff --git a/elements/Line.js b/elements/Line.js index a4a6cfe7..c63d06e2 100644 --- a/elements/Line.js +++ b/elements/Line.js @@ -1,5 +1,5 @@ import React from 'react'; -import createReactNativeComponentClass from 'react/lib/createReactNativeComponentClass'; +import createReactNativeComponentClass from 'react-native/Libraries/Renderer/src/renderers/native/createReactNativeComponentClass'; import {LineAttributes} from '../lib/attributes'; import Shape from './Shape'; import {pathProps, numberProp} from '../lib/props'; diff --git a/elements/LinearGradient.js b/elements/LinearGradient.js index 985e9fc5..4f0626ad 100644 --- a/elements/LinearGradient.js +++ b/elements/LinearGradient.js @@ -1,7 +1,7 @@ import React, {PropTypes} from 'react'; import {numberProp} from '../lib/props'; import Gradient from './Gradient'; -import createReactNativeComponentClass from 'react/lib/createReactNativeComponentClass'; +import createReactNativeComponentClass from 'react-native/Libraries/Renderer/src/renderers/native/createReactNativeComponentClass'; import {LinearGradientAttributes} from '../lib/attributes'; class LinearGradient extends Gradient{ diff --git a/elements/Path.js b/elements/Path.js index ff359442..d8ff1678 100644 --- a/elements/Path.js +++ b/elements/Path.js @@ -1,6 +1,5 @@ import React, {PropTypes} from 'react'; -import SerializablePath from '../lib/SerializablePath'; -import createReactNativeComponentClass from 'react/lib/createReactNativeComponentClass'; +import createReactNativeComponentClass from 'react-native/Libraries/Renderer/src/renderers/native/createReactNativeComponentClass'; import {PathAttributes} from '../lib/attributes'; import Shape from './Shape'; import {pathProps} from '../lib/props'; @@ -20,12 +19,11 @@ class Path extends Shape { render() { let props = this.props; - let d = new SerializablePath(props.d).toJSON(); return ( {this.root = ele;}} {...this.extractProps(props)} - d={d} + d={props.d} /> ); } diff --git a/elements/RadialGradient.js b/elements/RadialGradient.js index f11b7e45..7fad89ee 100644 --- a/elements/RadialGradient.js +++ b/elements/RadialGradient.js @@ -1,7 +1,7 @@ import React, {PropTypes} from 'react'; import {numberProp} from '../lib/props'; import Gradient from './Gradient'; -import createReactNativeComponentClass from 'react/lib/createReactNativeComponentClass'; +import createReactNativeComponentClass from 'react-native/Libraries/Renderer/src/renderers/native/createReactNativeComponentClass'; import {RadialGradientAttributes} from '../lib/attributes'; class RadialGradient extends Gradient{ diff --git a/elements/Rect.js b/elements/Rect.js index 6fc3dc08..b918edce 100644 --- a/elements/Rect.js +++ b/elements/Rect.js @@ -1,6 +1,6 @@ import React from 'react'; import './Path'; // must import Path first, don`t know why. without this will throw an `Super expression must either be null or a function, not undefined` -import createReactNativeComponentClass from 'react/lib/createReactNativeComponentClass'; +import createReactNativeComponentClass from 'react-native/Libraries/Renderer/src/renderers/native/createReactNativeComponentClass'; import {pathProps, numberProp} from '../lib/props'; import {RectAttributes} from '../lib/attributes'; import Shape from './Shape'; diff --git a/elements/Text.js b/elements/Text.js index 95da276f..f188d45b 100644 --- a/elements/Text.js +++ b/elements/Text.js @@ -1,5 +1,5 @@ import React, {PropTypes} from 'react'; -import createReactNativeComponentClass from 'react/lib/createReactNativeComponentClass'; +import createReactNativeComponentClass from 'react-native/Libraries/Renderer/src/renderers/native/createReactNativeComponentClass'; import extractText from '../lib/extract/extractText'; import {numberProp, pathProps, fontProps} from '../lib/props'; import {TextAttributes} from '../lib/attributes'; diff --git a/elements/Use.js b/elements/Use.js index f02645f4..75f01ab5 100644 --- a/elements/Use.js +++ b/elements/Use.js @@ -3,7 +3,7 @@ import {pathProps, numberProp} from '../lib/props'; import {UseAttributes} from '../lib/attributes'; import Shape from './Shape'; import React from 'react'; -import createReactNativeComponentClass from 'react/lib/createReactNativeComponentClass'; +import createReactNativeComponentClass from 'react-native/Libraries/Renderer/src/renderers/native/createReactNativeComponentClass'; const idExpReg = /^#(.+)$/; class Use extends Shape { diff --git a/elements/ViewBox.js b/elements/ViewBox.js index 40f0c807..8f5f62ac 100644 --- a/elements/ViewBox.js +++ b/elements/ViewBox.js @@ -1,8 +1,7 @@ import React, {Component, PropTypes} from 'react'; -import createReactNativeComponentClass from 'react/lib/createReactNativeComponentClass'; +import createReactNativeComponentClass from 'react-native/Libraries/Renderer/src/renderers/native/createReactNativeComponentClass'; import {ViewBoxAttributes} from '../lib/attributes'; import G from './G'; -import _ from 'lodash'; const meetOrSliceTypes = { meet: 0, @@ -10,12 +9,12 @@ const meetOrSliceTypes = { none: 2 }; -const alignEnum = _.reduce([ +const alignEnum = [ 'xMinYMin', 'xMidYMin', 'xMaxYMin', 'xMinYMid', 'xMidYMid', 'xMaxYMid', 'xMinYMax', 'xMidYMax', 'xMaxYMax', 'none' -], (prev, name) => { +].reduce((prev, name) => { prev[name] = name; return prev; }, {}); @@ -36,11 +35,11 @@ class ViewBox extends Component{ }; render() { - let {viewBox, preserveAspectRatio, name} = this.props; + const {viewBox, preserveAspectRatio, name} = this.props; let params = viewBox.trim().split(spacesRegExp); - if (params.length !== 4 || !_.some(params, param => param && numberRegExp.test(param))) { + if (params.length !== 4 || !params.some(param => param && numberRegExp.test(param))) { console.warn('`viewBox` expected a string like `minX minY width height`, but got:' + viewBox); return {this.props.children} diff --git a/ios/Brushes/RNSVGBaseBrush.m b/ios/Brushes/RNSVGBaseBrush.m index 2732fd52..5e587eb2 100644 --- a/ios/Brushes/RNSVGBaseBrush.m +++ b/ios/Brushes/RNSVGBaseBrush.m @@ -8,7 +8,7 @@ #import "RNSVGBaseBrush.h" #import "RCTConvert+RNSVG.h" -#import "RCTLog.h" +#import @implementation RNSVGBaseBrush diff --git a/ios/Brushes/RNSVGBrush.m b/ios/Brushes/RNSVGBrush.m index 10be584f..d99b4bfb 100644 --- a/ios/Brushes/RNSVGBrush.m +++ b/ios/Brushes/RNSVGBrush.m @@ -8,7 +8,7 @@ #import "RNSVGBrush.h" -#import "RCTDefines.h" +#import @implementation RNSVGBrush diff --git a/ios/Brushes/RNSVGPattern.m b/ios/Brushes/RNSVGPattern.m index 83676dcc..29cefdab 100644 --- a/ios/Brushes/RNSVGPattern.m +++ b/ios/Brushes/RNSVGPattern.m @@ -9,7 +9,7 @@ #import "RNSVGPattern.h" #import "RCTConvert+RNSVG.h" -#import "RCTLog.h" +#import @implementation RNSVGPattern { diff --git a/ios/Brushes/RNSVGSolidColorBrush.m b/ios/Brushes/RNSVGSolidColorBrush.m index 66607d9f..57a3f9c0 100644 --- a/ios/Brushes/RNSVGSolidColorBrush.m +++ b/ios/Brushes/RNSVGSolidColorBrush.m @@ -9,7 +9,7 @@ #import "RNSVGSolidColorBrush.h" #import "RCTConvert+RNSVG.h" -#import "RCTLog.h" +#import @implementation RNSVGSolidColorBrush { diff --git a/ios/Elements/RNSVGDefs.m b/ios/Elements/RNSVGDefs.m index 068b56e7..93ebdd5e 100644 --- a/ios/Elements/RNSVGDefs.m +++ b/ios/Elements/RNSVGDefs.m @@ -13,11 +13,10 @@ - (void)renderTo:(CGContextRef)context { - for (RNSVGNode *node in self.subviews) { - if ([node isKindOfClass:[RNSVGNode class]]) { - [node saveDefinition]; - } - } + [self traverseSubviews:^(RNSVGNode *node) { + [node saveDefinition]; + return YES; + }]; } - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event diff --git a/ios/Elements/RNSVGGroup.m b/ios/Elements/RNSVGGroup.m index a9452782..aae4bd58 100644 --- a/ios/Elements/RNSVGGroup.m +++ b/ios/Elements/RNSVGGroup.m @@ -19,7 +19,7 @@ { RNSVGSvgView* svg = [self getSvgView]; [self clip:context]; - + CGContextConcatCTM(context, transform); [self traverseSubviews:^(RNSVGNode *node) { if (node.responsible && !svg.responsible) { @@ -28,11 +28,11 @@ } return YES; }]; - + [self traverseSubviews:^(RNSVGNode *node) { - [node mergeProperties:self mergeList:self.ownedPropList inherited:YES]; + [node mergeProperties:self mergeList:self.attributeList inherited:YES]; [node renderTo:context]; - + if ([node isKindOfClass: [RNSVGRenderable class]]) { RNSVGRenderable *renderable = node; [self concatLayoutBoundingBox:[renderable getLayoutBoundingBox]]; @@ -59,19 +59,19 @@ CGPathAddPath(path, &transform, [node getPath:context]); return YES; }]; - + return (CGPathRef)CFAutorelease(path); } - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event withTransform:(CGAffineTransform)transform { CGAffineTransform matrix = CGAffineTransformConcat(self.matrix, transform); - + CGPathRef clip = [self getComputedClipPath]; if (clip && !CGPathContainsPoint(clip, nil, point, NO)) { return nil; } - + for (RNSVGNode *node in [self.subviews reverseObjectEnumerator]) { if ([node isKindOfClass:[RNSVGNode class]]) { if (event) { @@ -81,7 +81,7 @@ } UIView *view = [node hitTest: point withEvent:event withTransform:matrix]; - + if (view) { node.active = YES; if (node.responsible || (node != view)) { @@ -101,20 +101,12 @@ RNSVGSvgView* svg = [self getSvgView]; [svg defineTemplate:self templateName:self.name]; } - + [self traverseSubviews:^(RNSVGNode *node) { [node saveDefinition]; return YES; }]; - -} -- (void)mergeProperties:(RNSVGNode *)target mergeList:(NSArray *)mergeList -{ - [self traverseSubviews:^(RNSVGNode *node) { - [node mergeProperties:target mergeList:mergeList]; - return YES; - }]; } - (void)resetProperties @@ -125,16 +117,4 @@ }]; } -- (void)traverseSubviews:(BOOL (^)(__kindof RNSVGNode *node))block -{ - for (RNSVGNode *node in self.subviews) { - if ([node isKindOfClass:[RNSVGNode class]]) { - if (!block(node)) { - break; - } - } - } -} - - @end diff --git a/ios/Elements/RNSVGImage.m b/ios/Elements/RNSVGImage.m index bfa6aa14..efd87231 100644 --- a/ios/Elements/RNSVGImage.m +++ b/ios/Elements/RNSVGImage.m @@ -7,9 +7,9 @@ */ #import "RNSVGImage.h" -#import "RCTImageSource.h" #import "RCTConvert+RNSVG.h" -#import "RCTLog.h" +#import +#import #import "RNSVGViewBox.h" @implementation RNSVGImage diff --git a/ios/Elements/RNSVGPath.m b/ios/Elements/RNSVGPath.m index 010b3e63..32e24e17 100644 --- a/ios/Elements/RNSVGPath.m +++ b/ios/Elements/RNSVGPath.m @@ -15,116 +15,20 @@ if (d == _d) { return; } - + [self invalidate]; CGPathRelease(_d); _d = CGPathRetain(d); } -- (void)dealloc -{ - CGPathRelease(_d); -} - -- (void)renderLayerTo:(CGContextRef)context -{ - // todo: add detection if path has changed since last update. - self.d = [self getPath:context]; - - CGPathRef path = self.d; - [self setLayoutBoundingBox:CGPathGetBoundingBox(path)]; - - if ((!self.fill && !self.stroke) || !path) { - return; - } - - if ([self getSvgView].responsible) { - // Add path to hitArea - CGMutablePathRef hitAreaPath = CGPathCreateMutableCopy(path); - if (self.stroke) { - // Add stroke to hitArea - CGPathRef strokePath = CGPathCreateCopyByStrokingPath(hitAreaPath, nil, self.strokeWidth, self.strokeLinecap, self.strokeLinejoin, self.strokeMiterlimit); - CGPathAddPath(hitAreaPath, nil, strokePath); - CGPathRelease(strokePath); - } - - CGAffineTransform transform = self.matrix; - self.hitArea = CGPathCreateCopyByTransformingPath(hitAreaPath, &transform); - CGPathRelease(hitAreaPath); - } - - if (self.opacity == 0) { - return; - } - - CGPathDrawingMode mode = kCGPathStroke; - BOOL fillColor = YES; - - if (self.fill) { - mode = self.fillRule == kRNSVGCGFCRuleEvenodd ? kCGPathEOFill : kCGPathFill; - fillColor = [self.fill applyFillColor:context opacity:self.fillOpacity]; - - if (!fillColor) { - [self clip:context]; - - CGContextSaveGState(context); - CGContextAddPath(context, path); - CGContextClip(context); - RNSVGBrushConverter *brushConverter = [[self getSvgView] getDefinedBrushConverter:[self.fill brushRef]]; - [self.fill paint:context opacity:self.fillOpacity brushConverter:brushConverter]; - CGContextRestoreGState(context); - if (!self.stroke) { - return; - } - } - } - - if (self.stroke) { - CGContextSetLineWidth(context, self.strokeWidth); - CGContextSetLineCap(context, self.strokeLinecap); - CGContextSetLineJoin(context, self.strokeLinejoin); - RNSVGCGFloatArray dash = self.strokeDasharray; - - if (dash.count) { - CGContextSetLineDash(context, self.strokeDashoffset, dash.array, dash.count); - } - - if (!fillColor) { - CGContextAddPath(context, path); - CGContextReplacePathWithStrokedPath(context); - CGContextClip(context); - } - - if ([self.stroke applyStrokeColor:context opacity:self.strokeOpacity]) { - if (mode == kCGPathFill) { - mode = kCGPathFillStroke; - } else if (mode == kCGPathEOFill) { - mode = kCGPathEOFillStroke; - } - } else { - // draw fill - [self clip:context]; - CGContextAddPath(context, path); - CGContextDrawPath(context, mode); - - // draw stroke - CGContextAddPath(context, path); - CGContextReplacePathWithStrokedPath(context); - CGContextClip(context); - RNSVGBrushConverter *brushConverter = [[self getSvgView] getDefinedBrushConverter:[self.stroke brushRef]]; - [self.stroke paint:context opacity:self.strokeOpacity brushConverter:brushConverter]; - return; - } - } - - [self clip:context]; - CGContextAddPath(context, path); - CGContextDrawPath(context, mode); -} - - (CGPathRef)getPath:(CGContextRef)context { return self.d; } +- (void)dealloc +{ + CGPathRelease(_d); +} + @end diff --git a/ios/Elements/RNSVGSvgView.m b/ios/Elements/RNSVGSvgView.m index 544db77c..5cfba6dc 100644 --- a/ios/Elements/RNSVGSvgView.m +++ b/ios/Elements/RNSVGSvgView.m @@ -9,7 +9,7 @@ #import "RNSVGSvgView.h" #import "RNSVGNode.h" -#import "RCTLog.h" +#import @implementation RNSVGSvgView { diff --git a/ios/Elements/RNSVGUse.m b/ios/Elements/RNSVGUse.m index e7237b87..e56382b6 100644 --- a/ios/Elements/RNSVGUse.m +++ b/ios/Elements/RNSVGUse.m @@ -6,7 +6,7 @@ * LICENSE file in the root directory of this source tree. */ #import "RNSVGUse.h" -#import "RCTLog.h" +#import @implementation RNSVGUse @@ -27,7 +27,7 @@ if (template) { [self beginTransparencyLayer:context]; [self clip:context]; - [template mergeProperties:self mergeList:self.ownedPropList]; + [template mergeProperties:self mergeList:self.attributeList inherited:YES]; [template renderTo:context]; [template resetProperties]; [self endTransparencyLayer:context]; diff --git a/ios/RNSVG.xcodeproj/project.pbxproj b/ios/RNSVG.xcodeproj/project.pbxproj index 3727f6cf..79ed9360 100644 --- a/ios/RNSVG.xcodeproj/project.pbxproj +++ b/ios/RNSVG.xcodeproj/project.pbxproj @@ -51,6 +51,7 @@ 10ED4A9E1CF0656A0078BC02 /* RNSVGClipPathManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10ED4A9D1CF0656A0078BC02 /* RNSVGClipPathManager.m */; }; 10ED4AA21CF078830078BC02 /* RNSVGNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 10ED4AA11CF078830078BC02 /* RNSVGNode.m */; }; 10FDEEB21D3FB60500A5C46C /* RNSVGBaseBrush.m in Sources */ = {isa = PBXBuildFile; fileRef = 10FDEEB11D3FB60500A5C46C /* RNSVGBaseBrush.m */; }; + 7F9CDAFA1E1F809C00E0C805 /* RNSVGPathParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F9CDAF91E1F809C00E0C805 /* RNSVGPathParser.m */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -160,8 +161,12 @@ 10FDEEB01D3FB60500A5C46C /* RNSVGBaseBrush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGBaseBrush.h; sourceTree = ""; }; 10FDEEB11D3FB60500A5C46C /* RNSVGBaseBrush.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGBaseBrush.m; sourceTree = ""; }; 10FDEEB31D3FBED400A5C46C /* RNSVGBrushType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGBrushType.h; sourceTree = ""; }; + 7F888B9C1DD378000038D083 /* RNSVGGlyphPoint.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNSVGGlyphPoint.h; path = Utils/RNSVGGlyphPoint.h; sourceTree = ""; }; 7FF070191DC249BE000E28A0 /* RNSVGTextAnchor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGTextAnchor.h; path = Utils/RNSVGTextAnchor.h; sourceTree = ""; }; + 7F9CDAF81E1F809C00E0C805 /* RNSVGPathParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGPathParser.h; path = Utils/RNSVGPathParser.h; sourceTree = ""; }; + 7F9CDAF91E1F809C00E0C805 /* RNSVGPathParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGPathParser.m; path = Utils/RNSVGPathParser.m; sourceTree = ""; }; + /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -320,15 +325,21 @@ 1039D29A1CE7212C001E90A8 /* Utils */ = { isa = PBXGroup; children = ( + 1039D29E1CE72177001E90A8 /* RNSVGCGFloatArray.h */, 10ABC7381D43982B006CCF6E /* RNSVGVBMOS.h */, 7FF070191DC249BE000E28A0 /* RNSVGTextAnchor.h */, 10ABC7371D439779006CCF6E /* RNSVGCGFCRule.h */, 1039D2AE1CE72F27001E90A8 /* RNSVGPercentageConverter.h */, 1039D2AF1CE72F27001E90A8 /* RNSVGPercentageConverter.m */, + 7F9CDAF81E1F809C00E0C805 /* RNSVGPathParser.h */, + 7F9CDAF91E1F809C00E0C805 /* RNSVGPathParser.m */, 1039D29B1CE72177001E90A8 /* RCTConvert+RNSVG.h */, 1039D29C1CE72177001E90A8 /* RCTConvert+RNSVG.m */, +<<<<<<< HEAD 1039D29E1CE72177001E90A8 /* RNSVGCGFloatArray.h */, 7F888B9C1DD378000038D083 /* RNSVGGlyphPoint.h */, +======= +>>>>>>> master ); name = Utils; sourceTree = ""; @@ -426,6 +437,7 @@ 10BA0D351CE74E3100887C2B /* RNSVGEllipseManager.m in Sources */, 1039D2A01CE72177001E90A8 /* RCTConvert+RNSVG.m in Sources */, 0CF68B0B1AF0549300FF9E5C /* RNSVGBrush.m in Sources */, + 7F9CDAFA1E1F809C00E0C805 /* RNSVGPathParser.m in Sources */, 10BA0D361CE74E3100887C2B /* RNSVGGroupManager.m in Sources */, 10BA0D4A1CE74E3D00887C2B /* RNSVGLine.m in Sources */, 10FDEEB21D3FB60500A5C46C /* RNSVGBaseBrush.m in Sources */, @@ -528,10 +540,11 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../../react-native/React/**", + "$(BUILT_PRODUCTS_DIR)/usr/local/include", ); OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = RNSVG; + PUBLIC_HEADERS_FOLDER_PATH = /usr/local/include/; SKIP_INSTALL = YES; }; name = Debug; @@ -542,10 +555,11 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../../react-native/React/**", + "$(BUILT_PRODUCTS_DIR)/usr/local/include", ); OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = RNSVG; + PUBLIC_HEADERS_FOLDER_PATH = /usr/local/include/; SKIP_INSTALL = YES; }; name = Release; diff --git a/ios/RNSVGNode.h b/ios/RNSVGNode.h index 6d75ea92..42ab8d54 100644 --- a/ios/RNSVGNode.h +++ b/ios/RNSVGNode.h @@ -6,7 +6,7 @@ * LICENSE file in the root directory of this source tree. */ -#import "UIView+React.h" +#import #import "RNSVGCGFCRule.h" #import "RNSVGSvgView.h" @@ -20,7 +20,7 @@ @property (nonatomic, strong) NSString *name; @property (nonatomic, assign) CGFloat opacity; @property (nonatomic, assign) RNSVGCGFCRule clipRule; -@property (nonatomic, assign) NSString *clipPath; +@property (nonatomic, strong) NSString *clipPath; @property (nonatomic, assign) BOOL responsible; @property (nonatomic, assign) CGAffineTransform matrix; @property (nonatomic, assign) BOOL active; @@ -36,6 +36,10 @@ */ - (void)renderLayerTo:(CGContextRef)context; +- (CGPathRef)getClipPath; + +- (CGPathRef)getClipPath:(CGContextRef)context; + /** * clip node by clipPath */ @@ -79,4 +83,6 @@ - (void)endTransparencyLayer:(CGContextRef)context; +- (void)traverseSubviews:(BOOL (^)(RNSVGNode *node))block; + @end diff --git a/ios/RNSVGNode.m b/ios/RNSVGNode.m index ee4be007..0d07c0d9 100644 --- a/ios/RNSVGNode.m +++ b/ios/RNSVGNode.m @@ -13,7 +13,7 @@ @implementation RNSVGNode { BOOL _transparent; - CGPathRef _computedClipPath; + CGPathRef _cachedClipPath; } - (instancetype)init @@ -84,9 +84,10 @@ if (_clipPath == clipPath) { return; } - [self invalidate]; + CGPathRelease(_cachedClipPath); + _cachedClipPath = nil; _clipPath = clipPath; - + [self invalidate]; } - (void)beginTransparencyLayer:(CGContextRef)context @@ -108,38 +109,35 @@ // abstract } +- (CGPathRef)getClipPath +{ + return _cachedClipPath; +} + +- (CGPathRef)getClipPath:(CGContextRef)context +{ + if (self.clipPath && !_cachedClipPath) { + CGPathRelease(_cachedClipPath); + _cachedClipPath = CGPathRetain([[[self getSvgView] getDefinedClipPath:self.clipPath] getPath:context]); + } + + return [self getClipPath]; +} + - (void)clip:(CGContextRef)context { - if (self.clipPath) { - CGPathRef clip = [[[self getSvgView] getDefinedClipPath:self.clipPath] getPath:context]; - - if (!clip) { - // TODO: WARNING ABOUT THIS - return; - } - - CGContextAddPath(context, clip); + CGPathRef clipPath = [self getClipPath:context]; + + if (clipPath) { + CGContextAddPath(context, clipPath); if (self.clipRule == kRNSVGCGFCRuleEvenodd) { CGContextEOClip(context); } else { CGContextClip(context); } - - CGAffineTransform matrix = self.matrix; - [self computeClipPath:CGPathCreateCopyByTransformingPath(clip, &matrix)]; } } -- (CGPathRef)getComputedClipPath{ - return _computedClipPath; -} - -- (void)computeClipPath:(CGPathRef)computedClipPath -{ - CGPathRelease(_computedClipPath); - _computedClipPath = computedClipPath; -} - - (CGPathRef)getPath: (CGContextRef) context { // abstract @@ -151,8 +149,10 @@ // abstract } +// hitTest delagate - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { + // abstract return nil; } @@ -177,7 +177,7 @@ { if (self.name) { RNSVGSvgView* svg = [self getSvgView]; - [svg defineTemplate:self templateName:self.name]; + [svg defineTemplate:self templateRef:self.name]; } } @@ -191,6 +191,17 @@ // abstract } +- (void)traverseSubviews:(BOOL (^)(RNSVGNode *node))block +{ + for (RNSVGNode *node in self.subviews) { + if ([node isKindOfClass:[RNSVGNode class]]) { + if (!block(node)) { + break; + } + } + } +} + - (void)resetProperties { // abstract @@ -198,7 +209,7 @@ - (void)dealloc { - CGPathRelease(_computedClipPath); + CGPathRelease(_cachedClipPath); } @end diff --git a/ios/RNSVGRenderable.h b/ios/RNSVGRenderable.h index 14ee14bf..6b40fad5 100644 --- a/ios/RNSVGRenderable.h +++ b/ios/RNSVGRenderable.h @@ -28,7 +28,7 @@ @property (nonatomic, assign) CGFloat strokeDashoffset; @property (nonatomic, assign) CGPathRef hitArea; @property (nonatomic, copy) NSArray *propList; -@property (nonatomic, strong) NSMutableArray *ownedPropList; +@property (nonatomic, strong) NSArray *attributeList; - (void)setContextBoundingBox:(CGRect)contextBoundingBox; - (CGRect)getContextBoundingBox; diff --git a/ios/RNSVGRenderable.m b/ios/RNSVGRenderable.m index 268e6a20..02e11c67 100644 --- a/ios/RNSVGRenderable.m +++ b/ios/RNSVGRenderable.m @@ -12,7 +12,7 @@ @implementation RNSVGRenderable { NSMutableDictionary *_originProperties; - NSArray *_changedList; + NSArray *_lastMergedList; RNSVGPercentageConverter *_widthConverter; RNSVGPercentageConverter *_heightConverter; CGRect _contextBoundingBox; @@ -24,7 +24,7 @@ if (self = [super init]) { _fillOpacity = 1; _strokeOpacity = 1; - _strokeWidth = 1; + _strokeWidth = 0; } return self; } @@ -136,7 +136,7 @@ if (hitArea == _hitArea) { return; } - + CGPathRelease(_hitArea); _hitArea = CGPathRetain(CFAutorelease(hitArea)); } @@ -146,8 +146,8 @@ if (propList == _propList) { return; } + _attributeList = [propList copy]; _propList = propList; - self.ownedPropList = [propList mutableCopy]; [self invalidate]; } @@ -165,7 +165,7 @@ CGContextSaveGState(context); CGContextConcatCTM(context, self.matrix); CGContextSetAlpha(context, self.opacity); - + [self beginTransparencyLayer:context]; [self renderLayerTo:context]; [self endTransparencyLayer:context]; @@ -173,13 +173,105 @@ CGContextRestoreGState(context); } +- (void)renderLayerTo:(CGContextRef)context +{ + // todo: add detection if path has changed since last update. + CGPathRef path = [self getPath:context]; + if ((!self.fill && !self.stroke) || !path) { + return; + } + + if ([self getSvgView].responsible) { + // Add path to hitArea + CGMutablePathRef hitArea = CGPathCreateMutableCopy(path); + if (self.stroke && self.strokeWidth) { + // Add stroke to hitArea + CGPathRef strokePath = CGPathCreateCopyByStrokingPath(hitArea, nil, self.strokeWidth, self.strokeLinecap, self.strokeLinejoin, self.strokeMiterlimit); + CGPathAddPath(hitArea, nil, strokePath); + CGPathRelease(strokePath); + } + + self.hitArea = CFAutorelease(CGPathCreateCopy(hitArea)); + CGPathRelease(hitArea); + } + + if (self.opacity == 0) { + return; + } + + CGPathDrawingMode mode = kCGPathStroke; + BOOL fillColor = YES; + + if (self.fill) { + mode = self.fillRule == kRNSVGCGFCRuleEvenodd ? kCGPathEOFill : kCGPathFill; + fillColor = [self.fill applyFillColor:context opacity:self.fillOpacity]; + + if (!fillColor) { + [self clip:context]; + + CGContextSaveGState(context); + CGContextAddPath(context, path); + CGContextClip(context); + RNSVGBrushConverter *brushConverter = [[self getSvgView] getDefinedBrushConverter:[self.fill brushRef]]; + [self.fill paint:context opacity:self.fillOpacity brushConverter:brushConverter]; + CGContextRestoreGState(context); + if (!self.stroke) { + return; + } + } + } + + if (self.stroke && self.strokeWidth) { + CGContextSetLineWidth(context, self.strokeWidth); + CGContextSetLineCap(context, self.strokeLinecap); + CGContextSetLineJoin(context, self.strokeLinejoin); + RNSVGCGFloatArray dash = self.strokeDasharray; + + if (dash.count) { + CGContextSetLineDash(context, self.strokeDashoffset, dash.array, dash.count); + } + + if (!fillColor) { + CGContextAddPath(context, path); + CGContextReplacePathWithStrokedPath(context); + CGContextClip(context); + } + + if ([self.stroke applyStrokeColor:context opacity:self.strokeOpacity]) { + if (mode == kCGPathFill) { + mode = kCGPathFillStroke; + } else if (mode == kCGPathEOFill) { + mode = kCGPathEOFillStroke; + } + } else { + // draw fill + [self clip:context]; + CGContextAddPath(context, path); + CGContextDrawPath(context, mode); + + // draw stroke + CGContextAddPath(context, path); + CGContextReplacePathWithStrokedPath(context); + CGContextClip(context); + RNSVGBrushConverter *brushConverter = [[self getSvgView] getDefinedBrushConverter:[self.stroke brushRef]]; + [self.stroke paint:context opacity:self.strokeOpacity brushConverter:brushConverter]; + return; + } + } + + [self clip:context]; + CGContextAddPath(context, path); + CGContextDrawPath(context, mode); +} + + // hitTest delagate - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { return [self hitTest:point withEvent:event withTransform:CGAffineTransformMakeRotation(0)]; } -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event withTransform:(CGAffineTransform)transfrom +- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event withTransform:(CGAffineTransform)transform { if (self.active) { if (!event) { @@ -188,15 +280,21 @@ return self; } - CGPathRef hitArea = CGPathCreateCopyByTransformingPath(self.hitArea, &transfrom); + CGAffineTransform matrix = CGAffineTransformConcat(self.matrix, transform); + CGPathRef hitArea = CGPathCreateCopyByTransformingPath(self.hitArea, &matrix); BOOL contains = CGPathContainsPoint(hitArea, nil, point, NO); CGPathRelease(hitArea); + if (contains) { - CGPathRef clipPath = [self getComputedClipPath]; - if (clipPath) { - return CGPathContainsPoint(clipPath, nil, point, NO) ? self : nil; - } else { + 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 { return nil; @@ -245,49 +343,38 @@ - (void)mergeProperties:(__kindof RNSVGNode *)target mergeList:(NSArray *)mergeList inherited:(BOOL)inherited { + _lastMergedList = mergeList; + if (mergeList.count == 0) { return; } - - self.ownedPropList = [self.propList mutableCopy]; - - if (!inherited) { - _originProperties = [[NSMutableDictionary alloc] init]; - _changedList = mergeList; - } - + + NSMutableArray* attributeList = [self.propList mutableCopy]; + + _originProperties = [[NSMutableDictionary alloc] init]; + for (NSString *key in mergeList) { if (inherited) { - [self inheritProperty:target propName:key]; + if (![attributeList containsObject:key]) { + [attributeList addObject:key]; + [_originProperties setValue:[self valueForKey:key] forKey:key]; + [self setValue:[target valueForKey:key] forKey:key]; + } } else { [_originProperties setValue:[self valueForKey:key] forKey:key]; [self setValue:[target valueForKey:key] forKey:key]; } } + + _attributeList = [attributeList copy]; } - (void)resetProperties { - if (_changedList) { - for (NSString *key in _changedList) { - [self setValue:[_originProperties valueForKey:key] forKey:key]; - } + for (NSString *key in _lastMergedList) { + [self setValue:[_originProperties valueForKey:key] forKey:key]; } - _changedList = nil; -} - -- (void)inheritProperty:(__kindof RNSVGNode *)parent propName:(NSString *)propName -{ - if (![self.ownedPropList containsObject:propName]) { - // add prop to props - [self.ownedPropList addObject:propName]; - [self setValue:[parent valueForKey:propName] forKey:propName]; - } -} - -- (void)renderLayerTo:(CGContextRef)context -{ - // abstract + _attributeList = [_propList copy]; } @end diff --git a/ios/RNSVGViewBox.m b/ios/RNSVGViewBox.m index e803f571..0b90683f 100644 --- a/ios/RNSVGViewBox.m +++ b/ios/RNSVGViewBox.m @@ -160,7 +160,7 @@ return CGAffineTransformTranslate(transform, -translateX * (_fromSymbol ? scaleX : 1), -translateY * (_fromSymbol ? scaleY : 1)); } -- (void)mergeProperties:(__kindof RNSVGNode *)target mergeList:(NSArray *)mergeList +- (void)mergeProperties:(__kindof RNSVGNode *)target mergeList:(NSArray *)mergeList inherited:(BOOL)inherited { if ([target isKindOfClass:[RNSVGUse class]]) { RNSVGUse *use = target; @@ -172,8 +172,10 @@ - (void)resetProperties { - self.width = self.height = nil; - _fromSymbol = NO; + if (_fromSymbol) { + self.width = self.height = nil; + _fromSymbol = NO; + } } @end diff --git a/ios/Shapes/RNSVGCircle.h b/ios/Shapes/RNSVGCircle.h index b1f086b3..8d195803 100644 --- a/ios/Shapes/RNSVGCircle.h +++ b/ios/Shapes/RNSVGCircle.h @@ -10,7 +10,7 @@ #import "RNSVGPath.h" -@interface RNSVGCircle : RNSVGPath +@interface RNSVGCircle : RNSVGRenderable @property (nonatomic, strong) NSString* cx; @property (nonatomic, strong) NSString* cy; diff --git a/ios/Shapes/RNSVGCircle.m b/ios/Shapes/RNSVGCircle.m index a8791cc5..b95f0fac 100644 --- a/ios/Shapes/RNSVGCircle.m +++ b/ios/Shapes/RNSVGCircle.m @@ -7,7 +7,7 @@ */ #import "RNSVGCircle.h" -#import "RCTLog.h" +#import @implementation RNSVGCircle diff --git a/ios/Shapes/RNSVGEllipse.h b/ios/Shapes/RNSVGEllipse.h index 17d279be..67c246a3 100644 --- a/ios/Shapes/RNSVGEllipse.h +++ b/ios/Shapes/RNSVGEllipse.h @@ -10,7 +10,7 @@ #import "RNSVGPath.h" -@interface RNSVGEllipse : RNSVGPath +@interface RNSVGEllipse : RNSVGRenderable @property (nonatomic, strong) NSString* cx; @property (nonatomic, strong) NSString* cy; @property (nonatomic, strong) NSString* rx; diff --git a/ios/Shapes/RNSVGEllipse.m b/ios/Shapes/RNSVGEllipse.m index 814c1e85..36eb11c7 100644 --- a/ios/Shapes/RNSVGEllipse.m +++ b/ios/Shapes/RNSVGEllipse.m @@ -7,7 +7,7 @@ */ #import "RNSVGEllipse.h" -#import "RCTLog.h" +#import @implementation RNSVGEllipse diff --git a/ios/Shapes/RNSVGLine.h b/ios/Shapes/RNSVGLine.h index 0263e3a1..e1df2ccb 100644 --- a/ios/Shapes/RNSVGLine.h +++ b/ios/Shapes/RNSVGLine.h @@ -10,7 +10,7 @@ #import "RNSVGPath.h" -@interface RNSVGLine : RNSVGPath +@interface RNSVGLine : RNSVGRenderable @property (nonatomic, strong) NSString* x1; @property (nonatomic, strong) NSString* y1; @property (nonatomic, strong) NSString* x2; diff --git a/ios/Shapes/RNSVGLine.m b/ios/Shapes/RNSVGLine.m index 2f7df69b..4fe744b3 100644 --- a/ios/Shapes/RNSVGLine.m +++ b/ios/Shapes/RNSVGLine.m @@ -7,7 +7,7 @@ */ #import "RNSVGLine.h" -#import "RCTLog.h" +#import @implementation RNSVGLine diff --git a/ios/Shapes/RNSVGRect.h b/ios/Shapes/RNSVGRect.h index c63c1b82..224aeef7 100644 --- a/ios/Shapes/RNSVGRect.h +++ b/ios/Shapes/RNSVGRect.h @@ -10,7 +10,7 @@ #import "RNSVGPath.h" -@interface RNSVGRect : RNSVGPath +@interface RNSVGRect : RNSVGRenderable @property (nonatomic, strong) NSString* x; @property (nonatomic, strong) NSString* y; diff --git a/ios/Shapes/RNSVGRect.m b/ios/Shapes/RNSVGRect.m index 4e3d3e79..8136bdce 100644 --- a/ios/Shapes/RNSVGRect.m +++ b/ios/Shapes/RNSVGRect.m @@ -7,7 +7,7 @@ */ #import "RNSVGRect.h" -#import "RCTLog.h" +#import @implementation RNSVGRect diff --git a/ios/Utils/RCTConvert+RNSVG.h b/ios/Utils/RCTConvert+RNSVG.h index 959c1e5e..c8d7068b 100644 --- a/ios/Utils/RCTConvert+RNSVG.h +++ b/ios/Utils/RCTConvert+RNSVG.h @@ -10,7 +10,8 @@ #import #import "RCTConvert+RNSVG.h" #import "RNSVGCGFloatArray.h" -#import "RCTConvert.h" +#import "RNSVGTextFrame.h" +#import #import "RNSVGCGFCRule.h" #import "RNSVGVBMOS.h" #import "RNSVGTextAnchor.h" @@ -19,13 +20,19 @@ @interface RCTConvert (RNSVG) +<<<<<<< HEAD + (CGPathRef)CGPath:(id)json; + (RNSVGTextAnchor)RNSVGTextAnchor:(id)json; +======= ++ (CGPathRef)CGPath:(NSString *)d; ++ (CTTextAlignment)CTTextAlignment:(id)json; +>>>>>>> master + (RNSVGCGFCRule)RNSVGCGFCRule:(id)json; + (RNSVGVBMOS)RNSVGVBMOS:(id)json; + (RNSVGCGFloatArray)RNSVGCGFloatArray:(id)json; + (RNSVGBrush *)RNSVGBrush:(id)json; + + (NSArray *)RNSVGBezier:(id)json; + (CGRect)CGRect:(id)json offset:(NSUInteger)offset; + (CGColorRef)CGColor:(id)json offset:(NSUInteger)offset; diff --git a/ios/Utils/RCTConvert+RNSVG.m b/ios/Utils/RCTConvert+RNSVG.m index 397919c0..e1778da0 100644 --- a/ios/Utils/RCTConvert+RNSVG.m +++ b/ios/Utils/RCTConvert+RNSVG.m @@ -11,55 +11,17 @@ #import "RNSVGBaseBrush.h" #import "RNSVGPattern.h" #import "RNSVGSolidColorBrush.h" -#import "RCTLog.h" +#import +#import "RNSVGCGFCRule.h" +#import "RNSVGVBMOS.h" +#import +#import "RNSVGPathParser.h" @implementation RCTConvert (RNSVG) -+ (CGPathRef)CGPath:(id)json ++ (CGPathRef)CGPath:(NSString *)d { - NSArray *arr = [self NSNumberArray:json]; - - NSUInteger count = [arr count]; - -#define NEXT_VALUE [self double:arr[i++]] - - CGMutablePathRef path = CGPathCreateMutable(); - CGPathMoveToPoint(path, nil, 0, 0); - - @try { - NSUInteger i = 0; - while (i < count) { - NSUInteger type = [arr[i++] unsignedIntegerValue]; - switch (type) { - case 0: - CGPathMoveToPoint(path, nil, NEXT_VALUE, NEXT_VALUE); - break; - case 1: - CGPathCloseSubpath(path); - break; - case 2: - CGPathAddLineToPoint(path, nil, NEXT_VALUE, NEXT_VALUE); - break; - case 3: - CGPathAddCurveToPoint(path, nil, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE); - break; - case 4: - CGPathAddArc(path, NULL, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE == 0); - break; - default: - RCTLogError(@"Invalid CGPath type %zd at element %zd of %@", type, i, arr); - CGPathRelease(path); - return nil; - } - } - } - @catch (NSException *exception) { - RCTLogError(@"Invalid CGPath format: %@", arr); - CGPathRelease(path); - return nil; - } - - return (CGPathRef)CFAutorelease(path); + return [[[RNSVGPathParser alloc] initWithPathString: d] getPath]; } RCT_ENUM_CONVERTER(RNSVGCGFCRule, (@{ diff --git a/ios/Utils/RNSVGPathParser.h b/ios/Utils/RNSVGPathParser.h new file mode 100644 index 00000000..1815390c --- /dev/null +++ b/ios/Utils/RNSVGPathParser.h @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2015-present, Horcrux. + * All rights reserved. + * + * This source code is licensed under the MIT-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import +#import + +@interface RNSVGPathParser : NSObject + +- (instancetype) initWithPathString:(NSString *)d; +- (CGPathRef)getPath; + +@end diff --git a/ios/Utils/RNSVGPathParser.m b/ios/Utils/RNSVGPathParser.m new file mode 100644 index 00000000..3c07a9ca --- /dev/null +++ b/ios/Utils/RNSVGPathParser.m @@ -0,0 +1,379 @@ +/** + * Copyright (c) 2015-present, Horcrux. + * All rights reserved. + * + * This source code is licensed under the MIT-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RNSVGPathParser.h" +#import + +@implementation RNSVGPathParser : NSObject +{ + NSString* _d; + NSString* _originD; + NSRegularExpression* _pathRegularExpression; + double _penX; + double _penY; + double _penDownX; + double _penDownY; + double _pivotX; + double _pivotY; + BOOL _valid; + BOOL _penDownSet; +} + +- (instancetype) initWithPathString:(NSString *)d +{ + if (self = [super init]) { + NSRegularExpression* decimalRegularExpression = [[NSRegularExpression alloc] initWithPattern:@"(\\.\\d+)(?=\\-?\\.)" options:0 error:nil]; + _originD = d; + _d = [decimalRegularExpression stringByReplacingMatchesInString:d options:0 range:NSMakeRange(0, [d length]) withTemplate:@"$1\,"]; + _pathRegularExpression = [[NSRegularExpression alloc] initWithPattern:@"[a-df-z]|[\\-+]?(?:[\\d.]e[\\-+]?|[^\\s\\-+,a-z])+" options:NSRegularExpressionCaseInsensitive error:nil]; + } + return self; +} + +- (CGPathRef)getPath +{ + CGMutablePathRef path = CGPathCreateMutable(); + NSArray* results = [_pathRegularExpression matchesInString:_d options:0 range:NSMakeRange(0, [_d length])]; + + int count = [results count]; + if (count) { + NSUInteger i = 0; + #define NEXT_VALUE [self getNextValue:results[i++]] + #define NEXT_DOUBLE [self double:NEXT_VALUE] + #define NEXT_BOOL [self bool:NEXT_VALUE] + NSString* lastCommand; + NSString* command = NEXT_VALUE; + + @try { + while (command) { + if ([command isEqualToString:@"m"]) { // moveTo command + [self move:path x:NEXT_DOUBLE y:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"M"]) { + [self moveTo:path x:NEXT_DOUBLE y:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"l"]) { // lineTo command + [self line:path x:NEXT_DOUBLE y:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"L"]) { + [self lineTo:path x:NEXT_DOUBLE y:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"h"]) { // horizontalTo command + [self line:path x:NEXT_DOUBLE y:0]; + } else if ([command isEqualToString:@"H"]) { + [self lineTo:path x:NEXT_DOUBLE y:_penY]; + } else if ([command isEqualToString:@"v"]) { // verticalTo command + [self line:path x:0 y:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"V"]) { + [self lineTo:path x:_penX y:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"c"]) { // curveTo command + [self curve:path c1x:NEXT_DOUBLE c1y:NEXT_DOUBLE c2x:NEXT_DOUBLE c2y:NEXT_DOUBLE ex:NEXT_DOUBLE ey:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"C"]) { + [self curveTo:path c1x:NEXT_DOUBLE c1y:NEXT_DOUBLE c2x:NEXT_DOUBLE c2y:NEXT_DOUBLE ex:NEXT_DOUBLE ey:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"s"]) { // smoothCurveTo command + [self smoothCurve:path c1x:NEXT_DOUBLE c1y:NEXT_DOUBLE ex:NEXT_DOUBLE ey:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"S"]) { + [self smoothCurveTo:path c1x:NEXT_DOUBLE c1y:NEXT_DOUBLE ex:NEXT_DOUBLE ey:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"q"]) { // quadraticBezierCurveTo command + [self quadraticBezierCurve:path c1x:NEXT_DOUBLE c1y:NEXT_DOUBLE c2x:NEXT_DOUBLE c2y:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"Q"]) { + [self quadraticBezierCurveTo:path c1x:NEXT_DOUBLE c1y:NEXT_DOUBLE c2x:NEXT_DOUBLE c2y:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"t"]) {// smoothQuadraticBezierCurveTo command + [self smoothQuadraticBezierCurve:path c1x:NEXT_DOUBLE c1y:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"T"]) { + [self smoothQuadraticBezierCurveTo:path c1x:NEXT_DOUBLE c1y:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"a"]) { // arcTo command + [self arc:path rx:NEXT_DOUBLE ry:NEXT_DOUBLE rotation:NEXT_DOUBLE outer:NEXT_BOOL clockwise:NEXT_BOOL x:NEXT_DOUBLE y:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"A"]) { + [self arcTo:path rx:NEXT_DOUBLE ry:NEXT_DOUBLE rotation:NEXT_DOUBLE outer:NEXT_BOOL clockwise:NEXT_BOOL x:NEXT_DOUBLE y:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"z"]) { // close command + [self close:path]; + } else if ([command isEqualToString:@"Z"]) { + [self close:path]; + } else { + command = lastCommand; + i--; + continue; + } + + lastCommand = command; + if ([lastCommand isEqualToString:@"m"]) { + lastCommand = @"l"; + } else if ([lastCommand isEqualToString:@"M"]) { + lastCommand = @"L"; + } + + command = i < count ? NEXT_VALUE : nil; + } + } @catch (NSException *exception) { + RCTLogWarn(@"Invalid CGPath format: %@", _originD); + CGPathRelease(path); + return nil; + } + + } + + return (CGPathRef)CFAutorelease(path); +} + +- (NSString *)getNextValue:(NSTextCheckingResult *)result +{ + if (!result) { + return nil; + } + return [_d substringWithRange:NSMakeRange(result.range.location, result.range.length)]; +} + +- (double)double:(NSString *)value +{ + return [value doubleValue]; +} + +- (BOOL)bool:(NSString *)value +{ + return ![value isEqualToString:@"0"]; +} + +- (void)move:(CGPathRef)path x:(double)x y:(double)y +{ + [self moveTo:path x:x + _penX y:y + _penY]; +} + +- (void)moveTo:(CGPathRef)path x:(double)x y:(double)y +{ + _pivotX = _penX = x; + _pivotY = _penY = y; + CGPathMoveToPoint(path, nil, x, y); +} + +- (void)line:(CGPathRef)path x:(double)x y:(double)y +{ + [self lineTo:path x:x + _penX y:y + _penY]; +} + +- (void)lineTo:(CGPathRef)path x:(double)x y:(double)y{ + [self setPenDown]; + _pivotX = _penX = x; + _pivotY = _penY = y; + CGPathAddLineToPoint(path, nil, x, y); +} + +- (void)curve:(CGPathRef)path c1x:(double)c1x c1y:(double)c1y c2x:(double)c2x c2y:(double)c2y ex:(double)ex ey:(double)ey +{ + [self curveTo:path c1x:c1x + _penX + c1y:c1y + _penY + c2x:c2x + _penX + c2y:c2y + _penY + ex:ex + _penX + ey:ey + _penY]; +} + +- (void)curveTo:(CGPathRef)path c1x:(double)c1x c1y:(double)c1y c2x:(double)c2x c2y:(double)c2y ex:(double)ex ey:(double)ey +{ + _pivotX = ex; + _pivotY = ey; + [self curveToPoint:path c1x:(double)c1x c1y:(double)c1y c2x:(double)c2x c2y:(double)c2y ex:(double)ex ey:(double)ey]; +} + +- (void)curveToPoint:(CGPathRef)path c1x:(double)c1x c1y:(double)c1y c2x:(double)c2x c2y:(double)c2y ex:(double)ex ey:(double)ey +{ + [self setPenDown]; + _penX = ex; + _penY = ey; + CGPathAddCurveToPoint(path, nil, c1x, c1y, c2x, c2y, ex, ey); +} + +- (void)smoothCurve:(CGPathRef)path c1x:(double)c1x c1y:(double)c1y ex:(double)ex ey:(double)ey +{ + [self smoothCurveTo:path c1x:c1x + _penX c1y:c1y + _penY ex:ex + _penX ey:ey + _penY]; +} + +- (void)smoothCurveTo:(CGPathRef)path c1x:(double)c1x c1y:(double)c1y ex:(double)ex ey:(double)ey +{ + double c2x = c1x; + double c2y = c1y; + c1x = (_penX * 2) - _pivotX; + c1y = (_penY * 2) - _pivotY; + _pivotX = c2x; + _pivotY = c2y; + [self curveToPoint:path c1x:(double)c1x c1y:(double)c1y c2x:(double)c2x c2y:(double)c2y ex:(double)ex ey:(double)ey]; +} + +- (void)quadraticBezierCurve:(CGPathRef)path c1x:(double)c1x c1y:(double)c1y c2x:(double)c2x c2y:(double)c2y +{ + [self quadraticBezierCurveTo:path c1x:(double)c1x + _penX c1y:(double)c1y + _penY c2x:(double)c2x + _penX c2y:(double)c2y + _penY]; +} + +- (void)quadraticBezierCurveTo:(CGPathRef)path c1x:(double)c1x c1y:(double)c1y c2x:(double)c2x c2y:(double)c2y +{ + _pivotX = c1x; + _pivotY = c1y; + double ex = c2x; + double ey = c2y; + c2x = (ex + c1x * 2) / 3; + c2y = (ey + c1y * 2) / 3; + c1x = (_penX + c1x * 2) / 3; + c1y = (_penY + c1y * 2) / 3; + [self curveToPoint:path c1x:(double)c1x c1y:(double)c1y c2x:(double)c2x c2y:(double)c2y ex:(double)ex ey:(double)ey]; +} + +- (void)smoothQuadraticBezierCurve:(CGPathRef)path c1x:(double)c1x c1y:(double)c1y +{ + [self smoothQuadraticBezierCurveTo:path c1x:c1x + _penX c1y:c1y + _penY]; +} + +- (void)smoothQuadraticBezierCurveTo:(CGPathRef)path c1x:(double)c1x c1y:(double)c1y +{ + double c2x = c1x; + double c2y = c1y; + c1x = (_penX * 2) - _pivotX; + c1y = (_penY * 2) - _pivotY; + [self quadraticBezierCurveTo:path c1x:c1x c1y:c1y c2x:c2x c2y:c2y]; +} + +- (void)arc:(CGPathRef)path rx:(double)rx ry:(double)ry rotation:(double)rotation outer:(BOOL)outer clockwise:(BOOL)clockwise x:(double)x y:(double)y +{ + [self arcTo:path rx:rx ry:ry rotation:rotation outer:outer clockwise:clockwise x:x + _penX y:y + _penY]; +} + +- (void)arcTo:(CGPathRef)path rx:(double)rx ry:(double)ry rotation:(double)rotation outer:(BOOL)outer clockwise:(BOOL)clockwise x:(double)x y:(double)y +{ + double tX = _penX; + double tY = _penY; + + ry = abs(ry == 0 ? (rx == 0 ? (y - tY) : rx) : ry); + rx = abs(rx == 0 ? (x - tX) : rx); + + if (rx == 0 || ry == 0 || (x == tX && y == tY)) { + [self lineTo:path x:x y:y]; + return; + } + + + double rad = rotation * M_PI / 180; + double cosed = cos(rad); + double sined = sin(rad); + x -= tX; + y -= tY; + // Ellipse Center + float cx = cosed * x / 2 + sined * y / 2; + float cy = -sined * x / 2 + cosed * y / 2; + float rxry = rx * rx * ry * ry; + float rycx = ry * ry * cx * cx; + float rxcy = rx * rx * cy * cy; + float a = rxry - rxcy - rycx; + + if (a < 0){ + a = sqrt(1 - a / rxry); + rx *= a; + ry *= a; + cx = x / 2; + cy = y / 2; + } else { + a = sqrt(a / (rxcy + rycx)); + + if (outer == clockwise) { + a = -a; + } + float cxd = -a * cy * rx / ry; + float cyd = a * cx * ry / rx; + cx = cosed * cxd - sined * cyd + x / 2; + cy = sined * cxd + cosed * cyd + y / 2; + } + + // Rotation + Scale Transform + float xx = cosed / rx; + float yx = sined / rx; + float xy = -sined / ry; + float yy = cosed / ry; + + // Start and End Angle + float sa = atan2(xy * -cx + yy * -cy, xx * -cx + yx * -cy); + float ea = atan2(xy * (x - cx) + yy * (y - cy), xx * (x - cx) + yx * (y - cy)); + + cx += tX; + cy += tY; + x += tX; + y += tY; + + [self setPenDown]; + + _penX = _pivotX = x; + _penY = _pivotY = y; + + if (rx != ry || rad != 0) { + [self arcToBezier:path cx:cx cy:cy rx:rx ry:ry sa:sa ea:ea clockwise:clockwise rad:rad]; + } else { + CGPathAddArc(path, nil, cx, cy, rx, sa, ea, !clockwise); + } +} + +- (void)arcToBezier:(CGPathRef)path cx:(double)cx cy:(double)cy rx:(double)rx ry:(double)ry sa:(double)sa ea:(double)ea clockwise:(BOOL)clockwise rad:(double)rad +{ + // Inverse Rotation + Scale Transform + double cosed = cos(rad); + double sined = sin(rad); + double xx = cosed * rx; + double yx = -sined * ry; + double xy = sined * rx; + double yy = cosed * ry; + + // Bezier Curve Approximation + double arc = ea - sa; + if (arc < 0 && clockwise) { + arc += M_PI * 2; + } else if (arc > 0 && !clockwise) { + arc -= M_PI * 2; + } + + int n = ceil(abs(arc / (M_PI / 2))); + + double step = arc / n; + double k = (4 / 3) * tan(step / 4); + + double x = cos(sa); + double y = sin(sa); + + for (int i = 0; i < n; i++){ + double cp1x = x - k * y; + double cp1y = y + k * x; + + sa += step; + x = cos(sa); + y = sin(sa); + + double cp2x = x + k * y; + double cp2y = y - k * x; + + CGPathAddCurveToPoint(path, + nil, + cx + xx * cp1x + yx * cp1y, + cy + xy * cp1x + yy * cp1y, + cx + xx * cp2x + yx * cp2y, + cy + xy * cp2x + yy * cp2y, + cx + xx * x + yx * y, + cy + xy * x + yy * y); + } +} + +- (void)close:(CGPathRef)path +{ + if (_penDownSet) { + _penX = _penDownX; + _penY = _penDownY; + _penDownSet = NO; + CGPathCloseSubpath(path); + } +} + +- (void)setPenDown +{ + if (!_penDownSet) { + _penDownX = _penX; + _penDownY = _penY; + _penDownSet = YES; + } +} + +@end diff --git a/ios/Utils/RNSVGPercentageConverter.m b/ios/Utils/RNSVGPercentageConverter.m index 6b2b0a60..3ba5986f 100644 --- a/ios/Utils/RNSVGPercentageConverter.m +++ b/ios/Utils/RNSVGPercentageConverter.m @@ -12,7 +12,7 @@ { CGFloat _relative; CGFloat _offset; - NSRegularExpression *percentageRegularExpression; + NSRegularExpression *_percentageRegularExpression; } - (instancetype) initWithRelativeAndOffset:(CGFloat)relative offset:(CGFloat)offset @@ -20,7 +20,7 @@ if (self = [super init]) { _relative = relative; _offset = offset; - percentageRegularExpression = [[NSRegularExpression alloc] initWithPattern:@"^(\\-?\\d+(?:\\.\\d+)?)%$" options:0 error:nil]; + _percentageRegularExpression = [[NSRegularExpression alloc] initWithPattern:@"^(\\-?\\d+(?:\\.\\d+)?)%$" options:0 error:nil]; } return self; } @@ -28,14 +28,14 @@ - (id)init { if (self = [super init]) { - percentageRegularExpression = [[NSRegularExpression alloc] initWithPattern:@"^(\\-?\\d+(?:\\.\\d+)?)%$" options:0 error:nil]; + _percentageRegularExpression = [[NSRegularExpression alloc] initWithPattern:@"^(\\-?\\d+(?:\\.\\d+)?)%$" options:0 error:nil]; } return self; } - (NSRegularExpression *) getPercentageRegularExpression { - return percentageRegularExpression; + return _percentageRegularExpression; } - (CGFloat) stringToFloat:(NSString *)string @@ -61,7 +61,7 @@ { __block CGFloat matched; - [percentageRegularExpression enumerateMatchesInString:percentage + [_percentageRegularExpression enumerateMatchesInString:percentage options:0 range:NSMakeRange(0, percentage.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) @@ -76,7 +76,7 @@ - (BOOL) isPercentage:(NSString *) string { - return [percentageRegularExpression firstMatchInString:string options:0 range:NSMakeRange(0, [string length])] != nil; + return [_percentageRegularExpression firstMatchInString:string options:0 range:NSMakeRange(0, [string length])] != nil; } @end diff --git a/ios/ViewManagers/RNSVGDefsManager.h b/ios/ViewManagers/RNSVGDefsManager.h index 6c2d232c..591ba680 100644 --- a/ios/ViewManagers/RNSVGDefsManager.h +++ b/ios/ViewManagers/RNSVGDefsManager.h @@ -6,7 +6,7 @@ * LICENSE file in the root directory of this source tree. */ -#import "RCTViewManager.h" +#import @interface RNSVGDefsManager : RCTViewManager diff --git a/ios/ViewManagers/RNSVGNodeManager.h b/ios/ViewManagers/RNSVGNodeManager.h index c7282306..2075bf61 100644 --- a/ios/ViewManagers/RNSVGNodeManager.h +++ b/ios/ViewManagers/RNSVGNodeManager.h @@ -7,7 +7,7 @@ */ #import "RNSVGNode.h" -#import "RCTViewManager.h" +#import @interface RNSVGNodeManager : RCTViewManager diff --git a/ios/ViewManagers/RNSVGSvgViewManager.h b/ios/ViewManagers/RNSVGSvgViewManager.h index 700c8eb9..adbb6645 100644 --- a/ios/ViewManagers/RNSVGSvgViewManager.h +++ b/ios/ViewManagers/RNSVGSvgViewManager.h @@ -6,7 +6,7 @@ * LICENSE file in the root directory of this source tree. */ -#import "RCTViewManager.h" +#import @interface RNSVGSvgViewManager : RCTViewManager diff --git a/ios/ViewManagers/RNSVGSvgViewManager.m b/ios/ViewManagers/RNSVGSvgViewManager.m index 774e81a8..b606d3bd 100644 --- a/ios/ViewManagers/RNSVGSvgViewManager.m +++ b/ios/ViewManagers/RNSVGSvgViewManager.m @@ -6,8 +6,8 @@ * LICENSE file in the root directory of this source tree. */ -#import "RCTBridge.h" -#import "RCTUIManager.h" +#import +#import #import "RNSVGSvgViewManager.h" #import "RNSVGSvgView.h" diff --git a/lib/attributes.js b/lib/attributes.js index b829a853..5b74c97b 100644 --- a/lib/attributes.js +++ b/lib/attributes.js @@ -84,9 +84,7 @@ const UseAttributes = merge({ }, RenderableAttributes); const PathAttributes = merge({ - d: { - diff: arrayDiffer - } + d: true }, RenderableAttributes); const TextAttributes = merge({ diff --git a/lib/extract/extractClipping.js b/lib/extract/extractClipping.js index 4024c3fe..605c42e5 100644 --- a/lib/extract/extractClipping.js +++ b/lib/extract/extractClipping.js @@ -1,4 +1,3 @@ -import SerializablePath from '../SerializablePath'; import clipReg from './patternReg'; const clipRules = { @@ -17,6 +16,8 @@ export default function (props) { if (matched) { clippingProps.clipPath = matched[1]; + } else { + console.warn('Invalid `clipPath` prop, expected a clipPath like `"#id"`, but got: "' + clipPath + '"'); } } diff --git a/lib/extract/extractStroke.js b/lib/extract/extractStroke.js index 94b08568..ad978867 100644 --- a/lib/extract/extractStroke.js +++ b/lib/extract/extractStroke.js @@ -20,10 +20,6 @@ export default function(props) { let strokeWidth = +props.strokeWidth; - if (_.isNil(props.strokeWidth)) { - strokeWidth = null; - } - let strokeDasharray = props.strokeDasharray; if (typeof strokeDasharray === 'string') { @@ -41,7 +37,7 @@ export default function(props) { strokeLinecap: caps[props.strokeLinecap] || 0, strokeLinejoin: joins[props.strokeLinejoin] || 0, strokeDasharray: strokeDasharray || null, - strokeWidth: strokeWidth, + strokeWidth: strokeWidth || null, strokeDashoffset: strokeDasharray ? (+props.strokeDashoffset || 0) : null, strokeMiterlimit: props.strokeMiterlimit || 4 }; diff --git a/package.json b/package.json index c27e7e6e..08eaac63 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "4.3.2", + "version": "4.6.1", "name": "react-native-svg", "description": "SVG library for react-native", "repository": { @@ -22,7 +22,8 @@ "lint": "eslint ./" }, "peerDependencies": { - "react-native": ">=0.33.0" + "react-native": ">=0.40.0", + "react": ">=15.4.0" }, "dependencies": { "color": "^0.11.1",