diff --git a/.eslintrc b/.eslintrc
index c739b3fc..9fa0ac25 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -83,7 +83,7 @@
"curly": 1, // specify curly brace conventions for all control statements
"default-case": 0, // require default case in switch statements (off by default)
"dot-notation": 1, // encourages use of dot notation whenever possible
- "eqeqeq": 1, // require the use of === and !==
+ "eqeqeq": 0, // require the use of === and !==
"guard-for-in": 0, // make sure for-in loops have an if statement (off by default)
"no-alert": 0, // disallow the use of alert, confirm, and prompt
"no-caller": 1, // disallow use of arguments.caller or arguments.callee
diff --git a/README.md b/README.md
index fd513fdc..df10776c 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
[](https://www.npmjs.com/package/react-native-svg)
[](https://www.npmjs.com/package/react-native-svg)
-`react-native-svg` provides SVG support to React Native on iOS and Android.
+`react-native-svg` provides SVG support to React Native on iOS and Android, and a compatibility layer for the web.
[Check out the demo](https://snack.expo.io/@msand/react-native-svg-example)
@@ -70,15 +70,15 @@
# NOTICE:
-Due to breaking changes in react-native, the version given in the left column
+Due to breaking changes in react-native, the version given in the left column
(and higher versions) of react-native-svg only supports the react-native version
in the right column (and higher versions, if possible).
-It is recommended to use the version of react given in the peer dependencies
+It is recommended to use the version of react given in the peer dependencies
of the react-native version you are using.
The latest version of react-native-svg should always work in a clean react-native project.
-
+
| react-native-svg | react-native |
|------------------|--------------|
| 3.2.0 | 0.29 |
@@ -91,12 +91,16 @@ The latest version of react-native-svg should always work in a clean react-nativ
| 5.3.0 | 0.46 |
| 5.4.1 | 0.47 |
| 5.5.1 | >=0.50 |
-| 6.0.0 | >=0.50 |
-| 7.0.0 | >=0.57.4 |
-| 8.0.0 | >=0.57.4 |
+| >=6 | >=0.50 |
+| >=7 | >=0.57.4 |
+| >=8 | >=0.57.4 |
+| >=9 | >=0.57.4 |
-Or, include [this PR](https://github.com/facebook/react-native/pull/17842) manually for v7+ stability on android for older RN ( [included in 0.57-stable](https://github.com/facebook/react-native/commit/d9f5319cf0d9828b29d0e350284b22ce29985042) and newer)
+Or, include [this PR](https://github.com/facebook/react-native/pull/17842) manually for v7+ stability on android for older RN ( [included in 0.57-stable](https://github.com/facebook/react-native/commit/d9f5319cf0d9828b29d0e350284b22ce29985042) and newer).
+The latest version of v6, v7, v8 and v9 should all work in the latest react-native version.
+
+v7 and newer requires the patch for making android thread safe, to get native animation support.
#### Manually
@@ -181,7 +185,7 @@ react-native link
```
Make a reproduction of the problem in `App.js`
-
+
```bash
react-native run-ios
react-native run-android
@@ -193,6 +197,8 @@ Verify that it is still an issue with the latest version. If so, open a new issu
react-native info
```
+If you suspect that you've found a spec conformance bug, then you can test using your component in a react-native-web project by forking this codesandbox, to see how different browsers render the same content: https://codesandbox.io/s/pypn6mn3y7
+
### Usage
Here's a simple example. To render output like this:
@@ -291,9 +297,10 @@ export default () => (
Try [react-native-svg-transformer](https://github.com/kristerkari/react-native-svg-transformer) to get compile time conversion and cached transformations.
https://github.com/kristerkari/react-native-svg-transformer#installation-and-configuration
-https://github.com/kristerkari/react-native-svg-transformer#for-react-native-v057-or-newer
+https://github.com/kristerkari/react-native-svg-transformer#for-react-native-v057-or-newer--expo-sdk-v3100-or-newer
+
+`metro.config.js`
-rn-cli.config.js
```js
const { getDefaultConfig } = require("metro-config");
@@ -395,6 +402,33 @@ originY | 0 | Transform originY coordinates for the current obj
```
+Colors set in the Svg element are inherited by its children:
+
+```html
+
+
+
+
+```
+
+
+
+ Code explanation:
+
+ * The fill prop defines the color inside the object.
+ * The stroke prop defines the color of the line drawn around the object.
+ * The color prop is a bit special in the sense that it won't color anything by itself, but define a kind of color variable that can be used by children elements. In this example we're defining a "green" color in the Svg element and using it in the second Path element via stroke="currentColor". The "currentColor" is what refers to that "green" value, and it can be used in other props that accept colors too, e.g. fill="currentColor".
+
### Rect
The element is used to create a rectangle and variations of a rectangle shape:
@@ -575,7 +609,7 @@ The following commands are available for path data:
* A = elliptical Arc
* Z = closepath
-`Note:` All of the commands above can also be expressed with lower letters. Capital letters means absolutely positioned, lower cases means relatively positioned.
+`Note:` All of the commands above can also be expressed with lower letters. Capital letters means absolutely positioned, lower cases means relatively positioned. See [Path document of SVG](https://www.w3.org/TR/SVG/paths.html) to know parameters for each command.
```html
mFontContext = new ArrayList<>();
+ final ArrayList mFontContext = new ArrayList<>();
// Unique input attribute lists (only added if node sets a value)
private final ArrayList mXsContext = new ArrayList<>();
diff --git a/android/src/main/java/com/horcrux/svg/RenderableView.java b/android/src/main/java/com/horcrux/svg/RenderableView.java
index d0f54d4b..84c29d08 100644
--- a/android/src/main/java/com/horcrux/svg/RenderableView.java
+++ b/android/src/main/java/com/horcrux/svg/RenderableView.java
@@ -56,6 +56,14 @@ abstract public class RenderableView extends VirtualView {
private static final int FILL_RULE_EVENODD = 0;
static final int FILL_RULE_NONZERO = 1;
+ // vectorEffect
+ static final int VECTOR_EFFECT_DEFAULT = 0;
+ static final int VECTOR_EFFECT_NON_SCALING_STROKE = 1;
+ static final int VECTOR_EFFECT_INHERIT = 2;
+ static final int VECTOR_EFFECT_URI = 3;
+
+ public int vectorEffect = VECTOR_EFFECT_DEFAULT;
+
/*
Used in mergeProperties, keep public
*/
@@ -86,6 +94,12 @@ abstract public class RenderableView extends VirtualView {
private static final Pattern regex = Pattern.compile("[0-9.-]+");
+ @ReactProp(name = "vectorEffect", defaultInt = VECTOR_EFFECT_DEFAULT)
+ public void setVectorEffect(int vectorEffect) {
+ this.vectorEffect = vectorEffect;
+ invalidate();
+ }
+
@ReactProp(name = "fill")
public void setFill(@Nullable Dynamic fill) {
if (fill == null || fill.isNull()) {
@@ -322,7 +336,14 @@ abstract public class RenderableView extends VirtualView {
mPath = getPath(canvas, paint);
mPath.setFillType(fillRule);
}
+ boolean nonScalingStroke = vectorEffect == VECTOR_EFFECT_NON_SCALING_STROKE;
Path path = mPath;
+ if (nonScalingStroke) {
+ Path scaled = new Path();
+ mPath.transform(canvas.getMatrix(), scaled);
+ canvas.setMatrix(null);
+ path = scaled;
+ }
RectF clientRect = new RectF();
path.computeBounds(clientRect, true);
diff --git a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java
index 34d31c18..c69810ac 100644
--- a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java
+++ b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java
@@ -13,10 +13,11 @@ import android.graphics.Matrix;
import android.view.View;
import android.view.ViewGroup;
-import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.Dynamic;
+import com.facebook.react.bridge.JavaOnlyMap;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.bridge.ReadableType;
import com.facebook.react.uimanager.DisplayMetricsHolder;
import com.facebook.react.uimanager.LayoutShadowNode;
import com.facebook.react.uimanager.MatrixMathHelper;
@@ -43,6 +44,7 @@ import static com.facebook.react.uimanager.ViewProps.*;
import static com.horcrux.svg.RenderableView.CAP_ROUND;
import static com.horcrux.svg.RenderableView.FILL_RULE_NONZERO;
import static com.horcrux.svg.RenderableView.JOIN_ROUND;
+import static com.horcrux.svg.RenderableView.VECTOR_EFFECT_DEFAULT;
/**
* ViewManager for all RNSVG views
@@ -176,7 +178,9 @@ class RenderableViewManager extends ViewGroupManager {
}
private static void decomposeMatrix() {
- Assertions.assertCondition(sTransformDecompositionArray.length == 16);
+ if (sTransformDecompositionArray.length != 16) {
+ throw new AssertionError();
+ }
// output values
final double[] perspective = sMatrixDecompositionContext.perspective;
@@ -314,8 +318,12 @@ class RenderableViewManager extends ViewGroupManager {
float scale = DisplayMetricsHolder.getScreenDisplayMetrics().density;
// The following converts the matrix's perspective to a camera distance
- // such that the camera perspective looks the same on Android and iOS
- float normalizedCameraDistance = scale * cameraDistance * CAMERA_DISTANCE_NORMALIZATION_MULTIPLIER;
+ // such that the camera perspective looks the same on Android and iOS.
+ // The native Android implementation removed the screen density from the
+ // calculation, so squaring and a normalization value of
+ // sqrt(5) produces an exact replica with iOS.
+ // For more information, see https://github.com/facebook/react-native/pull/18302
+ float normalizedCameraDistance = scale * scale * cameraDistance * CAMERA_DISTANCE_NORMALIZATION_MULTIPLIER;
view.setCameraDistance(normalizedCameraDistance);
}
@@ -345,6 +353,22 @@ class RenderableViewManager extends ViewGroupManager {
public void setFont(GroupView node, @Nullable ReadableMap font) {
node.setFont(font);
}
+
+ @ReactProp(name = "fontSize")
+ public void setFontSize(GroupView node, Dynamic fontSize) {
+ JavaOnlyMap map = new JavaOnlyMap();
+ switch (fontSize.getType()) {
+ case Number:
+ map.putDouble("fontSize", fontSize.asDouble());
+ break;
+ case String:
+ map.putString("fontSize", fontSize.asString());
+ break;
+ default:
+ return;
+ }
+ node.setFont(map);
+ }
}
@@ -991,17 +1015,26 @@ class RenderableViewManager extends ViewGroupManager {
node.setStrokeLinejoin(strokeLinejoin);
}
+ @ReactProp(name = "vectorEffect", defaultInt = VECTOR_EFFECT_DEFAULT)
+ public void setVectorEffect(RenderableView node, int vectorEffect) {
+ node.setVectorEffect(vectorEffect);
+ }
+
@ReactProp(name = "matrix")
public void setMatrix(VirtualView node, Dynamic matrixArray) {
node.setMatrix(matrixArray);
}
@ReactProp(name = "transform")
- public void setTransform(VirtualView node, ReadableArray matrix) {
- if (matrix == null) {
+ public void setTransform(VirtualView node, Dynamic matrix) {
+ if (matrix.getType() != ReadableType.Array) {
+ return;
+ }
+ ReadableArray ma = matrix.asArray();
+ if (ma == null) {
resetTransformProperty(node);
} else {
- setTransformProperty(node, matrix);
+ setTransformProperty(node, ma);
}
Matrix m = node.getMatrix();
node.mTransform = m;
@@ -1028,6 +1061,9 @@ class RenderableViewManager extends ViewGroupManager {
if (view!= null) {
view.invalidate();
}
+ if (node instanceof TextView) {
+ ((TextView)node).getTextContainer().clearChildCache();
+ }
}
@Override
diff --git a/android/src/main/java/com/horcrux/svg/SvgView.java b/android/src/main/java/com/horcrux/svg/SvgView.java
index 90d855db..74c97d3c 100644
--- a/android/src/main/java/com/horcrux/svg/SvgView.java
+++ b/android/src/main/java/com/horcrux/svg/SvgView.java
@@ -101,8 +101,19 @@ public class SvgView extends ReactViewGroup implements ReactCompoundView, ReactC
if (mBitmap == null) {
mBitmap = drawOutput();
}
- if (mBitmap != null)
+ if (mBitmap != null) {
canvas.drawBitmap(mBitmap, 0, 0, null);
+ if (toDataUrlTask != null) {
+ toDataUrlTask.run();
+ toDataUrlTask = null;
+ }
+ }
+ }
+
+ private Runnable toDataUrlTask = null;
+
+ void setToDataUrlTask(Runnable task) {
+ toDataUrlTask = task;
}
@Override
@@ -138,6 +149,10 @@ public class SvgView extends ReactViewGroup implements ReactCompoundView, ReactC
private boolean mRendered = false;
int mTintColor = 0;
+ boolean isRendered() {
+ return mRendered;
+ }
+
private void clearChildCache() {
if (!mRendered) {
return;
@@ -238,7 +253,7 @@ public class SvgView extends ReactViewGroup implements ReactCompoundView, ReactC
return mCanvas.getClipBounds();
}
- void drawChildren(final Canvas canvas) {
+ synchronized void drawChildren(final Canvas canvas) {
mRendered = true;
mCanvas = canvas;
if (mAlign != null) {
@@ -298,7 +313,27 @@ public class SvgView extends ReactViewGroup implements ReactCompoundView, ReactC
getHeight(),
Bitmap.Config.ARGB_8888);
+ clearChildCache();
drawChildren(new Canvas(bitmap));
+ clearChildCache();
+ this.invalidate();
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
+ bitmap.recycle();
+ byte[] bitmapBytes = stream.toByteArray();
+ return Base64.encodeToString(bitmapBytes, Base64.DEFAULT);
+ }
+
+ String toDataURL(int width, int height) {
+ Bitmap bitmap = Bitmap.createBitmap(
+ width,
+ height,
+ Bitmap.Config.ARGB_8888);
+
+ clearChildCache();
+ drawChildren(new Canvas(bitmap));
+ clearChildCache();
+ this.invalidate();
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
bitmap.recycle();
diff --git a/android/src/main/java/com/horcrux/svg/SvgViewManager.java b/android/src/main/java/com/horcrux/svg/SvgViewManager.java
index 1ef27013..862e22dd 100644
--- a/android/src/main/java/com/horcrux/svg/SvgViewManager.java
+++ b/android/src/main/java/com/horcrux/svg/SvgViewManager.java
@@ -28,9 +28,19 @@ class SvgViewManager extends ReactViewManager {
private static final String REACT_CLASS = "RNSVGSvgView";
private static final SparseArray mTagToSvgView = new SparseArray<>();
+ private static final SparseArray mTagToRunnable = new SparseArray<>();
static void setSvgView(int tag, SvgView svg) {
mTagToSvgView.put(tag, svg);
+ Runnable task = mTagToRunnable.get(tag);
+ if (task != null) {
+ task.run();
+ mTagToRunnable.delete(tag);
+ }
+ }
+
+ static void runWhenViewIsAvailable(int tag, Runnable task) {
+ mTagToRunnable.put(tag, task);
}
static @Nullable SvgView getSvgViewByTag(int tag) {
diff --git a/android/src/main/java/com/horcrux/svg/SvgViewModule.java b/android/src/main/java/com/horcrux/svg/SvgViewModule.java
index b9ef4c03..42e749e7 100644
--- a/android/src/main/java/com/horcrux/svg/SvgViewModule.java
+++ b/android/src/main/java/com/horcrux/svg/SvgViewModule.java
@@ -13,6 +13,8 @@ import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.bridge.UiThreadUtil;
class SvgViewModule extends ReactContextBaseJavaModule {
SvgViewModule(ReactApplicationContext reactContext) {
@@ -24,13 +26,55 @@ class SvgViewModule extends ReactContextBaseJavaModule {
return "RNSVGSvgViewManager";
}
+ static public void toDataURL(final int tag, final ReadableMap options, final Callback successCallback, final int attempt) {
+ UiThreadUtil.runOnUiThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ SvgView svg = SvgViewManager.getSvgViewByTag(tag);
+
+ if (svg == null) {
+ SvgViewManager.runWhenViewIsAvailable(tag, new Runnable() {
+ @Override
+ public void run() {
+ SvgView svg = SvgViewManager.getSvgViewByTag(tag);
+ if (svg == null) { // Should never happen
+ return;
+ }
+ svg.setToDataUrlTask(new Runnable() {
+ @Override
+ public void run() {
+ toDataURL(tag, options, successCallback, attempt + 1);
+ }
+ });
+ }
+ });
+ } else if (!svg.isRendered()) {
+ svg.setToDataUrlTask(new Runnable() {
+ @Override
+ public void run() {
+ toDataURL(tag, options, successCallback, attempt + 1);
+ }
+ });
+ } else {
+ if (options != null) {
+ successCallback.invoke(
+ svg.toDataURL(
+ options.getInt("width"),
+ options.getInt("height")
+ )
+ );
+ } else {
+ successCallback.invoke(svg.toDataURL());
+ }
+ }
+ }
+ }
+ );
+ }
@ReactMethod
- public void toDataURL(int tag, Callback successCallback) {
- SvgView svg = SvgViewManager.getSvgViewByTag(tag);
-
- if (svg != null) {
- successCallback.invoke(svg.toDataURL());
- }
+ public void toDataURL(int tag, ReadableMap options, Callback successCallback) {
+ toDataURL(tag, options, successCallback, 0);
}
}
diff --git a/android/src/main/java/com/horcrux/svg/TSpanView.java b/android/src/main/java/com/horcrux/svg/TSpanView.java
index 32248517..6aff115f 100644
--- a/android/src/main/java/com/horcrux/svg/TSpanView.java
+++ b/android/src/main/java/com/horcrux/svg/TSpanView.java
@@ -21,6 +21,7 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.os.Build;
+import android.view.View;
import android.view.ViewParent;
import com.facebook.react.bridge.ReactContext;
@@ -47,6 +48,7 @@ class TSpanView extends TextView {
private static final String OTF = ".otf";
private static final String TTF = ".ttf";
+ private Path mCachedPath;
@Nullable String mContent;
private TextPathView textPath;
ArrayList emoji = new ArrayList<>();
@@ -62,6 +64,17 @@ class TSpanView extends TextView {
invalidate();
}
+ @Override
+ public void invalidate() {
+ mCachedPath = null;
+ super.invalidate();
+ }
+
+ void clearCache() {
+ mCachedPath = null;
+ super.clearCache();
+ }
+
@Override
void draw(Canvas canvas, Paint paint, float opacity) {
if (mContent != null) {
@@ -88,22 +101,73 @@ class TSpanView extends TextView {
@Override
Path getPath(Canvas canvas, Paint paint) {
- if (mPath != null) {
- return mPath;
+ if (mCachedPath != null) {
+ return mCachedPath;
}
if (mContent == null) {
- mPath = getGroupPath(canvas, paint);
- return mPath;
+ mCachedPath = getGroupPath(canvas, paint);
+ return mCachedPath;
}
setupTextPath();
pushGlyphContext();
- mPath = getLinePath(mContent, paint, canvas);
+ mCachedPath = getLinePath(mContent, paint, canvas);
popGlyphContext();
- return mPath;
+ return mCachedPath;
+ }
+
+ double getSubtreeTextChunksTotalAdvance(Paint paint) {
+ if (!Double.isNaN(cachedAdvance)) {
+ return cachedAdvance;
+ }
+ double advance = 0;
+
+ if (mContent == null) {
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (child instanceof TextView) {
+ TextView text = (TextView)child;
+ advance += text.getSubtreeTextChunksTotalAdvance(paint);
+ }
+ }
+ cachedAdvance = advance;
+ return advance;
+ }
+
+ String line = mContent;
+ final int length = line.length();
+
+ if (length == 0) {
+ cachedAdvance = 0;
+ return advance;
+ }
+
+ GlyphContext gc = getTextRootGlyphContext();
+ FontData font = gc.getFont();
+ applyTextPropertiesToPaint(paint, font);
+
+ double letterSpacing = font.letterSpacing;
+ final boolean allowOptionalLigatures = letterSpacing == 0 &&
+ font.fontVariantLigatures == FontVariantLigatures.normal;
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ String required = "'rlig', 'liga', 'clig', 'calt', 'locl', 'ccmp', 'mark', 'mkmk',";
+ String defaultFeatures = required + "'kern', ";
+ if (allowOptionalLigatures) {
+ String additionalLigatures = "'hlig', 'cala', ";
+ paint.setFontFeatureSettings(defaultFeatures + additionalLigatures + font.fontFeatureSettings);
+ } else {
+ String disableDiscretionaryLigatures = "'liga' 0, 'clig' 0, 'dlig' 0, 'hlig' 0, 'cala' 0, ";
+ paint.setFontFeatureSettings(defaultFeatures + disableDiscretionaryLigatures + font.fontFeatureSettings);
+ }
+ paint.setLetterSpacing((float)(letterSpacing / (font.fontSize * mScale)));
+ }
+
+ cachedAdvance = paint.measureText(line);
+ return cachedAdvance;
}
@SuppressWarnings("ConstantConditions")
@@ -311,8 +375,10 @@ class TSpanView extends TextView {
attributes, such as a ‘dx’ attribute value on a ‘tspan’ element.
*/
final TextAnchor textAnchor = font.textAnchor;
- final double textMeasure = paint.measureText(line);
+ TextView anchorRoot = getTextAnchorRoot();
+ final double textMeasure = anchorRoot.getSubtreeTextChunksTotalAdvance(paint);
double offset = getTextAnchorOffset(textAnchor, textMeasure);
+ applyTextPropertiesToPaint(paint, font);
int side = 1;
double startOfRendering = 0;
@@ -563,7 +629,7 @@ class TSpanView extends TextView {
// this will just retrieve the bounding rect for 'x'
paint.getTextBounds("x", 0, 1, bounds);
int xHeight = bounds.height();
- baselineShift = xHeight / 2;
+ baselineShift = xHeight / 2.0;
break;
case central:
@@ -691,7 +757,6 @@ class TSpanView extends TextView {
final Matrix end = new Matrix();
final float[] startPointMatrixData = new float[9];
- final float[] midPointMatrixData = new float[9];
final float[] endPointMatrixData = new float[9];
emoji.clear();
@@ -961,6 +1026,9 @@ class TSpanView extends TextView {
paint.setTypeface(typeface);
paint.setTextSize((float) fontSize);
paint.setTextAlign(Paint.Align.LEFT);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ paint.setLetterSpacing(0);
+ }
// Do these have any effect for anyone? Not for me (@msand) at least.
// paint.setUnderlineText(underlineText);
@@ -1000,6 +1068,9 @@ class TSpanView extends TextView {
if (mRegion == null && mFillPath != null) {
mRegion = getRegion(mFillPath);
}
+ if (mRegion == null && mPath != null) {
+ mRegion = getRegion(mPath);
+ }
if (mStrokeRegion == null && mStrokePath != null) {
mStrokeRegion = getRegion(mStrokePath);
}
diff --git a/android/src/main/java/com/horcrux/svg/TextView.java b/android/src/main/java/com/horcrux/svg/TextView.java
index 22be697d..0fa37376 100644
--- a/android/src/main/java/com/horcrux/svg/TextView.java
+++ b/android/src/main/java/com/horcrux/svg/TextView.java
@@ -14,6 +14,7 @@ import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Region;
+import android.view.View;
import android.view.ViewParent;
import com.facebook.react.bridge.Dynamic;
@@ -38,6 +39,7 @@ class TextView extends GroupView {
@Nullable ArrayList mRotate;
@Nullable ArrayList mDeltaX;
@Nullable ArrayList mDeltaY;
+ double cachedAdvance = Double.NaN;
public TextView(ReactContext reactContext) {
super(reactContext);
@@ -49,7 +51,12 @@ class TextView extends GroupView {
return;
}
super.invalidate();
- clearChildCache();
+ getTextContainer().clearChildCache();
+ }
+
+ void clearCache() {
+ cachedAdvance = Double.NaN;
+ super.clearCache();
}
@ReactProp(name = "textLength")
@@ -207,4 +214,45 @@ class TextView extends GroupView {
boolean isTextNode = !(this instanceof TextPathView) && !(this instanceof TSpanView);
getTextRootGlyphContext().pushContext(isTextNode, this, mFont, mPositionX, mPositionY, mDeltaX, mDeltaY, mRotate);
}
+
+ TextView getTextAnchorRoot() {
+ GlyphContext gc = getTextRootGlyphContext();
+ ArrayList font = gc.mFontContext;
+ TextView node = this;
+ ViewParent parent = this.getParent();
+ for (int i = font.size() - 1; i >= 0; i--) {
+ if (!(parent instanceof TextView) || font.get(i).textAnchor == TextProperties.TextAnchor.start || node.mPositionX != null) {
+ return node;
+ }
+ node = (TextView) parent;
+ parent = node.getParent();
+ }
+ return node;
+ }
+
+ double getSubtreeTextChunksTotalAdvance(Paint paint) {
+ if (!Double.isNaN(cachedAdvance)) {
+ return cachedAdvance;
+ }
+ double advance = 0;
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (child instanceof TextView) {
+ TextView text = (TextView) child;
+ advance += text.getSubtreeTextChunksTotalAdvance(paint);
+ }
+ }
+ cachedAdvance = advance;
+ return advance;
+ }
+
+ TextView getTextContainer() {
+ TextView node = this;
+ ViewParent parent = this.getParent();
+ while (parent instanceof TextView) {
+ node = (TextView) parent;
+ parent = node.getParent();
+ }
+ return node;
+ }
}
diff --git a/android/src/main/java/com/horcrux/svg/UseView.java b/android/src/main/java/com/horcrux/svg/UseView.java
index c5b14d5f..487de47f 100644
--- a/android/src/main/java/com/horcrux/svg/UseView.java
+++ b/android/src/main/java/com/horcrux/svg/UseView.java
@@ -11,6 +11,7 @@ package com.horcrux.svg;
import android.annotation.SuppressLint;
import android.graphics.Canvas;
+import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
@@ -66,31 +67,33 @@ class UseView extends RenderableView {
void draw(Canvas canvas, Paint paint, float opacity) {
VirtualView template = getSvgView().getDefinedTemplate(mHref);
- if (template != null) {
- canvas.translate((float) relativeOnWidth(mX), (float) relativeOnHeight(mY));
- if (template instanceof RenderableView) {
- ((RenderableView)template).mergeProperties(this);
- }
-
- int count = template.saveAndSetupCanvas(canvas);
- clip(canvas, paint);
-
- if (template instanceof SymbolView) {
- SymbolView symbol = (SymbolView)template;
- symbol.drawSymbol(canvas, paint, opacity, (float) relativeOnWidth(mW), (float) relativeOnHeight(mH));
- } else {
- template.draw(canvas, paint, opacity * mOpacity);
- }
-
- this.setClientRect(template.getClientRect());
-
- template.restoreCanvas(canvas, count);
- if (template instanceof RenderableView) {
- ((RenderableView)template).resetProperties();
- }
- } else {
+ if (template == null) {
FLog.w(ReactConstants.TAG, "`Use` element expected a pre-defined svg template as `href` prop, " +
- "template named: " + mHref + " is not defined.");
+ "template named: " + mHref + " is not defined.");
+ return;
+ }
+
+ template.clearCache();
+ canvas.translate((float) relativeOnWidth(mX), (float) relativeOnHeight(mY));
+ if (template instanceof RenderableView) {
+ ((RenderableView)template).mergeProperties(this);
+ }
+
+ int count = template.saveAndSetupCanvas(canvas);
+ clip(canvas, paint);
+
+ if (template instanceof SymbolView) {
+ SymbolView symbol = (SymbolView)template;
+ symbol.drawSymbol(canvas, paint, opacity, (float) relativeOnWidth(mW), (float) relativeOnHeight(mH));
+ } else {
+ template.draw(canvas, paint, opacity * mOpacity);
+ }
+
+ this.setClientRect(template.getClientRect());
+
+ template.restoreCanvas(canvas, count);
+ if (template instanceof RenderableView) {
+ ((RenderableView)template).resetProperties();
}
}
@@ -105,6 +108,12 @@ class UseView extends RenderableView {
mInvTransform.mapPoints(dst);
VirtualView template = getSvgView().getDefinedTemplate(mHref);
+ if (template == null) {
+ FLog.w(ReactConstants.TAG, "`Use` element expected a pre-defined svg template as `href` prop, " +
+ "template named: " + mHref + " is not defined.");
+ return -1;
+ }
+
int hitChild = template.hitTest(dst);
if (hitChild != -1) {
return (template.isResponsible() || hitChild != template.getId()) ? hitChild : getId();
@@ -115,7 +124,17 @@ class UseView extends RenderableView {
@Override
Path getPath(Canvas canvas, Paint paint) {
- // todo:
- return new Path();
+ VirtualView template = getSvgView().getDefinedTemplate(mHref);
+ if (template == null) {
+ FLog.w(ReactConstants.TAG, "`Use` element expected a pre-defined svg template as `href` prop, " +
+ "template named: " + mHref + " is not defined.");
+ return null;
+ }
+ Path path = template.getPath(canvas, paint);
+ Path use = new Path();
+ Matrix m = new Matrix();
+ m.setTranslate((float) relativeOnWidth(mX), (float) relativeOnHeight(mY));
+ path.transform(m, use);
+ return use;
}
}
diff --git a/android/src/main/java/com/horcrux/svg/VirtualView.java b/android/src/main/java/com/horcrux/svg/VirtualView.java
index 07d28da0..933ab5ad 100644
--- a/android/src/main/java/com/horcrux/svg/VirtualView.java
+++ b/android/src/main/java/com/horcrux/svg/VirtualView.java
@@ -101,7 +101,7 @@ abstract public class VirtualView extends ReactViewGroup {
super.invalidate();
}
- private void clearCache() {
+ void clearCache() {
canvasDiagonal = -1;
canvasHeight = -1;
canvasWidth = -1;
diff --git a/elements/ClipPath.js b/elements/ClipPath.js
index 1c3c91b7..15a1807f 100644
--- a/elements/ClipPath.js
+++ b/elements/ClipPath.js
@@ -7,12 +7,13 @@ export default class ClipPath extends Shape {
static displayName = 'ClipPath';
render() {
- const { id, children } = this.props;
+ const { props } = this;
+ const { id, children } = props;
return (
{children}
diff --git a/elements/Image.js b/elements/Image.js
index 7b6e13b5..f4c36b98 100644
--- a/elements/Image.js
+++ b/elements/Image.js
@@ -39,7 +39,9 @@ export default class SvgImage extends Shape {
height={height}
meetOrSlice={meetOrSliceTypes[modes[1]] || 0}
align={alignEnum[modes[0]] || 'xMidYMid'}
- src={Image.resolveAssetSource(href)}
+ src={Image.resolveAssetSource(
+ typeof href === 'string' ? { uri: href } : href,
+ )}
/>
);
}
diff --git a/elements/Svg.js b/elements/Svg.js
index 1bac2f4a..2ed783b8 100644
--- a/elements/Svg.js
+++ b/elements/Svg.js
@@ -49,9 +49,12 @@ export default class Svg extends Shape {
this.root.setNativeProps(props);
};
- toDataURL = callback => {
- callback &&
- RNSVGSvgViewManager.toDataURL(findNodeHandle(this.root), callback);
+ toDataURL = (callback, options) => {
+ if (!callback) {
+ return;
+ }
+ const handle = findNodeHandle(this.root);
+ RNSVGSvgViewManager.toDataURL(handle, options, callback);
};
render() {
diff --git a/elements/TSpan.js b/elements/TSpan.js
index 2362026d..94872861 100644
--- a/elements/TSpan.js
+++ b/elements/TSpan.js
@@ -14,30 +14,24 @@ export default class TSpan extends Shape {
if (matrix) {
props.matrix = matrix;
}
- const text = pickNotNil(extractText(props, false));
- this.root.setNativeProps({
- ...props,
- ...text,
- });
+ const prop = propsAndStyles(props);
+ Object.assign(prop, pickNotNil(extractText(prop, false)));
+ this.root.setNativeProps(prop);
};
render() {
- const props = this.props;
- const prop = propsAndStyles(props);
- return (
-
+ const prop = propsAndStyles(this.props);
+ const props = extractProps(
+ {
+ ...prop,
+ x: null,
+ y: null,
+ },
+ this,
);
+ Object.assign(props, extractText(prop, false));
+ props.ref = this.refMethod;
+ return ;
}
}
diff --git a/elements/Text.js b/elements/Text.js
index 043472f7..24a586cd 100644
--- a/elements/Text.js
+++ b/elements/Text.js
@@ -15,30 +15,24 @@ export default class Text extends Shape {
if (matrix) {
props.matrix = matrix;
}
- const text = pickNotNil(extractText(props, true));
- this.root.setNativeProps({
- ...props,
- ...text,
- });
+ const prop = propsAndStyles(props);
+ Object.assign(prop, pickNotNil(extractText(prop, true)));
+ this.root.setNativeProps(prop);
};
render() {
- const props = this.props;
- const prop = propsAndStyles(props);
- return (
-
+ const prop = propsAndStyles(this.props);
+ const props = extractProps(
+ {
+ ...prop,
+ x: null,
+ y: null,
+ },
+ this,
);
+ Object.assign(props, extractText(prop, true));
+ props.ref = this.refMethod;
+ return ;
}
}
diff --git a/elements/TextPath.js b/elements/TextPath.js
index 3ba7a754..7d2ddd2c 100644
--- a/elements/TextPath.js
+++ b/elements/TextPath.js
@@ -15,11 +15,8 @@ export default class TextPath extends Shape {
if (matrix) {
props.matrix = matrix;
}
- const text = pickNotNil(extractText(props, true));
- this.root.setNativeProps({
- ...props,
- ...text,
- });
+ Object.assign(props, pickNotNil(extractText(props, true)));
+ this.root.setNativeProps(props);
};
render() {
@@ -27,45 +24,45 @@ export default class TextPath extends Shape {
children,
xlinkHref,
href = xlinkHref,
- startOffset,
+ startOffset = 0,
method,
spacing,
side,
alignmentBaseline,
midLine,
- ...props
+ ...prop
} = this.props;
const matched = href && href.match(idPattern);
const match = matched && matched[1];
if (match) {
- return (
-
+ const props = extractProps(
+ {
+ ...propsAndStyles(prop),
+ x: null,
+ y: null,
+ },
+ this,
);
+ Object.assign(
+ props,
+ extractText(
+ {
+ children,
+ },
+ true,
+ ),
+ {
+ href: match,
+ startOffset,
+ method,
+ spacing,
+ side,
+ alignmentBaseline,
+ midLine,
+ },
+ );
+ props.ref = this.refMethod;
+ return ;
}
console.warn(
diff --git a/index.d.ts b/index.d.ts
index ec02938e..5f1166e8 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -80,7 +80,7 @@ export interface TouchableProps {
}
export interface ResponderProps extends ReactNative.GestureResponderHandlers {
- pointerEvents?: (event: any) => any,
+ pointerEvents?: "box-none" | "none" | "box-only" | "auto",
}
// rgba values inside range 0 to 1 inclusive
@@ -101,6 +101,10 @@ export interface ClipProps {
clipRule?: FillRule,
clipPath?: string
}
+
+interface VectorEffectProps {
+ vectorEffect?: "none" | "non-scaling-stroke" | "nonScalingStroke" | "default" | "inherit" | "uri";
+}
export interface DefinitionProps {
id?: string,
@@ -180,7 +184,7 @@ export interface CommonMaskProps {
mask?: string;
}
-export interface CommonPathProps extends FillProps, StrokeProps, ClipProps, TransformProps, ResponderProps, TouchableProps, DefinitionProps, CommonMaskProps {}
+export interface CommonPathProps extends FillProps, StrokeProps, ClipProps, TransformProps, VectorEffectProps, ResponderProps, TouchableProps, DefinitionProps, CommonMaskProps {}
// Element props
export interface CircleProps extends CommonPathProps {
@@ -306,11 +310,13 @@ export interface StopProps {
}
export const Stop: React.ComponentClass;
-export interface SvgProps extends ReactNative.ViewProperties {
+export interface SvgProps extends GProps, ReactNative.ViewProperties {
width?: NumberProp,
height?: NumberProp,
viewBox?: string,
preserveAspectRatio?: string,
+ color?: int32ARGBColor | rgbaArray | string,
+ title?: string,
}
// Svg is both regular and default exported
diff --git a/index.web.js b/index.web.js
new file mode 100644
index 00000000..2d830be6
--- /dev/null
+++ b/index.web.js
@@ -0,0 +1,217 @@
+import { createElement } from 'react-native-web';
+import { resolve } from './lib/resolve';
+import { Component } from 'react';
+
+/**
+ * `react-native-svg` supports additional props that aren't defined in the spec.
+ * This function replaces them in a spec conforming manner.
+ *
+ * @param {Object} props Properties given to us.
+ * @returns {Object} Cleaned object.
+ * @private
+ */
+function prepare(props) {
+ const {
+ translate,
+ scale,
+ rotation,
+ skewX,
+ skewY,
+ originX,
+ originY,
+ fontFamily,
+ fontSize,
+ fontWeight,
+ fontStyle,
+ style,
+ ...clean
+ } = props;
+
+ const transform = [];
+
+ if (originX != null || originY != null) {
+ transform.push(`translate(${originX || 0}, ${originY || 0})`);
+ }
+ if (translate != null) {
+ transform.push(`translate(${translate})`);
+ }
+ if (scale != null) {
+ transform.push(`scale(${scale})`);
+ }
+ // rotation maps to rotate, not to collide with the text rotate attribute (which acts per glyph rather than block)
+ if (rotation != null) {
+ transform.push(`rotate(${rotation})`);
+ }
+ if (skewX != null) {
+ transform.push(`skewX(${skewX})`);
+ }
+ if (skewY != null) {
+ transform.push(`skewY(${skewY})`);
+ }
+ if (originX != null || originY != null) {
+ transform.push(`translate(${-originX || 0}, ${-originY || 0})`);
+ }
+
+ if (transform.length) {
+ clean.transform = transform.join(' ');
+ }
+
+ const styles = {};
+
+ if (fontFamily != null) {
+ styles.fontFamily = fontFamily;
+ }
+ if (fontSize != null) {
+ styles.fontSize = fontSize;
+ }
+ if (fontWeight != null) {
+ styles.fontWeight = fontWeight;
+ }
+ if (fontStyle != null) {
+ styles.fontStyle = fontStyle;
+ }
+
+ clean.style = resolve(style, styles);
+
+ return clean;
+}
+
+export class Circle extends Component {
+ render() {
+ return createElement('circle', prepare(this.props));
+ }
+}
+
+export class ClipPath extends Component {
+ render() {
+ return createElement('clipPath', prepare(this.props));
+ }
+}
+
+export class Defs extends Component {
+ render() {
+ return createElement('defs', prepare(this.props));
+ }
+}
+
+export class Ellipse extends Component {
+ render() {
+ return createElement('ellipse', prepare(this.props));
+ }
+}
+
+export class G extends Component {
+ render() {
+ const { x, y, ...rest } = this.props;
+
+ if ((x || y) && !rest.translate) {
+ rest.translate = `${x || 0}, ${y || 0}`;
+ }
+
+ return createElement('g', prepare(rest));
+ }
+}
+
+export class Image extends Component {
+ render() {
+ return createElement('image', prepare(this.props));
+ }
+}
+
+export class Line extends Component {
+ render() {
+ return createElement('line', prepare(this.props));
+ }
+}
+
+export class LinearGradient extends Component {
+ render() {
+ return createElement('linearGradient', prepare(this.props));
+ }
+}
+
+export class Path extends Component {
+ render() {
+ return createElement('path', prepare(this.props));
+ }
+}
+
+export class Polygon extends Component {
+ render() {
+ return createElement('polygon', prepare(this.props));
+ }
+}
+
+export class Polyline extends Component {
+ render() {
+ return createElement('polyline', prepare(this.props));
+ }
+}
+
+export class RadialGradient extends Component {
+ render() {
+ return createElement('radialGradient', prepare(this.props));
+ }
+}
+
+export class Rect extends Component {
+ render() {
+ return createElement('rect', prepare(this.props));
+ }
+}
+
+export class Stop extends Component {
+ render() {
+ return createElement('stop', prepare(this.props));
+ }
+}
+
+export class Svg extends Component {
+ render() {
+ return createElement('svg', prepare(this.props));
+ }
+}
+
+export class Symbol extends Component {
+ render() {
+ return createElement('symbol', prepare(this.props));
+ }
+}
+
+export class Text extends Component {
+ render() {
+ return createElement('text', prepare(this.props));
+ }
+}
+
+export class TSpan extends Component {
+ render() {
+ return createElement('tspan', prepare(this.props));
+ }
+}
+
+export class TextPath extends Component {
+ render() {
+ return createElement('textPath', prepare(this.props));
+ }
+}
+
+export class Use extends Component {
+ render() {
+ return createElement('use', prepare(this.props));
+ }
+}
+
+export class Mask extends Component {
+ render() {
+ return createElement('mask', prepare(this.props));
+ }
+}
+
+export class Pattern extends Component {
+ render() {
+ return createElement('pattern', prepare(this.props));
+ }
+}
+
+export default Svg;
diff --git a/ios/Elements/RNSVGGroup.m b/ios/Elements/RNSVGGroup.m
index fc75689a..7a26cd67 100644
--- a/ios/Elements/RNSVGGroup.m
+++ b/ios/Elements/RNSVGGroup.m
@@ -174,7 +174,7 @@
}
if (!event) {
- NSPredicate *const anyActive = [NSPredicate predicateWithFormat:@"active == TRUE"];
+ NSPredicate *const anyActive = [NSPredicate predicateWithFormat:@"self isKindOfClass: %@ AND active == TRUE", [RNSVGNode class]];
NSArray *const filtered = [self.subviews filteredArrayUsingPredicate:anyActive];
if ([filtered count] != 0) {
return [filtered.lastObject hitTest:transformed withEvent:event];
diff --git a/ios/Elements/RNSVGImage.m b/ios/Elements/RNSVGImage.m
index 3d8dc444..6d7bddf8 100644
--- a/ios/Elements/RNSVGImage.m
+++ b/ios/Elements/RNSVGImage.m
@@ -10,6 +10,7 @@
#import "RCTConvert+RNSVG.h"
#import
#import
+#import
#import
#import "RNSVGViewBox.h"
diff --git a/ios/Elements/RNSVGSvgView.h b/ios/Elements/RNSVGSvgView.h
index 99112606..808c41ab 100644
--- a/ios/Elements/RNSVGSvgView.h
+++ b/ios/Elements/RNSVGSvgView.h
@@ -52,6 +52,8 @@
- (NSString *)getDataURL;
+- (NSString *)getDataURLwithBounds:(CGRect)bounds;
+
- (CGRect)getContextBounds;
- (void)drawRect:(CGRect)rect;
diff --git a/ios/Elements/RNSVGSvgView.m b/ios/Elements/RNSVGSvgView.m
index 8b71ba30..856a83d4 100644
--- a/ios/Elements/RNSVGSvgView.m
+++ b/ios/Elements/RNSVGSvgView.m
@@ -247,11 +247,26 @@
return nil;
}
-
- (NSString *)getDataURL
{
UIGraphicsBeginImageContextWithOptions(_boundingBox.size, NO, 0);
+ [self clearChildCache];
[self drawRect:_boundingBox];
+ [self clearChildCache];
+ [self invalidate];
+ NSData *imageData = UIImagePNGRepresentation(UIGraphicsGetImageFromCurrentImageContext());
+ NSString *base64 = [imageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
+ UIGraphicsEndImageContext();
+ return base64;
+}
+
+- (NSString *)getDataURLwithBounds:(CGRect)bounds
+{
+ UIGraphicsBeginImageContextWithOptions(bounds.size, NO, 0);
+ [self clearChildCache];
+ [self drawRect:bounds];
+ [self clearChildCache];
+ [self invalidate];
NSData *imageData = UIImagePNGRepresentation(UIGraphicsGetImageFromCurrentImageContext());
NSString *base64 = [imageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
UIGraphicsEndImageContext();
diff --git a/ios/Elements/RNSVGUse.m b/ios/Elements/RNSVGUse.m
index 5a42b568..14047b06 100644
--- a/ios/Elements/RNSVGUse.m
+++ b/ios/Elements/RNSVGUse.m
@@ -89,6 +89,9 @@
} else if (self.href) {
// TODO: calling yellow box here
RCTLogWarn(@"`Use` element expected a pre-defined svg template as `href` prop, template named: %@ is not defined.", self.href);
+ return;
+ } else {
+ return;
}
CGRect bounds = template.clientRect;
self.clientRect = bounds;
@@ -120,5 +123,16 @@
return nil;
}
+- (CGPathRef)getPath: (CGContextRef)context
+{
+ CGAffineTransform transform = CGAffineTransformMakeTranslation([self relativeOnWidth:self.x], [self relativeOnHeight:self.y]);
+ RNSVGNode const* template = [self.svgView getDefinedTemplate:self.href];
+ if (!template) {
+ return nil;
+ }
+ CGPathRef path = [template getPath:context];
+ return CGPathCreateCopyByTransformingPath(path, &transform);
+}
+
@end
diff --git a/ios/RNSVG.xcodeproj/project.pbxproj b/ios/RNSVG.xcodeproj/project.pbxproj
index 8e861247..819a6631 100644
--- a/ios/RNSVG.xcodeproj/project.pbxproj
+++ b/ios/RNSVG.xcodeproj/project.pbxproj
@@ -251,6 +251,7 @@
94241669213B0DB800088E93 /* RNSVGPattern.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNSVGPattern.h; sourceTree = ""; };
9424166A213B2FF100088E93 /* RNSVGPatternManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNSVGPatternManager.h; sourceTree = ""; };
9424166C213B302600088E93 /* RNSVGPatternManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNSVGPatternManager.m; sourceTree = ""; };
+ 94696EE92235A7F200C1D558 /* RNSVGVectorEffect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGVectorEffect.h; path = Utils/RNSVGVectorEffect.h; sourceTree = ""; };
947F3809214810B800677F2A /* RNSVGMask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGMask.h; path = Elements/RNSVGMask.h; sourceTree = ""; };
947F380A214810DC00677F2A /* RNSVGMask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGMask.m; path = Elements/RNSVGMask.m; sourceTree = ""; };
947F380D2148118300677F2A /* RNSVGMaskManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGMaskManager.h; sourceTree = ""; };
@@ -463,6 +464,7 @@
1039D29A1CE7212C001E90A8 /* Utils */ = {
isa = PBXGroup;
children = (
+ 94696EE92235A7F200C1D558 /* RNSVGVectorEffect.h */,
B56895A920352B36004DBF1E /* RNSVGBezierElement.h */,
B56895A820352B35004DBF1E /* RNSVGBezierElement.m */,
7F69160D1E3703D800DA6EDC /* RNSVGUnits.h */,
diff --git a/ios/RNSVGNode.h b/ios/RNSVGNode.h
index bb94d891..83dde07e 100644
--- a/ios/RNSVGNode.h
+++ b/ios/RNSVGNode.h
@@ -118,4 +118,6 @@ extern CGFloat const RNSVG_DEFAULT_FONT_SIZE;
- (void)clearChildCache;
+- (void)clearPath;
+
@end
diff --git a/ios/RNSVGNode.m b/ios/RNSVGNode.m
index 7d491e21..0c622d21 100644
--- a/ios/RNSVGNode.m
+++ b/ios/RNSVGNode.m
@@ -290,7 +290,7 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12;
if (_clipMask) {
CGImageRelease(_clipMask);
}
- if ([_clipNode isSimpleClipPath]) {
+ if ([_clipNode isSimpleClipPath] || _clipNode.clipRule == kRNSVGCGFCRuleEvenodd) {
_clipMask = nil;
} else {
CGRect bounds = CGContextGetClipBoundingBox(context);
diff --git a/ios/RNSVGRenderable.h b/ios/RNSVGRenderable.h
index d59d48ad..2973f25b 100644
--- a/ios/RNSVGRenderable.h
+++ b/ios/RNSVGRenderable.h
@@ -12,6 +12,7 @@
#import "RNSVGCGFCRule.h"
#import "RNSVGNode.h"
#import "RNSVGLength.h"
+#import "RNSVGVectorEffect.h"
#import "RNSVGPercentageConverter.h"
@interface RNSVGRenderable : RNSVGNode
@@ -27,6 +28,7 @@
@property (nonatomic, assign) CGFloat strokeMiterlimit;
@property (nonatomic, strong) NSArray *strokeDasharray;
@property (nonatomic, assign) CGFloat strokeDashoffset;
+@property (nonatomic, assign) RNSVGVectorEffect vectorEffect;
@property (nonatomic, copy) NSArray *propList;
- (void)setHitArea:(CGPathRef)path;
diff --git a/ios/RNSVGRenderable.m b/ios/RNSVGRenderable.m
index d8067aae..e19db35a 100644
--- a/ios/RNSVGRenderable.m
+++ b/ios/RNSVGRenderable.m
@@ -10,6 +10,7 @@
#import "RNSVGClipPath.h"
#import "RNSVGMask.h"
#import "RNSVGViewBox.h"
+#import "RNSVGVectorEffect.h"
@implementation RNSVGRenderable
{
@@ -143,6 +144,15 @@
_strokeDashoffset = strokeDashoffset;
}
+- (void)setVectorEffect:(RNSVGVectorEffect)vectorEffect
+{
+ if (vectorEffect == _vectorEffect) {
+ return;
+ }
+ [self invalidate];
+ _vectorEffect = vectorEffect;
+}
+
- (void)setPropList:(NSArray *)propList
{
if (propList == _propList) {
@@ -305,6 +315,11 @@ UInt32 saturate(CGFloat value) {
self.clientRect = clientRect;
+ if (_vectorEffect == kRNSVGVectorEffectNonScalingStroke) {
+ path = CGPathCreateCopyByTransformingPath(path, &svgToClientTransform);
+ CGContextConcatCTM(context, CGAffineTransformInvert(svgToClientTransform));
+ }
+
CGAffineTransform vbmatrix = self.svgView.getViewBoxTransform;
CGAffineTransform transform = CGAffineTransformConcat(self.matrix, self.transforms);
CGAffineTransform matrix = CGAffineTransformConcat(transform, vbmatrix);
@@ -493,12 +508,12 @@ UInt32 saturate(CGFloat value) {
- (void)mergeProperties:(__kindof RNSVGRenderable *)target
{
- self.merging = true;
NSArray *targetAttributeList = [target getAttributeList];
if (targetAttributeList.count == 0) {
return;
}
+ self.merging = true;
NSMutableArray* attributeList = [self.propList mutableCopy];
_originProperties = [[NSMutableDictionary alloc] init];
diff --git a/ios/Text/RNSVGFontData.m b/ios/Text/RNSVGFontData.m
index 0d902ac0..9b9d9805 100644
--- a/ios/Text/RNSVGFontData.m
+++ b/ios/Text/RNSVGFontData.m
@@ -84,25 +84,40 @@ RNSVGFontData *RNSVGFontData_Defaults;
NSString* decoration = [font objectForKey:TEXT_DECORATION];
data->textDecoration = decoration ? RNSVGTextDecorationFromString(decoration) : parent->textDecoration;
- NSString* kerning = [font objectForKey:KERNING];
- data->manualKerning = (kerning || parent->manualKerning );
CGFloat fontSize = data->fontSize;
- data->kerning = kerning ?
- [RNSVGFontData toAbsoluteWithNSString:kerning
- fontSize:fontSize]
- : parent->kerning;
+ id kerning = [font objectForKey:KERNING];
+ data->manualKerning = (kerning || parent->manualKerning );
+ if ([kerning isKindOfClass:NSNumber.class]) {
+ NSNumber* kern = kerning;
+ data->kerning = (CGFloat)[kern doubleValue];
+ } else {
+ data->kerning = kerning ?
+ [RNSVGFontData toAbsoluteWithNSString:kerning
+ fontSize:fontSize]
+ : parent->kerning;
+ }
- NSString* wordSpacing = [font objectForKey:WORD_SPACING];
- data->wordSpacing = wordSpacing ?
- [RNSVGFontData toAbsoluteWithNSString:wordSpacing
- fontSize:fontSize]
- : parent->wordSpacing;
+ id wordSpacing = [font objectForKey:WORD_SPACING];
+ if ([wordSpacing isKindOfClass:NSNumber.class]) {
+ NSNumber* ws = wordSpacing;
+ data->wordSpacing = (CGFloat)[ws doubleValue];
+ } else {
+ data->wordSpacing = wordSpacing ?
+ [RNSVGFontData toAbsoluteWithNSString:wordSpacing
+ fontSize:fontSize]
+ : parent->wordSpacing;
+ }
- NSString* letterSpacing = [font objectForKey:LETTER_SPACING];
- data->letterSpacing = letterSpacing ?
- [RNSVGFontData toAbsoluteWithNSString:letterSpacing
- fontSize:fontSize]
- : parent->letterSpacing;
+ id letterSpacing = [font objectForKey:LETTER_SPACING];
+ if ([letterSpacing isKindOfClass:NSNumber.class]) {
+ NSNumber* ls = letterSpacing;
+ data->wordSpacing = (CGFloat)[ls doubleValue];
+ } else {
+ data->letterSpacing = letterSpacing ?
+ [RNSVGFontData toAbsoluteWithNSString:letterSpacing
+ fontSize:fontSize]
+ : parent->letterSpacing;
+ }
return data;
}
diff --git a/ios/Text/RNSVGGlyphContext.h b/ios/Text/RNSVGGlyphContext.h
index 723b12fc..de49110e 100644
--- a/ios/Text/RNSVGGlyphContext.h
+++ b/ios/Text/RNSVGGlyphContext.h
@@ -44,5 +44,6 @@
- (void)pushContext:(RNSVGGroup*)node
font:(NSDictionary *)font;
+- (NSArray*)getFontContext;
@end
diff --git a/ios/Text/RNSVGGlyphContext.m b/ios/Text/RNSVGGlyphContext.m
index 93056dcd..43f30a12 100644
--- a/ios/Text/RNSVGGlyphContext.m
+++ b/ios/Text/RNSVGGlyphContext.m
@@ -107,6 +107,9 @@
@implementation RNSVGGlyphContext
+- (NSArray*)getFontContext {
+ return mFontContext_;
+}
- (CTFontRef)getGlyphFont
{
diff --git a/ios/Text/RNSVGTSpan.m b/ios/Text/RNSVGTSpan.m
index 70b11adc..11a0e98c 100644
--- a/ios/Text/RNSVGTSpan.m
+++ b/ios/Text/RNSVGTSpan.m
@@ -25,6 +25,7 @@ static CGFloat RNSVGTSpan_radToDeg = 180 / (CGFloat)M_PI;
BOOL isClosed;
NSMutableArray *emoji;
NSMutableArray *emojiTransform;
+ CGFloat cachedAdvance;
}
- (id)init
@@ -41,6 +42,12 @@ static CGFloat RNSVGTSpan_radToDeg = 180 / (CGFloat)M_PI;
return self;
}
+- (void)clearPath
+{
+ [super clearPath];
+ cachedAdvance = NAN;
+}
+
- (void)setContent:(NSString *)content
{
if ([content isEqualToString:_content]) {
@@ -100,6 +107,69 @@ static CGFloat RNSVGTSpan_radToDeg = 180 / (CGFloat)M_PI;
return path;
}
+- (CGFloat)getSubtreeTextChunksTotalAdvance
+{
+ if (!isnan(cachedAdvance)) {
+ return cachedAdvance;
+ }
+ CGFloat advance = 0;
+
+ NSString *str = self.content;
+ if (!str) {
+ for (UIView *node in self.subviews) {
+ if ([node isKindOfClass:[RNSVGText class]]) {
+ RNSVGText *text = (RNSVGText*)node;
+ advance += [text getSubtreeTextChunksTotalAdvance];
+ }
+ }
+ cachedAdvance = advance;
+ return advance;
+ }
+
+ // Create a dictionary for this font
+ CTFontRef fontRef = [self getFontFromContext];
+ RNSVGGlyphContext* gc = [self.textRoot getGlyphContext];
+ RNSVGFontData* font = [gc getFont];
+
+ CGFloat letterSpacing = font->letterSpacing;
+ CGFloat kerning = font->kerning;
+
+ bool allowOptionalLigatures = letterSpacing == 0 && font->fontVariantLigatures == RNSVGFontVariantLigaturesNormal;
+
+ NSMutableDictionary *attrs = [[NSMutableDictionary alloc] init];
+
+ NSNumber *lig = [NSNumber numberWithInt:allowOptionalLigatures ? 2 : 1];
+ attrs[NSLigatureAttributeName] = lig;
+ CFDictionaryRef attributes;
+ if (fontRef != nil) {
+ attrs[NSFontAttributeName] = (__bridge id)fontRef;
+ }
+ float kern = (float)(letterSpacing + kerning);
+ NSNumber *kernAttr = [NSNumber numberWithFloat:kern];
+
+#if DTCORETEXT_SUPPORT_NS_ATTRIBUTES
+ if (___useiOS6Attributes)
+ {
+ [attrs setObject:kernAttr forKey:NSKernAttributeName];
+ }
+ else
+#endif
+ {
+ [attrs setObject:kernAttr forKey:(id)kCTKernAttributeName];
+ }
+
+ attributes = (__bridge CFDictionaryRef)attrs;
+
+ CFStringRef string = (__bridge CFStringRef)str;
+ CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes);
+ CTLineRef line = CTLineCreateWithAttributedString(attrString);
+
+ CGRect textBounds = CTLineGetBoundsWithOptions(line, 0);
+ CGFloat textMeasure = CGRectGetWidth(textBounds);
+ cachedAdvance = textMeasure;
+ return textMeasure;
+}
+
- (CGPathRef)getLinePath:(NSString *)str context:(CGContextRef)context
{
// Create a dictionary for this font
@@ -292,8 +362,8 @@ static CGFloat RNSVGTSpan_radToDeg = 180 / (CGFloat)M_PI;
attributes, such as a ‘dx’ attribute value on a ‘tspan’ element.
*/
enum RNSVGTextAnchor textAnchor = font->textAnchor;
- CGRect textBounds = CTLineGetBoundsWithOptions(line, 0);
- CGFloat textMeasure = CGRectGetWidth(textBounds);
+ RNSVGText *anchorRoot = [self getTextAnchorRoot];
+ CGFloat textMeasure = [anchorRoot getSubtreeTextChunksTotalAdvance];
CGFloat offset = [RNSVGTSpan getTextAnchorOffset:textAnchor width:textMeasure];
bool hasTextPath = textPath != nil;
diff --git a/ios/Text/RNSVGText.h b/ios/Text/RNSVGText.h
index 138f18f9..913ce61d 100644
--- a/ios/Text/RNSVGText.h
+++ b/ios/Text/RNSVGText.h
@@ -23,5 +23,7 @@
- (CGPathRef)getGroupPath:(CGContextRef)context;
- (CTFontRef)getFontFromContext;
+- (CGFloat)getSubtreeTextChunksTotalAdvance;
+- (RNSVGText*)getTextAnchorRoot;
@end
diff --git a/ios/Text/RNSVGText.m b/ios/Text/RNSVGText.m
index 7efc8cf0..ee94c96a 100644
--- a/ios/Text/RNSVGText.m
+++ b/ios/Text/RNSVGText.m
@@ -18,6 +18,7 @@
RNSVGGlyphContext *_glyphContext;
NSString *_alignmentBaseline;
NSString *_baselineShift;
+ CGFloat cachedAdvance;
}
- (void)invalidate
@@ -29,6 +30,12 @@
[self clearChildCache];
}
+- (void)clearPath
+{
+ [super clearPath];
+ cachedAdvance = NAN;
+}
+
- (void)setTextLength:(RNSVGLength *)textLength
{
if ([textLength isEqualTo:_textLength]) {
@@ -248,4 +255,39 @@
return [[self.textRoot getGlyphContext] getGlyphFont];
}
+- (RNSVGText*)getTextAnchorRoot
+{
+ RNSVGGlyphContext* gc = [self.textRoot getGlyphContext];
+ NSArray* font = [gc getFontContext];
+ RNSVGText* node = self;
+ UIView* parent = [self superview];
+ for (NSInteger i = [font count] - 1; i >= 0; i--) {
+ RNSVGFontData* fontData = [font objectAtIndex:i];
+ if (![parent isKindOfClass:[RNSVGText class]] ||
+ fontData->textAnchor == RNSVGTextAnchorStart ||
+ node.positionX != nil) {
+ return node;
+ }
+ node = (RNSVGText*) parent;
+ parent = [node superview];
+ }
+ return node;
+}
+
+- (CGFloat)getSubtreeTextChunksTotalAdvance
+{
+ if (!isnan(cachedAdvance)) {
+ return cachedAdvance;
+ }
+ CGFloat advance = 0;
+ for (UIView *node in self.subviews) {
+ if ([node isKindOfClass:[RNSVGText class]]) {
+ RNSVGText *text = (RNSVGText*)node;
+ advance += [text getSubtreeTextChunksTotalAdvance];
+ }
+ }
+ cachedAdvance = advance;
+ return advance;
+}
+
@end
diff --git a/ios/Utils/RNSVGVectorEffect.h b/ios/Utils/RNSVGVectorEffect.h
new file mode 100644
index 00000000..0ab17036
--- /dev/null
+++ b/ios/Utils/RNSVGVectorEffect.h
@@ -0,0 +1,14 @@
+/**
+ * Copyright (c) 2015-present, react-native-community.
+ * 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.
+ */
+
+typedef CF_ENUM(int32_t, RNSVGVectorEffect) {
+ kRNSVGVectorEffectDefault,
+ kRNSVGVectorEffectNonScalingStroke,
+ kRNSVGVectorEffectInherit,
+ kRNSVGVectorEffectUri
+};
diff --git a/ios/ViewManagers/RNSVGGroupManager.m b/ios/ViewManagers/RNSVGGroupManager.m
index ffac110f..8daa2335 100644
--- a/ios/ViewManagers/RNSVGGroupManager.m
+++ b/ios/ViewManagers/RNSVGGroupManager.m
@@ -21,4 +21,16 @@ RCT_EXPORT_MODULE()
RCT_EXPORT_VIEW_PROPERTY(font, NSDictionary)
+RCT_CUSTOM_VIEW_PROPERTY(fontSize, id, RNSVGGroup)
+{
+ if ([json isKindOfClass:[NSString class]]) {
+ NSString *stringValue = (NSString *)json;
+ view.font = @{ @"fontSize": stringValue };
+ } else {
+ NSNumber* number = (NSNumber*)json;
+ double num = [number doubleValue];
+ view.font = @{@"fontSize": [NSNumber numberWithDouble:num] };
+ }
+}
+
@end
diff --git a/ios/ViewManagers/RNSVGRenderableManager.m b/ios/ViewManagers/RNSVGRenderableManager.m
index befdd937..fae93de7 100644
--- a/ios/ViewManagers/RNSVGRenderableManager.m
+++ b/ios/ViewManagers/RNSVGRenderableManager.m
@@ -31,6 +31,7 @@ RCT_EXPORT_VIEW_PROPERTY(strokeLinejoin, CGLineJoin)
RCT_EXPORT_VIEW_PROPERTY(strokeDasharray, NSArray)
RCT_EXPORT_VIEW_PROPERTY(strokeDashoffset, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(strokeMiterlimit, CGFloat)
+RCT_EXPORT_VIEW_PROPERTY(vectorEffect, int)
RCT_EXPORT_VIEW_PROPERTY(propList, NSArray)
@end
diff --git a/ios/ViewManagers/RNSVGSvgViewManager.m b/ios/ViewManagers/RNSVGSvgViewManager.m
index ae32ae60..c07a9f08 100644
--- a/ios/ViewManagers/RNSVGSvgViewManager.m
+++ b/ios/ViewManagers/RNSVGSvgViewManager.m
@@ -8,6 +8,7 @@
#import
#import
+#import
#import "RNSVGSvgViewManager.h"
#import "RNSVGSvgView.h"
@@ -30,17 +31,52 @@ RCT_EXPORT_VIEW_PROPERTY(align, NSString)
RCT_EXPORT_VIEW_PROPERTY(meetOrSlice, RNSVGVBMOS)
RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor)
-RCT_EXPORT_METHOD(toDataURL:(nonnull NSNumber *)reactTag callback:(RCTResponseSenderBlock)callback)
-{
+
+- (void)toDataURL:(nonnull NSNumber *)reactTag options:(NSDictionary *)options callback:(RCTResponseSenderBlock)callback attempt:(int)attempt {
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) {
__kindof UIView *view = viewRegistry[reactTag];
+ NSString * b64;
if ([view isKindOfClass:[RNSVGSvgView class]]) {
RNSVGSvgView *svg = view;
- callback(@[[svg getDataURL]]);
+ if (options == nil) {
+ b64 = [svg getDataURL];
+ } else {
+ id width = [options objectForKey:@"width"];
+ id height = [options objectForKey:@"height"];
+ if (![width isKindOfClass:NSNumber.class] ||
+ ![height isKindOfClass:NSNumber.class]) {
+ RCTLogError(@"Invalid width or height given to toDataURL");
+ return;
+ }
+ NSNumber* w = width;
+ NSInteger wi = (NSInteger)[w intValue];
+ NSNumber* h = height;
+ NSInteger hi = (NSInteger)[h intValue];
+
+ CGRect bounds = CGRectMake(0, 0, wi, hi);
+ b64 = [svg getDataURLwithBounds:bounds];
+ }
} else {
RCTLogError(@"Invalid svg returned frin registry, expecting RNSVGSvgView, got: %@", view);
+ return;
+ }
+ if (b64) {
+ callback(@[b64]);
+ } else if (attempt < 1) {
+ void (^retryBlock)(void) = ^{
+ [self toDataURL:reactTag options:options callback:callback attempt:(attempt + 1)];
+ };
+
+ RCTExecuteOnUIManagerQueue(retryBlock);
+ } else {
+ callback(@[]);
}
}];
}
+RCT_EXPORT_METHOD(toDataURL:(nonnull NSNumber *)reactTag options:(NSDictionary *)options callback:(RCTResponseSenderBlock)callback)
+{
+ [self toDataURL:reactTag options:options callback:callback attempt:0];
+}
+
@end
diff --git a/ios/ViewManagers/RNSVGTextManager.m b/ios/ViewManagers/RNSVGTextManager.m
index 93d8e536..a273ff08 100644
--- a/ios/ViewManagers/RNSVGTextManager.m
+++ b/ios/ViewManagers/RNSVGTextManager.m
@@ -68,4 +68,16 @@ RCT_CUSTOM_VIEW_PROPERTY(baselineShift, id, RNSVGText)
RCT_EXPORT_VIEW_PROPERTY(lengthAdjust, NSString)
RCT_EXPORT_VIEW_PROPERTY(alignmentBaseline, NSString)
+RCT_CUSTOM_VIEW_PROPERTY(fontSize, id, RNSVGGroup)
+{
+ if ([json isKindOfClass:[NSString class]]) {
+ NSString *stringValue = (NSString *)json;
+ view.font = @{ @"fontSize": stringValue };
+ } else {
+ NSNumber* number = (NSNumber*)json;
+ double num = [number doubleValue];
+ view.font = @{@"fontSize": [NSNumber numberWithDouble:num] };
+ }
+}
+
@end
diff --git a/lib/Matrix2D.js b/lib/Matrix2D.js
index 763e607a..63434c70 100644
--- a/lib/Matrix2D.js
+++ b/lib/Matrix2D.js
@@ -4,6 +4,8 @@
*/
const DEG_TO_RAD = Math.PI / 180;
+export const identity = [1, 0, 0, 1, 0, 0];
+
/**
* Represents an affine transformation matrix, and provides tools for constructing and concatenating matrices.
*
@@ -85,6 +87,9 @@ export default class Matrix2D {
* @return {Array} an array with current matrix values.
**/
toArray = function() {
+ if (this.hasInitialState) {
+ return identity;
+ }
return [this.a, this.b, this.c, this.d, this.tx, this.ty];
};
diff --git a/lib/extract/extractBrush.js b/lib/extract/extractBrush.js
index 882fadfe..38e8f12b 100644
--- a/lib/extract/extractBrush.js
+++ b/lib/extract/extractBrush.js
@@ -1,5 +1,4 @@
-import extractColor from './extractColor';
-import { Platform } from 'react-native';
+import extractColor, { integerColor } from './extractColor';
const urlIdPattern = /^url\(#(.+)\)$/;
@@ -8,7 +7,7 @@ const currentColorBrush = [2];
export default function extractBrush(color) {
if (typeof color === 'number') {
if (color >>> 0 === color && color >= 0 && color <= 0xffffffff) {
- return [0, Platform.OS === 'android' ? color | 0x0 : color];
+ return [0, integerColor(color)];
}
}
diff --git a/lib/extract/extractColor.js b/lib/extract/extractColor.js
index f2539279..9b1167b9 100644
--- a/lib/extract/extractColor.js
+++ b/lib/extract/extractColor.js
@@ -345,7 +345,7 @@ function rgbFromString(string) {
return null;
}
- return Platform.OS === 'android' ? rgb | 0x0 : rgb;
+ return integerColor(rgb);
} else {
return null;
}
@@ -403,11 +403,22 @@ function colorFromString(string) {
}
}
+const identity = x => x;
+
+const toSignedInt32 = x => x | 0x0;
+
+// Android use 32 bit *signed* integer to represent the color
+// We utilize the fact that bitwise operations in JS also operates on
+// signed 32 bit integers, so that we can use those to convert from
+// *unsigned* to *signed* 32bit in that way.
+export const integerColor =
+ Platform.OS === 'android' ? toSignedInt32 : identity;
+
// Returns 0xaarrggbb or null
export default function extractColor(color) {
if (typeof color === 'number') {
if (color >>> 0 === color && color >= 0 && color <= 0xffffffff) {
- return Platform.OS === 'android' ? color | 0x0 : color;
+ return integerColor(color);
}
return null;
}
@@ -430,9 +441,5 @@ export default function extractColor(color) {
Math.round(b * 255)) >>>
0;
- // Android use 32 bit *signed* integer to represent the color
- // We utilize the fact that bitwise operations in JS also operates on
- // signed 32 bit integers, so that we can use those to convert from
- // *unsigned* to *signed* 32bit int that way.
- return Platform.OS === 'android' ? int32Color | 0x0 : int32Color;
+ return integerColor(int32Color);
}
diff --git a/lib/extract/extractFill.js b/lib/extract/extractFill.js
index 0ab4b437..18ec9e40 100644
--- a/lib/extract/extractFill.js
+++ b/lib/extract/extractFill.js
@@ -1,28 +1,25 @@
import extractBrush from './extractBrush';
import extractOpacity from './extractOpacity';
-import { colorNames } from './extractColor';
-import { Platform } from 'react-native';
+import { colorNames, integerColor } from './extractColor';
const fillRules = {
evenodd: 0,
nonzero: 1,
};
-const fillProps = ['fill', 'fillOpacity', 'fillRule'];
-const numFillProps = fillProps.length;
-
// default fill is black
-const defaultFill = [
- 0,
- Platform.OS === 'android' ? colorNames.black | 0x0 : colorNames.black,
-];
+const black = colorNames.black;
+const defaultFill = [0, integerColor(black)];
export default function extractFill(props, styleProperties) {
- for (let i = 0; i < numFillProps; i++) {
- const name = fillProps[i];
- if (props.hasOwnProperty(name)) {
- styleProperties.push(name);
- }
+ if (props.fill != null) {
+ styleProperties.push('fill');
+ }
+ if (props.fillOpacity != null) {
+ styleProperties.push('fillOpacity');
+ }
+ if (props.fillRule != null) {
+ styleProperties.push('fillRule');
}
const { fill, fillRule, fillOpacity } = props;
diff --git a/lib/extract/extractResponder.js b/lib/extract/extractResponder.js
index 810b9413..d8b697cf 100644
--- a/lib/extract/extractResponder.js
+++ b/lib/extract/extractResponder.js
@@ -3,29 +3,21 @@ import { PanResponder } from 'react-native';
const responderProps = Object.keys(PanResponder.create({}).panHandlers);
const numResponderProps = responderProps.length;
-const touchableProps = [
- 'disabled',
- 'onPress',
- 'onPressIn',
- 'onPressOut',
- 'onLongPress',
- 'delayPressIn',
- 'delayPressOut',
- 'delayLongPress',
-];
-const numTouchableProps = touchableProps.length;
-
function hasTouchableProperty(props) {
- for (let i = 0; i < numTouchableProps; i++) {
- if (props.hasOwnProperty(touchableProps[i])) {
- return true;
- }
- }
- return false;
+ return (
+ props.disabled != null ||
+ props.onPress ||
+ props.onPressIn ||
+ props.onPressOut ||
+ props.onLongPress ||
+ props.delayPressIn ||
+ props.delayPressOut ||
+ props.delayLongPress
+ );
}
export default function extractResponder(props, ref) {
- const extractedProps = {};
+ const o = {};
let responsible = false;
for (let i = 0; i < numResponderProps; i++) {
@@ -33,31 +25,28 @@ export default function extractResponder(props, ref) {
const value = props[key];
if (value) {
responsible = true;
- extractedProps[key] = value;
+ o[key] = value;
}
}
const pointerEvents = props.pointerEvents;
if (pointerEvents) {
- extractedProps.pointerEvents = pointerEvents;
+ o.pointerEvents = pointerEvents;
}
if (hasTouchableProperty(props)) {
responsible = true;
- Object.assign(extractedProps, {
- onStartShouldSetResponder: ref.touchableHandleStartShouldSetResponder,
- onResponderTerminationRequest:
- ref.touchableHandleResponderTerminationRequest,
- onResponderGrant: ref.touchableHandleResponderGrant,
- onResponderMove: ref.touchableHandleResponderMove,
- onResponderRelease: ref.touchableHandleResponderRelease,
- onResponderTerminate: ref.touchableHandleResponderTerminate,
- });
+ o.onResponderMove = ref.touchableHandleResponderMove;
+ o.onResponderGrant = ref.touchableHandleResponderGrant;
+ o.onResponderRelease = ref.touchableHandleResponderRelease;
+ o.onResponderTerminate = ref.touchableHandleResponderTerminate;
+ o.onStartShouldSetResponder = ref.touchableHandleStartShouldSetResponder;
+ o.onResponderTerminationRequest = ref.touchableHandleResponderTerminationRequest;
}
if (responsible) {
- extractedProps.responsible = true;
+ o.responsible = true;
}
- return extractedProps;
+ return o;
}
diff --git a/lib/extract/extractStroke.js b/lib/extract/extractStroke.js
index ec8fa516..27be678a 100644
--- a/lib/extract/extractStroke.js
+++ b/lib/extract/extractStroke.js
@@ -14,24 +14,39 @@ const joins = {
round: 1,
};
-const strokeProps = [
- 'stroke',
- 'strokeWidth',
- 'strokeOpacity',
- 'strokeDasharray',
- 'strokeDashoffset',
- 'strokeLinecap',
- 'strokeLinejoin',
- 'strokeMiterlimit',
-];
-const numStrokeProps = strokeProps.length;
+const vectorEffects = {
+ none: 0,
+ default: 0,
+ nonScalingStroke: 1,
+ 'non-scaling-stroke': 1,
+ inherit: 2,
+ uri: 3
+};
export default function extractStroke(props, styleProperties) {
- for (let i = 0; i < numStrokeProps; i++) {
- const name = strokeProps[i];
- if (props.hasOwnProperty(name)) {
- styleProperties.push(name);
- }
+ if (props.stroke != null) {
+ styleProperties.push('stroke');
+ }
+ if (props.strokeWidth != null) {
+ styleProperties.push('strokeWidth');
+ }
+ if (props.strokeOpacity != null) {
+ styleProperties.push('strokeOpacity');
+ }
+ if (props.strokeDasharray != null) {
+ styleProperties.push('strokeDasharray');
+ }
+ if (props.strokeDashoffset != null) {
+ styleProperties.push('strokeDashoffset');
+ }
+ if (props.strokeLinecap != null) {
+ styleProperties.push('strokeLinecap');
+ }
+ if (props.strokeLinejoin != null) {
+ styleProperties.push('strokeLinejoin');
+ }
+ if (props.strokeMiterlimit != null) {
+ styleProperties.push('strokeMiterlimit');
}
const { stroke, strokeWidth = 1, strokeDasharray } = props;
@@ -52,5 +67,6 @@ export default function extractStroke(props, styleProperties) {
strokeWidth,
strokeDashoffset: strokeDasharray ? +props.strokeDashoffset || 0 : null,
strokeMiterlimit: parseFloat(props.strokeMiterlimit) || 4,
+ vectorEffect: vectorEffects[props.vectorEffect] || 0,
};
}
diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js
index d908079e..4b0f3d59 100644
--- a/lib/extract/extractText.js
+++ b/lib/extract/extractText.js
@@ -33,7 +33,7 @@ function parseFontString(font) {
const isBold = /bold/.exec(match[1]);
const isItalic = /italic/.exec(match[1]);
cachedFontObjectsFromString[font] = {
- fontSize: match[2] || '12',
+ fontSize: match[2] || 12,
fontWeight: isBold ? 'bold' : 'normal',
fontStyle: isItalic ? 'italic' : 'normal',
fontFamily: extractSingleFontFamily(match[3]),
diff --git a/lib/extract/extractTransform.js b/lib/extract/extractTransform.js
index 94a76472..3bdbc5cb 100644
--- a/lib/extract/extractTransform.js
+++ b/lib/extract/extractTransform.js
@@ -1,4 +1,4 @@
-import Matrix2D from '../Matrix2D';
+import Matrix2D, { identity } from '../Matrix2D';
import transformParser from './transform';
const pooledMatrix = new Matrix2D();
@@ -30,6 +30,13 @@ function universal2axis(universal, axisX, axisY, defaultValue) {
} else if (coords.length === 1) {
x = y = +coords[0];
}
+ } else if (Array.isArray(universal)) {
+ if (universal.length === 2) {
+ x = +universal[0];
+ y = +universal[1];
+ } else if (universal.length === 1) {
+ x = y = +universal[0];
+ }
}
axisX = +axisX;
@@ -51,10 +58,8 @@ export function props2transform(props) {
const skew = universal2axis(props.skew, props.skewX, props.skewY);
const translate = universal2axis(
props.translate,
- // eslint-disable-next-line eqeqeq
- props.translateX == null ? props.x || 0 : props.translateX,
- // eslint-disable-next-line eqeqeq
- props.translateY == null ? props.y || 0 : props.translateY,
+ props.translateX || props.x,
+ props.translateY || props.y,
);
return {
@@ -102,8 +107,6 @@ export function transformToMatrix(props, transform) {
return pooledMatrix.toArray();
}
-const identity = [1, 0, 0, 1, 0, 0];
-
export default function extractTransform(props) {
if (Array.isArray(props)) {
return props;
diff --git a/lib/resolve.js b/lib/resolve.js
new file mode 100644
index 00000000..661fea27
--- /dev/null
+++ b/lib/resolve.js
@@ -0,0 +1,15 @@
+import { StyleSheet } from 'react-native-web';
+
+// Kept in separate file, to avoid name collision with Symbol element
+export function resolve(styleProp, cleanedProps) {
+ if (styleProp) {
+ return StyleSheet
+ ? [styleProp, cleanedProps]
+ : // Compatibility for arrays of styles in plain react web
+ styleProp[Symbol.iterator]
+ ? Object.assign({}, ...styleProp, cleanedProps)
+ : Object.assign({}, styleProp, cleanedProps);
+ } else {
+ return cleanedProps;
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index 5edf7825..ba3024c6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "react-native-svg",
- "version": "9.2.4",
+ "version": "9.4.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/package.json b/package.json
index d0db0fd7..3503a11b 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,5 @@
{
- "version": "9.2.4",
+ "version": "9.4.0",
"name": "react-native-svg",
"description": "SVG library for react-native",
"repository": {
@@ -7,7 +7,7 @@
"url": "https://github.com/react-native-community/react-native-svg"
},
"license": "MIT",
- "main": "./index.js",
+ "main": "./index",
"keywords": [
"react-component",
"react-native",
@@ -20,7 +20,7 @@
],
"scripts": {
"lint": "eslint ./",
- "format": "prettier index.js './{elements,lib}/*.js' './lib/extract/e*.js' --write",
+ "format": "prettier index.js index.web.js './{elements,lib}/*.js' './lib/extract/e*.js' --write",
"peg": "pegjs -o ./lib/extract/transform.js ./lib/extract/transform.peg"
},
"peerDependencies": {
diff --git a/screenShoots/pencil.png b/screenShoots/pencil.png
new file mode 100644
index 00000000..7730a1b5
Binary files /dev/null and b/screenShoots/pencil.png differ