Merge branch 'master' into NativeAnimation

# Conflicts:
#	android/src/main/java/com/horcrux/svg/RenderableShadowNode.java
#	android/src/main/java/com/horcrux/svg/SvgViewShadowNode.java
#	elements/Image.js
#	elements/Rect.js
#	elements/Use.js
#	lib/attributes.js
This commit is contained in:
Mikael Sand
2018-08-19 15:48:40 +03:00
78 changed files with 1444 additions and 919 deletions

View File

@@ -179,7 +179,7 @@
"no-underscore-dangle": 0, // disallow dangling underscores in identifiers "no-underscore-dangle": 0, // disallow dangling underscores in identifiers
//"no-wrap-func": 1, // disallow wrapping of non-IIFE statements in parens //"no-wrap-func": 1, // disallow wrapping of non-IIFE statements in parens
"no-mixed-spaces-and-tabs": 1, // disallow mixed spaces and tabs for indentation "no-mixed-spaces-and-tabs": 1, // disallow mixed spaces and tabs for indentation
"quotes": [1, "single", "avoid-escape"], // specify whether double or single quotes should be used "quotes": [1, "double", "avoid-escape"], // specify whether double or single quotes should be used
"quote-props": 0, // require quotes around object literal property names (off by default) "quote-props": 0, // require quotes around object literal property names (off by default)
"semi": 1, // require or disallow use of semicolons instead of ASI "semi": 1, // require or disallow use of semicolons instead of ASI
"sort-vars": 0, // sort variables within the same declaration block (off by default) "sort-vars": 0, // sort variables within the same declaration block (off by default)

View File

@@ -43,13 +43,27 @@
react-native link react-native-svg react-native link react-native-svg
``` ```
A bug in react-native currently links the tvOS library into the iOS project as well. * A bug in react-native currently links the tvOS library into the iOS project as well.
Until the fix is released: Until the fix is released:
https://github.com/facebook/react-native/issues/13783 https://github.com/facebook/react-native/issues/13783
https://github.com/facebook/react-native/commit/a63fd378a47173cc9f750e9980f18dc12dd7ea51 https://github.com/facebook/react-native/commit/a63fd378a47173cc9f750e9980f18dc12dd7ea51
Follow the instructions here: https://github.com/react-native-community/react-native-svg/issues/544
* If `cocoapods` is used and if error `RNSVGImage.m:12:9: 'React/RCTImageLoader.h' file not found` occurs:
Add the following entry in Podfile:
```ruby
pod 'React', :path => '../node_modules/react-native', :subspecs => [
[...]
'RCTImage', # !!!!!
]
```
and run `pod install` from `ios` folder
Follow the instructions here: https://github.com/react-native-community/react-native-svg/issues/544
#### Manual #### Manual

View File

@@ -1,6 +1,14 @@
def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}
buildscript { buildscript {
repositories { repositories {
jcenter() jcenter()
maven {
url 'https://maven.google.com/'
name 'Google'
}
} }
dependencies { dependencies {
@@ -12,16 +20,14 @@ buildscript {
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
android { android {
compileSdkVersion 23 compileSdkVersion safeExtGet('compileSdkVersion', 26)
//noinspection GradleDependency //noinspection GradleDependency
buildToolsVersion "23.0.1" buildToolsVersion safeExtGet('buildToolsVersion', '26.0.3')
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion safeExtGet('minSdkVersion', 16)
//noinspection OldTargetApi //noinspection OldTargetApi
targetSdkVersion 22 targetSdkVersion safeExtGet('targetSdkVersion', 26)
versionCode 1
versionName "1.0"
} }
lintOptions { lintOptions {
abortOnError false abortOnError false
@@ -35,6 +41,10 @@ repositories {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$projectDir/../../../node_modules/react-native/android" url "$projectDir/../../../node_modules/react-native/android"
} }
maven {
url 'https://maven.google.com/'
name 'Google'
}
} }
dependencies { dependencies {

View File

@@ -18,7 +18,9 @@ import android.graphics.Rect;
import android.graphics.RectF; import android.graphics.RectF;
import android.graphics.Shader; import android.graphics.Shader;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.ReactConstants;
class Brush { class Brush {
private BrushType mType = BrushType.LINEAR_GRADIENT; private BrushType mType = BrushType.LINEAR_GRADIENT;
@@ -108,6 +110,17 @@ class Brush {
float[] stops = new float[stopsCount]; float[] stops = new float[stopsCount];
parseGradientStops(mColors, stopsCount, stops, stopsColors, opacity); parseGradientStops(mColors, stopsCount, stops, stopsColors, opacity);
if (stops.length == 1) {
// Gradient with only one stop will make LinearGradient/RadialGradient
// throw. It may happen when source SVG contains only one stop or
// two stops at the same spot (see lib/extract/extractGradient.js).
// Although it's mistake SVGs like this can be produced by vector
// editors or other tools, so let's handle that gracefully.
stopsColors = new int[] { stopsColors[0], stopsColors[0] };
stops = new float[] { stops[0], stops[0] };
FLog.w(ReactConstants.TAG, "Gradient contains only on stop");
}
if (mType == BrushType.LINEAR_GRADIENT) { if (mType == BrushType.LINEAR_GRADIENT) {
double x1 = PropHelper.fromRelative(mPoints.getString(0), width, offsetX, scale, paint.getTextSize()); double x1 = PropHelper.fromRelative(mPoints.getString(0), width, offsetX, scale, paint.getTextSize());
double y1 = PropHelper.fromRelative(mPoints.getString(1), height, offsetY, scale, paint.getTextSize()); double y1 = PropHelper.fromRelative(mPoints.getString(1), height, offsetY, scale, paint.getTextSize());

View File

@@ -69,6 +69,7 @@ class GroupShadowNode extends RenderableShadowNode {
pushGlyphContext(); pushGlyphContext();
final SvgViewShadowNode svg = getSvgShadowNode(); final SvgViewShadowNode svg = getSvgShadowNode();
final GroupShadowNode self = this; final GroupShadowNode self = this;
final RectF groupRect = new RectF();
traverseChildren(new NodeRunnable() { traverseChildren(new NodeRunnable() {
public void run(ReactShadowNode lNode) { public void run(ReactShadowNode lNode) {
if (lNode instanceof VirtualNode) { if (lNode instanceof VirtualNode) {
@@ -79,6 +80,11 @@ class GroupShadowNode extends RenderableShadowNode {
int count = node.saveAndSetupCanvas(canvas); int count = node.saveAndSetupCanvas(canvas);
node.draw(canvas, paint, opacity * mOpacity); node.draw(canvas, paint, opacity * mOpacity);
RectF r = node.getClientRect();
if (r != null) {
groupRect.union(r);
}
node.restoreCanvas(canvas, count); node.restoreCanvas(canvas, count);
if (node instanceof RenderableShadowNode) { if (node instanceof RenderableShadowNode) {
@@ -98,6 +104,7 @@ class GroupShadowNode extends RenderableShadowNode {
} }
} }
}); });
this.setClientRect(groupRect);
popGlyphContext(); popGlyphContext();
} }

View File

@@ -32,6 +32,7 @@ import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.common.ReactConstants; import com.facebook.react.common.ReactConstants;
import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper; import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper;
import com.facebook.react.views.imagehelper.ImageSource;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -50,6 +51,7 @@ class ImageShadowNode extends RenderableShadowNode {
private String mW; private String mW;
private String mH; private String mH;
private Uri mUri; private Uri mUri;
private String uriString;
private int mImageWidth; private int mImageWidth;
private int mImageHeight; private int mImageHeight;
private String mAlign; private String mAlign;
@@ -90,7 +92,7 @@ class ImageShadowNode extends RenderableShadowNode {
@ReactProp(name = "src") @ReactProp(name = "src")
public void setSrc(@Nullable ReadableMap src) { public void setSrc(@Nullable ReadableMap src) {
if (src != null) { if (src != null) {
String uriString = src.getString("uri"); uriString = src.getString("uri");
if (uriString == null || uriString.isEmpty()) { if (uriString == null || uriString.isEmpty()) {
//TODO: give warning about this //TODO: give warning about this
@@ -147,7 +149,9 @@ class ImageShadowNode extends RenderableShadowNode {
@Override @Override
public void draw(final Canvas canvas, final Paint paint, final float opacity) { public void draw(final Canvas canvas, final Paint paint, final float opacity) {
if (!mLoading.get()) { if (!mLoading.get()) {
final ImageRequest request = ImageRequestBuilder.newBuilderWithSource(mUri).build(); final ImageSource imageSource = new ImageSource(getThemedContext(), uriString);
final ImageRequest request = ImageRequestBuilder.newBuilderWithSource(imageSource.getUri()).build();
if (Fresco.getImagePipeline().isInBitmapMemoryCache(request)) { if (Fresco.getImagePipeline().isInBitmapMemoryCache(request)) {
tryRender(request, canvas, paint, opacity * mOpacity); tryRender(request, canvas, paint, opacity * mOpacity);
} else { } else {
@@ -170,7 +174,10 @@ class ImageShadowNode extends RenderableShadowNode {
@Override @Override
public void onNewResultImpl(Bitmap bitmap) { public void onNewResultImpl(Bitmap bitmap) {
mLoading.set(false); mLoading.set(false);
bitmapTryRender(bitmap, canvas, paint, opacity * mOpacity); SvgViewShadowNode shadowNode = getSvgShadowNode();
if(shadowNode != null) {
shadowNode.markUpdated();
}
} }
@Override @Override

View File

@@ -562,10 +562,10 @@ class PropHelper {
arc -= Math.PI * 2; arc -= Math.PI * 2;
} }
int n = (int) Math.ceil(Math.abs(arc / (Math.PI / 2))); int n = (int) Math.ceil(Math.abs(round(arc / (Math.PI / 2), 4)));
float step = arc / n; float step = arc / n;
float k = (4 / 3) * (float) Math.tan(step / 4); float k = (float) ((4 / 3.0) * Math.tan(step / 4));
float x = (float) Math.cos(sa); float x = (float) Math.cos(sa);
float y = (float) Math.sin(sa); float y = (float) Math.sin(sa);
@@ -581,14 +581,14 @@ class PropHelper {
float cp2x = x + k * y; float cp2x = x + k * y;
float cp2y = y - k * x; float cp2y = y - k * x;
mPath.cubicTo( float c1x = (cx + xx * cp1x + yx * cp1y);
(cx + xx * cp1x + yx * cp1y) * mScale, float c1y = (cy + xy * cp1x + yy * cp1y);
(cy + xy * cp1x + yy * cp1y) * mScale, float c2x = (cx + xx * cp2x + yx * cp2y);
(cx + xx * cp2x + yx * cp2y) * mScale, float c2y = (cy + xy * cp2x + yy * cp2y);
(cy + xy * cp2x + yy * cp2y) * mScale, float ex = (cx + xx * x + yx * y);
(cx + xx * x + yx * y) * mScale, float ey = (cy + xy * x + yy * y);
(cy + xy * x + yy * y) * mScale
); mPath.cubicTo(c1x * mScale, c1y * mScale, c2x * mScale, c2y * mScale, ex * mScale, ey * mScale);
} }
} }
@@ -599,5 +599,10 @@ class PropHelper {
mPendDownSet = true; mPendDownSet = true;
} }
} }
private double round(double val, int sigDig) {
double multiplier = Math.pow(10, sigDig);
return Math.round(val * multiplier) / multiplier;
}
} }
} }

View File

@@ -11,6 +11,7 @@ package com.horcrux.svg;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.DashPathEffect; import android.graphics.DashPathEffect;
import android.graphics.Matrix;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Path; import android.graphics.Path;
import android.graphics.RectF; import android.graphics.RectF;
@@ -23,14 +24,16 @@ import com.facebook.react.bridge.JavaOnlyArray;
import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableType; import com.facebook.react.bridge.ReadableType;
import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableArray;
import com.facebook.react.uimanager.OnLayoutEvent;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.events.EventDispatcher;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
/** /**
@@ -68,10 +71,10 @@ abstract public class RenderableShadowNode extends VirtualNode {
public float mFillOpacity = 1; public float mFillOpacity = 1;
public Path.FillType mFillRule = Path.FillType.WINDING; public Path.FillType mFillRule = Path.FillType.WINDING;
private @Nullable ReadableArray mLastMergedList; private @Nullable ArrayList<String> mLastMergedList;
private @Nullable ArrayList<Object> mOriginProperties; private @Nullable ArrayList<Object> mOriginProperties;
protected @Nullable ReadableArray mPropList; protected @Nullable ArrayList<String> mPropList;
protected @Nullable WritableArray mAttributeList; protected @Nullable ArrayList<String> mAttributeList;
static final Pattern regex = Pattern.compile("[0-9.-]+"); static final Pattern regex = Pattern.compile("[0-9.-]+");
@@ -213,12 +216,11 @@ abstract public class RenderableShadowNode extends VirtualNode {
@ReactProp(name = "propList") @ReactProp(name = "propList")
public void setPropList(@Nullable ReadableArray propList) { public void setPropList(@Nullable ReadableArray propList) {
if (propList != null) { if (propList != null) {
WritableArray copy = Arguments.createArray(); mPropList = mAttributeList = new ArrayList<>();
for (int i = 0; i < propList.size(); i++) { for (int i = 0; i < propList.size(); i++) {
String fieldName = propertyNameToFieldName(propList.getString(i)); String fieldName = propertyNameToFieldName(propList.getString(i));
copy.pushString(fieldName); mPropList.add(fieldName);
} }
mPropList = mAttributeList = copy;
} }
markUpdated(); markUpdated();
@@ -235,6 +237,12 @@ abstract public class RenderableShadowNode extends VirtualNode {
} }
Path path = mPath; Path path = mPath;
RectF clientRect = new RectF();
path.computeBounds(clientRect, true);
Matrix svgToViewMatrix = new Matrix(canvas.getMatrix());
svgToViewMatrix.mapRect(clientRect);
this.setClientRect(clientRect);
clip(canvas, paint); clip(canvas, paint);
if (setupFillPaint(paint, opacity * mFillOpacity)) { if (setupFillPaint(paint, opacity * mFillOpacity)) {
@@ -327,10 +335,10 @@ abstract public class RenderableShadowNode extends VirtualNode {
int x = Math.round(dst[0]); int x = Math.round(dst[0]);
int y = Math.round(dst[1]); int y = Math.round(dst[1]);
if (mRegion == null) { if (mRegion == null && mPath != null) {
mRegion = getRegion(mPath); mRegion = getRegion(mPath);
} }
if (!mRegion.contains(x, y)) { if (mRegion == null || !mRegion.contains(x, y)) {
return -1; return -1;
} }
@@ -365,12 +373,12 @@ abstract public class RenderableShadowNode extends VirtualNode {
return region; return region;
} }
private WritableArray getAttributeList() { private ArrayList<String> getAttributeList() {
return mAttributeList; return mAttributeList;
} }
void mergeProperties(RenderableShadowNode target) { void mergeProperties(RenderableShadowNode target) {
WritableArray targetAttributeList = target.getAttributeList(); ArrayList<String> targetAttributeList = target.getAttributeList();
if (targetAttributeList == null || if (targetAttributeList == null ||
targetAttributeList.size() == 0) { targetAttributeList.size() == 0) {
@@ -378,17 +386,17 @@ abstract public class RenderableShadowNode extends VirtualNode {
} }
mOriginProperties = new ArrayList<>(); mOriginProperties = new ArrayList<>();
mAttributeList = clonePropList(); mAttributeList = mPropList == null ? new ArrayList<String>() : new ArrayList<>(mPropList);
for (int i = 0, size = targetAttributeList.size(); i < size; i++) { for (int i = 0, size = targetAttributeList.size(); i < size; i++) {
try { try {
String fieldName = targetAttributeList.getString(i); String fieldName = targetAttributeList.get(i);
Field field = getClass().getField(fieldName); Field field = getClass().getField(fieldName);
Object value = field.get(target); Object value = field.get(target);
mOriginProperties.add(field.get(this)); mOriginProperties.add(field.get(this));
if (!hasOwnProperty(fieldName)) { if (!hasOwnProperty(fieldName)) {
mAttributeList.pushString(fieldName); mAttributeList.add(fieldName);
field.set(this, value); field.set(this, value);
} }
} catch (Exception e) { } catch (Exception e) {
@@ -403,7 +411,7 @@ abstract public class RenderableShadowNode extends VirtualNode {
if (mLastMergedList != null && mOriginProperties != null) { if (mLastMergedList != null && mOriginProperties != null) {
try { try {
for (int i = mLastMergedList.size() - 1; i >= 0; i--) { for (int i = mLastMergedList.size() - 1; i >= 0; i--) {
Field field = getClass().getField(mLastMergedList.getString(i)); Field field = getClass().getField(mLastMergedList.get(i));
field.set(this, mOriginProperties.get(i)); field.set(this, mOriginProperties.get(i));
} }
} catch (Exception e) { } catch (Exception e) {
@@ -412,22 +420,10 @@ abstract public class RenderableShadowNode extends VirtualNode {
mLastMergedList = null; mLastMergedList = null;
mOriginProperties = null; mOriginProperties = null;
mAttributeList = clonePropList(); mAttributeList = mPropList;
} }
} }
private @Nonnull WritableArray clonePropList() {
WritableArray attributeList = Arguments.createArray();
if (mPropList != null) {
for (int i = 0; i < mPropList.size(); i++) {
attributeList.pushString(mPropList.getString(i));
}
}
return attributeList;
}
// convert propertyName something like fillOpacity to fieldName like mFillOpacity // convert propertyName something like fillOpacity to fieldName like mFillOpacity
private String propertyNameToFieldName(String fieldName) { private String propertyNameToFieldName(String fieldName) {
Pattern pattern = Pattern.compile("^(\\w)"); Pattern pattern = Pattern.compile("^(\\w)");
@@ -441,15 +437,6 @@ abstract public class RenderableShadowNode extends VirtualNode {
} }
private boolean hasOwnProperty(String propName) { private boolean hasOwnProperty(String propName) {
if (mAttributeList == null) { return mAttributeList != null && mAttributeList.contains(propName);
return false;
}
for (int i = mAttributeList.size() - 1; i >= 0; i--) {
if (mAttributeList.getString(i).equals(propName)) {
return true;
}
}
return false;
} }
} }

View File

@@ -78,6 +78,12 @@ public class SvgView extends ViewGroup {
SvgViewManager.setSvgView(this); SvgViewManager.setSvgView(this);
} }
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
SvgViewManager.dropSvgView(this);
}
public void setBitmap(Bitmap bitmap) { public void setBitmap(Bitmap bitmap) {
if (mBitmap != null) { if (mBitmap != null) {
mBitmap.recycle(); mBitmap.recycle();
@@ -100,11 +106,13 @@ public class SvgView extends ViewGroup {
@Override @Override
public boolean dispatchTouchEvent(MotionEvent ev) { public boolean dispatchTouchEvent(MotionEvent ev) {
mTargetTag = getShadowNode().hitTest(new Point((int) ev.getX(), (int) ev.getY())); SvgViewShadowNode node = getShadowNode();
if (node != null) {
if (mTargetTag != -1) { mTargetTag = node.hitTest(new Point((int) ev.getX(), (int) ev.getY()));
handleTouchEvent(ev); if (mTargetTag != -1) {
return true; handleTouchEvent(ev);
return true;
}
} }
return super.dispatchTouchEvent(ev); return super.dispatchTouchEvent(ev);

View File

@@ -51,6 +51,12 @@ class SvgViewManager extends ViewGroupManager<SvgView> {
mTagToSvgView.put(svg.getId(), svg); mTagToSvgView.put(svg.getId(), svg);
} }
static void dropSvgView(SvgView view) {
int tag = view.getId();
mTagToShadowNode.remove(tag);
mTagToSvgView.remove(tag);
}
@SuppressWarnings("unused") @SuppressWarnings("unused")
static @Nullable SvgView getSvgViewByTag(int tag) { static @Nullable SvgView getSvgViewByTag(int tag) {
return mTagToSvgView.get(tag); return mTagToSvgView.get(tag);
@@ -77,13 +83,6 @@ class SvgViewManager extends ViewGroupManager<SvgView> {
return node; return node;
} }
@Override
public void onDropViewInstance(SvgView view) {
int tag = view.getId();
mTagToShadowNode.remove(tag);
mTagToSvgView.remove(tag);
}
@Override @Override
protected SvgView createViewInstance(ThemedReactContext reactContext) { protected SvgView createViewInstance(ThemedReactContext reactContext) {
return new SvgView(reactContext); return new SvgView(reactContext);

View File

@@ -29,6 +29,7 @@ import com.facebook.react.uimanager.annotations.ReactProp;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Stack;
/** /**
* Shadow node for RNSVG virtual tree root - RNSVGSvgView * Shadow node for RNSVG virtual tree root - RNSVGSvgView
@@ -53,6 +54,7 @@ public class SvgViewShadowNode extends LayoutShadowNode {
private Matrix mInvViewBoxMatrix = new Matrix(); private Matrix mInvViewBoxMatrix = new Matrix();
private boolean mInvertible = true; private boolean mInvertible = true;
public SvgViewShadowNode() { public SvgViewShadowNode() {
mScale = DisplayMetricsHolder.getScreenDisplayMetrics().density; mScale = DisplayMetricsHolder.getScreenDisplayMetrics().density;
} }
@@ -155,12 +157,15 @@ public class SvgViewShadowNode extends LayoutShadowNode {
mCanvas = canvas; mCanvas = canvas;
if (mAlign != null) { if (mAlign != null) {
RectF vbRect = getViewBox(); RectF vbRect = getViewBox();
float width = getLayoutWidth(); float width;
float height = getLayoutHeight(); float height;
boolean nested = Float.isNaN(width) || Float.isNaN(height); boolean nested = getNativeParent() instanceof SvgViewShadowNode;
if (nested) { if (nested) {
width = (float) PropHelper.fromRelative(mbbWidth, canvas.getWidth(), 0f, mScale, 12); width = (float) PropHelper.fromRelative(mbbWidth, canvas.getWidth(), 0f, mScale, 12);
height = (float) PropHelper.fromRelative(mbbHeight, canvas.getHeight(), 0f, mScale, 12); height = (float) PropHelper.fromRelative(mbbHeight, canvas.getHeight(), 0f, mScale, 12);
} else {
width = getLayoutWidth();
height = getLayoutHeight();
} }
RectF eRect = new RectF(0,0, width, height); RectF eRect = new RectF(0,0, width, height);
if (nested) { if (nested) {
@@ -284,4 +289,5 @@ public class SvgViewShadowNode extends LayoutShadowNode {
runner.run(child); runner.run(child);
} }
} }
} }

View File

@@ -946,4 +946,39 @@ class TSpanShadowNode extends TextShadowNode {
parent = parent.getParent(); parent = parent.getParent();
} }
} }
@Override
public int hitTest(final float[] src) {
if (mContent == null) {
return super.hitTest(src);
}
if (mPath == null || !mInvertible) {
return -1;
}
float[] dst = new float[2];
mInvMatrix.mapPoints(dst, src);
int x = Math.round(dst[0]);
int y = Math.round(dst[1]);
if (mRegion == null && mPath != null) {
mRegion = getRegion(mPath);
}
if (mRegion == null || !mRegion.contains(x, y)) {
return -1;
}
Path clipPath = getClipPath();
if (clipPath != null) {
if (mClipRegionPath != clipPath) {
mClipRegionPath = clipPath;
mClipRegion = getRegion(clipPath);
}
if (!mClipRegion.contains(x, y)) {
return -1;
}
}
return getReactTag();
}
} }

View File

@@ -63,6 +63,8 @@ class UseShadowNode extends RenderableShadowNode {
template.draw(canvas, paint, opacity * mOpacity); template.draw(canvas, paint, opacity * mOpacity);
} }
this.setClientRect(template.getClientRect());
template.restoreCanvas(canvas, count); template.restoreCanvas(canvas, count);
if (template instanceof RenderableShadowNode) { if (template instanceof RenderableShadowNode) {
((RenderableShadowNode)template).resetProperties(); ((RenderableShadowNode)template).resetProperties();
@@ -73,6 +75,24 @@ class UseShadowNode extends RenderableShadowNode {
} }
} }
@Override
public int hitTest(float[] src) {
if (!mInvertible) {
return -1;
}
float[] dst = new float[2];
mInvMatrix.mapPoints(dst, src);
VirtualNode template = getSvgShadowNode().getDefinedTemplate(mHref);
int hitChild = template.hitTest(dst);
if (hitChild != -1) {
return (template.isResponsible() || hitChild != template.getReactTag()) ? hitChild : getReactTag();
}
return -1;
}
@Override @Override
protected Path getPath(Canvas canvas, Paint paint) { protected Path getPath(Canvas canvas, Paint paint) {
// todo: // todo:

View File

@@ -21,8 +21,11 @@ import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.ReactConstants; import com.facebook.react.common.ReactConstants;
import com.facebook.react.uimanager.DisplayMetricsHolder; import com.facebook.react.uimanager.DisplayMetricsHolder;
import com.facebook.react.uimanager.LayoutShadowNode; import com.facebook.react.uimanager.LayoutShadowNode;
import com.facebook.react.uimanager.OnLayoutEvent;
import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.ReactShadowNode;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.events.EventDispatcher;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -53,6 +56,7 @@ abstract class VirtualNode extends LayoutShadowNode {
Matrix mMatrix = new Matrix(); Matrix mMatrix = new Matrix();
Matrix mInvMatrix = new Matrix(); Matrix mInvMatrix = new Matrix();
boolean mInvertible = true; boolean mInvertible = true;
RectF mClientRect;
private int mClipRule; private int mClipRule;
private @Nullable String mClipPath; private @Nullable String mClipPath;
@@ -234,7 +238,7 @@ abstract class VirtualNode extends LayoutShadowNode {
} }
@Nullable Path getClipPath(Canvas canvas, Paint paint) { @Nullable Path getClipPath(Canvas canvas, Paint paint) {
if (mClipPath != null && mCachedClipPath == null) { if (mClipPath != null) {
VirtualNode node = getSvgShadowNode().getDefinedClipPath(mClipPath); VirtualNode node = getSvgShadowNode().getDefinedClipPath(mClipPath);
if (node != null) { if (node != null) {
@@ -350,4 +354,29 @@ abstract class VirtualNode extends LayoutShadowNode {
runner.run(child); runner.run(child);
} }
} }
void setClientRect(RectF rect) {
if (mClientRect != null && mClientRect.equals(rect)) {
return;
}
mClientRect = rect;
if (mClientRect == null) {
return;
}
EventDispatcher eventDispatcher = this.getThemedContext()
.getNativeModule(UIManagerModule.class)
.getEventDispatcher();
eventDispatcher.dispatchEvent(OnLayoutEvent.obtain(
this.getReactTag(),
(int) mClientRect.left,
(int) mClientRect.top,
(int) mClientRect.width(),
(int) mClientRect.height()
));
}
public RectF getClientRect() {
return mClientRect;
}
} }

View File

@@ -1,12 +1,12 @@
import React from 'react'; import React from "react";
import createReactNativeComponentClass from '../lib/createReactNativeComponentClass'; import { requireNativeComponent } from "react-native";
import Shape from './Shape'; import Shape from "./Shape";
import {CircleAttributes} from '../lib/attributes'; import { CircleAttributes } from "../lib/attributes";
import {pathProps, numberProp} from '../lib/props'; import { pathProps, numberProp } from "../lib/props";
import extractProps from '../lib/extract/extractProps'; import extractProps from "../lib/extract/extractProps";
export default class extends Shape { export default class extends Shape {
static displayName = 'Circle'; static displayName = "Circle";
static propTypes = { static propTypes = {
...pathProps, ...pathProps,
@@ -27,17 +27,20 @@ export default class extends Shape {
render() { render() {
let props = this.props; let props = this.props;
return <RNSVGCircle return (
ref={ele => {this.root = ele;}} <RNSVGCircle
{...extractProps(props, this)} ref={ele => {
cx={props.cx.toString()} this.root = ele;
cy={props.cy.toString()} }}
r={props.r.toString()} {...extractProps(props, this)}
/>; cx={props.cx.toString()}
cy={props.cy.toString()}
r={props.r.toString()}
/>
);
} }
} }
const RNSVGCircle = createReactNativeComponentClass('RNSVGCircle', () => ({ const RNSVGCircle = requireNativeComponent("RNSVGCircle", null, {
validAttributes: CircleAttributes, nativeOnly: CircleAttributes
uiViewClassName: 'RNSVGCircle' });
}));

View File

@@ -1,20 +1,23 @@
import React, {Component} from 'react'; import React, { Component } from "react";
import PropTypes from 'prop-types'; import PropTypes from "prop-types";
import createReactNativeComponentClass from '../lib/createReactNativeComponentClass'; import { requireNativeComponent } from "react-native";
import {ClipPathAttributes} from '../lib/attributes'; import { ClipPathAttributes } from "../lib/attributes";
export default class extends Component{ export default class extends Component {
static displayName = 'ClipPath'; static displayName = "ClipPath";
static propTypes = { static propTypes = {
id: PropTypes.string.isRequired id: PropTypes.string.isRequired
}; };
render() { render() {
return <RNSVGClipPath name={this.props.id}>{this.props.children}</RNSVGClipPath>; return (
<RNSVGClipPath name={this.props.id}>
{this.props.children}
</RNSVGClipPath>
);
} }
} }
const RNSVGClipPath = createReactNativeComponentClass('RNSVGClipPath', () => ({ const RNSVGClipPath = requireNativeComponent("RNSVGClipPath", null, {
validAttributes: ClipPathAttributes, nativeOnly: ClipPathAttributes
uiViewClassName: 'RNSVGClipPath' });
}));

View File

@@ -1,15 +1,14 @@
import React, { Component } from 'react'; import React, { Component } from "react";
import createReactNativeComponentClass from '../lib/createReactNativeComponentClass'; import { requireNativeComponent } from "react-native";
export default class extends Component { export default class extends Component {
static displayName = 'Defs'; static displayName = "Defs";
render() { render() {
return <RNSVGDefs>{this.props.children}</RNSVGDefs>; return <RNSVGDefs>{this.props.children}</RNSVGDefs>;
} }
} }
const RNSVGDefs = createReactNativeComponentClass('RNSVGDefs', () => ({ const RNSVGDefs = requireNativeComponent("RNSVGDefs", null, {
validAttributes: {}, nativeOnly: {}
uiViewClassName: 'RNSVGDefs' });
}));

View File

@@ -1,12 +1,12 @@
import React from 'react'; import React from "react";
import createReactNativeComponentClass from '../lib/createReactNativeComponentClass'; import { requireNativeComponent } from "react-native";
import Shape from './Shape'; import Shape from "./Shape";
import {pathProps, numberProp} from '../lib/props'; import { pathProps, numberProp } from "../lib/props";
import {EllipseAttributes} from '../lib/attributes'; import { EllipseAttributes } from "../lib/attributes";
import extractProps from '../lib/extract/extractProps'; import extractProps from "../lib/extract/extractProps";
export default class extends Shape{ export default class extends Shape {
static displayName = 'Ellipse'; static displayName = "Ellipse";
static propTypes = { static propTypes = {
...pathProps, ...pathProps,
@@ -30,18 +30,21 @@ export default class extends Shape{
render() { render() {
let props = this.props; let props = this.props;
return <RNSVGEllipse return (
ref={ele => {this.root = ele;}} <RNSVGEllipse
{...extractProps(props, this)} ref={ele => {
cx={props.cx.toString()} this.root = ele;
cy={props.cy.toString()} }}
rx={props.rx.toString()} {...extractProps(props, this)}
ry={props.ry.toString()} cx={props.cx.toString()}
/>; cy={props.cy.toString()}
rx={props.rx.toString()}
ry={props.ry.toString()}
/>
);
} }
} }
const RNSVGEllipse = createReactNativeComponentClass('RNSVGEllipse', () => ({ const RNSVGEllipse = requireNativeComponent("RNSVGEllipse", null, {
validAttributes: EllipseAttributes, nativeOnly: EllipseAttributes
uiViewClassName: 'RNSVGEllipse' });
}));

View File

@@ -1,17 +1,17 @@
import React from 'react'; import React from "react";
import createReactNativeComponentClass from '../lib/createReactNativeComponentClass'; import { requireNativeComponent } from "react-native";
import Shape from './Shape'; import Shape from "./Shape";
import {pathProps, fontProps} from '../lib/props'; import { pathProps, fontProps } from "../lib/props";
import {GroupAttributes} from '../lib/attributes'; import { GroupAttributes } from "../lib/attributes";
import extractProps from '../lib/extract/extractProps'; import extractProps from "../lib/extract/extractProps";
import {extractFont} from '../lib/extract/extractText'; import { extractFont } from "../lib/extract/extractText";
export default class extends Shape{ export default class extends Shape {
static displayName = 'G'; static displayName = "G";
static propTypes = { static propTypes = {
...pathProps, ...pathProps,
...fontProps, ...fontProps
}; };
setNativeProps = (...args) => { setNativeProps = (...args) => {
@@ -19,19 +19,22 @@ export default class extends Shape{
}; };
render() { render() {
let {props} = this; let { props } = this;
return <RNSVGGroup return (
{...extractProps(props, this)} <RNSVGGroup
font={extractFont(props)} {...extractProps(props, this)}
ref={ele => {this.root = ele;}} font={extractFont(props)}
> ref={ele => {
{props.children} this.root = ele;
</RNSVGGroup>; }}
>
{props.children}
</RNSVGGroup>
);
} }
} }
const RNSVGGroup = createReactNativeComponentClass('RNSVGGroup', () => ({ const RNSVGGroup = requireNativeComponent("RNSVGGroup", null, {
validAttributes: GroupAttributes, nativeOnly: GroupAttributes
uiViewClassName: 'RNSVGGroup' });
}));

View File

@@ -1,19 +1,17 @@
import React from 'react'; import React from "react";
import PropTypes from 'prop-types'; import PropTypes from "prop-types";
import { Image } from 'react-native'; import { Image, requireNativeComponent } from "react-native";
import createReactNativeComponentClass from '../lib/createReactNativeComponentClass'; import ImageSourcePropType from 'react-native/Libraries/Image/ImageSourcePropType';
import {ImageAttributes} from '../lib/attributes'; import { ImageAttributes } from "../lib/attributes";
import {numberProp, touchableProps, responderProps} from '../lib/props'; import { numberProp, touchableProps, responderProps } from "../lib/props";
import Shape from './Shape'; import Shape from "./Shape";
//noinspection JSUnresolvedVariable import { meetOrSliceTypes, alignEnum } from "../lib/extract/extractViewBox";
import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource'; import extractProps from "../lib/extract/extractProps";
import {meetOrSliceTypes, alignEnum} from '../lib/extract/extractViewBox';
import extractProps from '../lib/extract/extractProps';
const spacesRegExp = /\s+/; const spacesRegExp = /\s+/;
export default class extends Shape { export default class extends Shape {
static displayName = 'Image'; static displayName = "Image";
static propTypes = { static propTypes = {
...responderProps, ...responderProps,
...touchableProps, ...touchableProps,
@@ -21,7 +19,7 @@ export default class extends Shape {
y: numberProp, y: numberProp,
width: numberProp.isRequired, width: numberProp.isRequired,
height: numberProp.isRequired, height: numberProp.isRequired,
href: Image.propTypes.source, href: ImageSourcePropType,
preserveAspectRatio: PropTypes.string preserveAspectRatio: PropTypes.string
}; };
@@ -30,7 +28,7 @@ export default class extends Shape {
y: 0, y: 0,
width: 0, width: 0,
height: 0, height: 0,
preserveAspectRatio: 'xMidYMid meet' preserveAspectRatio: "xMidYMid meet"
}; };
setNativeProps = (...args) => { setNativeProps = (...args) => {
@@ -38,26 +36,29 @@ export default class extends Shape {
}; };
render() { render() {
let {props} = this; let { props } = this;
let modes = props.preserveAspectRatio.trim().split(spacesRegExp); let modes = props.preserveAspectRatio.trim().split(spacesRegExp);
let meetOrSlice = meetOrSliceTypes[modes[1]] || 0; let meetOrSlice = meetOrSliceTypes[modes[1]] || 0;
let align = alignEnum[modes[0]] || 'xMidYMid'; let align = alignEnum[modes[0]] || "xMidYMid";
return <RNSVGImage return (
ref={ele => {this.root = ele;}} <RNSVGImage
{...extractProps({...props, x: null, y: null}, this)} ref={ele => {
x={props.x.toString()} this.root = ele;
y={props.y.toString()} }}
imagewidth={props.width.toString()} {...extractProps({ ...props, x: null, y: null }, this)}
imageheight={props.height.toString()} x={props.x.toString()}
meetOrSlice={meetOrSlice} y={props.y.toString()}
align={align} imagewidth={props.width.toString()}
src={resolveAssetSource(props.href)} imageheight={props.height.toString()}
/>; meetOrSlice={meetOrSlice}
align={align}
src={Image.resolveAssetSource(props.href)}
/>
);
} }
} }
const RNSVGImage = createReactNativeComponentClass('RNSVGImage', () => ({ const RNSVGImage = requireNativeComponent("RNSVGImage", null, {
validAttributes: ImageAttributes, nativeOnly: ImageAttributes
uiViewClassName: 'RNSVGImage' });
}));

View File

@@ -1,12 +1,12 @@
import React from 'react'; import React from "react";
import createReactNativeComponentClass from '../lib/createReactNativeComponentClass'; import { requireNativeComponent } from "react-native";
import {LineAttributes} from '../lib/attributes'; import { LineAttributes } from "../lib/attributes";
import Shape from './Shape'; import Shape from "./Shape";
import {pathProps, numberProp} from '../lib/props'; import { pathProps, numberProp } from "../lib/props";
import extractProps from '../lib/extract/extractProps'; import extractProps from "../lib/extract/extractProps";
export default class extends Shape { export default class extends Shape {
static displayName = 'Line'; static displayName = "Line";
static propTypes = { static propTypes = {
...pathProps, ...pathProps,
@@ -29,18 +29,21 @@ export default class extends Shape {
render() { render() {
let props = this.props; let props = this.props;
return <RNSVGLine return (
ref={ele => {this.root = ele;}} <RNSVGLine
{...extractProps(props, this)} ref={ele => {
x1={props.x1.toString()} this.root = ele;
y1={props.y1.toString()} }}
x2={props.x2.toString()} {...extractProps(props, this)}
y2={props.y2.toString()} x1={props.x1.toString()}
/>; y1={props.y1.toString()}
x2={props.x2.toString()}
y2={props.y2.toString()}
/>
);
} }
} }
const RNSVGLine = createReactNativeComponentClass('RNSVGLine', () => ({ const RNSVGLine = requireNativeComponent("RNSVGLine", null, {
validAttributes: LineAttributes, nativeOnly: LineAttributes
uiViewClassName: 'RNSVGLine' });
}));

View File

@@ -1,45 +1,46 @@
import React, {Component} from 'react'; import React, { Component } from "react";
import PropTypes from 'prop-types'; import PropTypes from "prop-types";
import {numberProp} from '../lib/props'; import { numberProp } from "../lib/props";
import extractGradient from '../lib/extract/extractGradient'; import extractGradient from "../lib/extract/extractGradient";
import createReactNativeComponentClass from '../lib/createReactNativeComponentClass'; import { requireNativeComponent } from "react-native";
import {LinearGradientAttributes} from '../lib/attributes'; import { LinearGradientAttributes } from "../lib/attributes";
export default class extends Component{ export default class extends Component {
static displayName = 'LinearGradient'; static displayName = "LinearGradient";
static propTypes = { static propTypes = {
x1: numberProp.isRequired, x1: numberProp.isRequired,
x2: numberProp.isRequired, x2: numberProp.isRequired,
y1: numberProp.isRequired, y1: numberProp.isRequired,
y2: numberProp.isRequired, y2: numberProp.isRequired,
gradientUnits: PropTypes.oneOf(['objectBoundingBox', 'userSpaceOnUse']), gradientUnits: PropTypes.oneOf(["objectBoundingBox", "userSpaceOnUse"]),
id: PropTypes.string.isRequired id: PropTypes.string.isRequired
}; };
static defaultProps = { static defaultProps = {
x1: '0%', x1: "0%",
y1: '0%', y1: "0%",
x2: '100%', x2: "100%",
y2: '0%' y2: "0%"
}; };
render() { render() {
let {props} = this; let { props } = this;
return <RNSVGLinearGradient return (
x1={props.x1.toString()} <RNSVGLinearGradient
y1={props.y1.toString()} x1={props.x1.toString()}
x2={props.x2.toString()} y1={props.y1.toString()}
y2={props.y2.toString()} x2={props.x2.toString()}
{...extractGradient(this.props)} y2={props.y2.toString()}
/>; {...extractGradient(this.props)}
/>
);
} }
} }
const RNSVGLinearGradient = createReactNativeComponentClass( const RNSVGLinearGradient = requireNativeComponent(
'RNSVGLinearGradient', "RNSVGLinearGradient",
() => ({ null,
validAttributes: LinearGradientAttributes, {
uiViewClassName: 'RNSVGLinearGradient' nativeOnly: LinearGradientAttributes
}) }
); );

View File

@@ -1,13 +1,13 @@
import React from 'react'; import React from "react";
import PropTypes from 'prop-types'; import PropTypes from "prop-types";
import createReactNativeComponentClass from '../lib/createReactNativeComponentClass'; import { requireNativeComponent } from "react-native";
import {PathAttributes} from '../lib/attributes'; import { PathAttributes } from "../lib/attributes";
import Shape from './Shape'; import Shape from "./Shape";
import {pathProps} from '../lib/props'; import { pathProps } from "../lib/props";
import extractProps from '../lib/extract/extractProps'; import extractProps from "../lib/extract/extractProps";
export default class extends Shape { export default class extends Shape {
static displayName = 'Path'; static displayName = "Path";
static propTypes = { static propTypes = {
...pathProps, ...pathProps,
@@ -23,7 +23,9 @@ export default class extends Shape {
return ( return (
<RNSVGPath <RNSVGPath
ref={ele => {this.root = ele;}} ref={ele => {
this.root = ele;
}}
{...extractProps(props, this)} {...extractProps(props, this)}
d={props.d} d={props.d}
/> />
@@ -31,7 +33,6 @@ export default class extends Shape {
} }
} }
const RNSVGPath = createReactNativeComponentClass('RNSVGPath', () => ({ const RNSVGPath = requireNativeComponent("RNSVGPath", null, {
validAttributes: PathAttributes, nativeOnly: PathAttributes
uiViewClassName: 'RNSVGPath' });
}));

View File

@@ -1,20 +1,22 @@
import {Component} from 'react'; import { Component } from "react";
import PropTypes from 'prop-types'; import PropTypes from "prop-types";
import {numberProp} from '../lib/props'; import { numberProp } from "../lib/props";
export default class extends Component{ export default class extends Component {
static displayName = 'Pattern'; static displayName = "Pattern";
static propTypes = { static propTypes = {
x1: numberProp, x1: numberProp,
x2: numberProp, x2: numberProp,
y1: numberProp, y1: numberProp,
y2: numberProp, y2: numberProp,
patternTransform: PropTypes.string, patternTransform: PropTypes.string,
patternUnits: PropTypes.oneOf(['userSpaceOnUse', 'objectBoundingBox']), patternUnits: PropTypes.oneOf(["userSpaceOnUse", "objectBoundingBox"]),
patternContentUnits: PropTypes.oneOf(['userSpaceOnUse', 'objectBoundingBox']) patternContentUnits: PropTypes.oneOf([
"userSpaceOnUse",
"objectBoundingBox"
])
}; };
render() { render() {
return null; return null;
} }

View File

@@ -1,35 +1,47 @@
import React, {Component} from 'react'; import React, { Component } from "react";
import PropTypes from 'prop-types'; import PropTypes from "prop-types";
import Path from './Path'; import Path from "./Path";
import {pathProps} from '../lib/props'; import { pathProps } from "../lib/props";
import extractPolyPoints from '../lib/extract/extractPolyPoints'; import extractPolyPoints from "../lib/extract/extractPolyPoints";
export default class extends Component{ export default class extends Component {
static displayName = 'Polygon'; static displayName = "Polygon";
static propTypes = { static propTypes = {
...pathProps, ...pathProps,
points: PropTypes.oneOfType([PropTypes.string, PropTypes.array]).isRequired points: PropTypes.oneOfType([PropTypes.string, PropTypes.array])
.isRequired
}; };
static defaultProps = { static defaultProps = {
points: '' points: ""
}; };
setNativeProps = (...args) => { setNativeProps = (...args) => {
//noinspection JSUnresolvedFunction //noinspection JSUnresolvedFunction
this.root.getNativeElement().setNativeProps(...args); var points = [...args][0].points;
if (points) {
if (Array.isArray(points)) {
points = points.join(",");
}
[...args][0].d = `M${extractPolyPoints(points)}`
}
this.root.setNativeProps(...args);
}; };
render() { render() {
let points = this.props.points; let points = this.props.points;
if (Array.isArray(points)) { if (Array.isArray(points)) {
points = points.join(','); points = points.join(",");
} }
return <Path return (
ref={ele => {this.root = ele;}} <Path
{...this.props} ref={ele => {
d={`M${extractPolyPoints(points)}z`} this.root = ele;
/>; }}
{...this.props}
d={`M${extractPolyPoints(points)}z`}
/>
);
} }
} }

View File

@@ -1,35 +1,47 @@
import React, {Component} from 'react'; import React, { Component } from "react";
import PropTypes from 'prop-types'; import PropTypes from "prop-types";
import Path from './Path'; import Path from "./Path";
import {pathProps} from '../lib/props'; import { pathProps } from "../lib/props";
import extractPolyPoints from '../lib/extract/extractPolyPoints'; import extractPolyPoints from "../lib/extract/extractPolyPoints";
export default class extends Component{ export default class extends Component {
static displayName = 'Polyline'; static displayName = "Polyline";
static propTypes = { static propTypes = {
...pathProps, ...pathProps,
points: PropTypes.oneOfType([PropTypes.string, PropTypes.array]).isRequired points: PropTypes.oneOfType([PropTypes.string, PropTypes.array])
.isRequired
}; };
static defaultProps = { static defaultProps = {
points: '' points: ""
}; };
setNativeProps = (...args) => { setNativeProps = (...args) => {
//noinspection JSUnresolvedFunction //noinspection JSUnresolvedFunction
this.root.getNativeElement().setNativeProps(...args); var points = [...args][0].points;
if (points) {
if (Array.isArray(points)) {
points = points.join(",");
}
[...args][0].d = `M${extractPolyPoints(points)}`
}
this.root.setNativeProps(...args);
}; };
render() { render() {
let points = this.props.points; let points = this.props.points;
if (Array.isArray(points)) { if (Array.isArray(points)) {
points = points.join(','); points = points.join(",");
} }
return <Path return (
ref={ele => {this.root = ele;}} <Path
{...this.props} ref={ele => {
d={`M${extractPolyPoints(points)}`} this.root = ele;
/>; }}
{...this.props}
d={`M${extractPolyPoints(points)}`}
/>
);
} }
} }

View File

@@ -1,12 +1,12 @@
import React, {Component} from 'react'; import React, { Component } from "react";
import PropTypes from 'prop-types'; import PropTypes from "prop-types";
import {numberProp} from '../lib/props'; import { numberProp } from "../lib/props";
import extractGradient from '../lib/extract/extractGradient'; import extractGradient from "../lib/extract/extractGradient";
import createReactNativeComponentClass from '../lib/createReactNativeComponentClass'; import { requireNativeComponent } from "react-native";
import {RadialGradientAttributes} from '../lib/attributes'; import { RadialGradientAttributes } from "../lib/attributes";
export default class extends Component{ export default class extends Component {
static displayName = 'RadialGradient'; static displayName = "RadialGradient";
static propTypes = { static propTypes = {
fx: numberProp.isRequired, fx: numberProp.isRequired,
fy: numberProp.isRequired, fy: numberProp.isRequired,
@@ -15,37 +15,38 @@ export default class extends Component{
cx: numberProp.isRequired, cx: numberProp.isRequired,
cy: numberProp.isRequired, cy: numberProp.isRequired,
r: numberProp, r: numberProp,
gradientUnits: PropTypes.oneOf(['objectBoundingBox', 'userSpaceOnUse']), gradientUnits: PropTypes.oneOf(["objectBoundingBox", "userSpaceOnUse"]),
id: PropTypes.string.isRequired id: PropTypes.string.isRequired
}; };
static defaultProps = { static defaultProps = {
fx: '50%', fx: "50%",
fy: '50%', fy: "50%",
cx: '50%', cx: "50%",
cy: '50%', cy: "50%",
r: '50%' r: "50%"
}; };
render() { render() {
let {props} = this; let { props } = this;
return <RNSVGRadialGradient return (
fx={props.fx.toString()} <RNSVGRadialGradient
fy={props.fy.toString()} fx={props.fx.toString()}
rx={(props.rx || props.r).toString()} fy={props.fy.toString()}
ry={(props.ry || props.r).toString()} rx={(props.rx || props.r).toString()}
cx={props.cx.toString()} ry={(props.ry || props.r).toString()}
cy={props.cy.toString()} cx={props.cx.toString()}
{...extractGradient(this.props)} cy={props.cy.toString()}
/>; {...extractGradient(this.props)}
/>
);
} }
} }
const RNSVGRadialGradient = createReactNativeComponentClass( const RNSVGRadialGradient = requireNativeComponent(
'RNSVGRadialGradient', "RNSVGRadialGradient",
() => ({ null,
validAttributes: RadialGradientAttributes, {
uiViewClassName: 'RNSVGRadialGradient' nativeOnly: RadialGradientAttributes
}) }
); );

View File

@@ -1,13 +1,13 @@
import React from 'react'; 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 "./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 '../lib/createReactNativeComponentClass'; import { requireNativeComponent } from "react-native";
import {pathProps, numberProp} from '../lib/props'; import { pathProps, numberProp } from "../lib/props";
import {RectAttributes} from '../lib/attributes'; import { RectAttributes } from "../lib/attributes";
import extractProps from '../lib/extract/extractProps'; import extractProps from "../lib/extract/extractProps";
import Shape from './Shape'; import Shape from "./Shape";
export default class extends Shape { export default class extends Shape {
static displayName = 'Rect'; static displayName = "Rect";
static propTypes = { static propTypes = {
...pathProps, ...pathProps,
@@ -35,24 +35,30 @@ export default class extends Shape {
render() { render() {
let props = this.props; let props = this.props;
return <RNSVGRect return (
ref={ele => {this.root = ele;}} <RNSVGRect
{...extractProps({ ref={ele => {
...props, this.root = ele;
x: null, }}
y: null {...extractProps(
}, this)} {
x={props.x.toString()} ...props,
y={props.y.toString()} x: null,
rectwidth={props.width.toString()} y: null
rectheight={props.height.toString()} },
rx={props.rx.toString()} this
ry={props.ry.toString()} )}
/>; x={props.x.toString()}
y={props.y.toString()}
rectwidth={props.width.toString()}
rectheight={props.height.toString()}
rx={props.rx.toString()}
ry={props.ry.toString()}
/>
);
} }
} }
const RNSVGRect = createReactNativeComponentClass('RNSVGRect', () => ({ const RNSVGRect = requireNativeComponent("RNSVGRect", null, {
validAttributes: RectAttributes, nativeOnly: RectAttributes
uiViewClassName: 'RNSVGRect' });
}));

View File

@@ -1,6 +1,6 @@
import {Component} from 'react'; import { Component } from "react";
import SvgTouchableMixin from '../lib/SvgTouchableMixin'; import SvgTouchableMixin from "../lib/SvgTouchableMixin";
import _ from 'lodash'; import _ from "lodash";
class Shape extends Component { class Shape extends Component {
constructor() { constructor() {

View File

@@ -1,16 +1,16 @@
import {Component} from 'react'; import { Component } from "react";
import PropTypes from 'prop-types'; import PropTypes from "prop-types";
import {numberProp} from '../lib/props'; import { numberProp } from "../lib/props";
export default class extends Component{ export default class extends Component {
static displayName = 'Stop'; static displayName = "Stop";
static propTypes = { static propTypes = {
stopColor: PropTypes.string, stopColor: PropTypes.string,
stopOpacity: numberProp stopOpacity: numberProp
}; };
static defaultProps = { static defaultProps = {
stopColor: '#000', stopColor: "#000",
stopOpacity: 1 stopOpacity: 1
}; };

View File

@@ -1,17 +1,16 @@
//noinspection JSUnresolvedVariable //noinspection JSUnresolvedVariable
import React, { import React, { Component } from "react";
Component import PropTypes from "prop-types";
} from 'react';
import PropTypes from 'prop-types';
import { import {
ViewPropTypes, ViewPropTypes,
requireNativeComponent, requireNativeComponent,
StyleSheet, StyleSheet,
findNodeHandle, findNodeHandle,
NativeModules NativeModules
} from 'react-native'; } from "react-native";
import extractViewBox from '../lib/extract/extractViewBox'; import extractViewBox from "../lib/extract/extractViewBox";
import {ViewBoxAttributes} from '../lib/attributes'; import { ViewBoxAttributes } from "../lib/attributes";
import { numberProp } from "../lib/props";
/** @namespace NativeModules.RNSVGSvgViewManager */ /** @namespace NativeModules.RNSVGSvgViewManager */
const RNSVGSvgViewManager = NativeModules.RNSVGSvgViewManager; const RNSVGSvgViewManager = NativeModules.RNSVGSvgViewManager;
@@ -21,32 +20,29 @@ let id = 0;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
svg: { svg: {
backgroundColor: 'transparent' backgroundColor: "transparent"
} }
}); });
class Svg extends Component{ class Svg extends Component {
static displayName = 'Svg'; static displayName = "Svg";
static propTypes = { static propTypes = {
...ViewPropTypes, ...ViewPropTypes,
opacity: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), opacity: numberProp,
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), width: numberProp,
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), height: numberProp,
// more detail https://svgwg.org/svg2-draft/coords.html#ViewBoxAttribute // more detail https://svgwg.org/svg2-draft/coords.html#ViewBoxAttribute
viewBox: PropTypes.string, viewBox: PropTypes.string,
preserveAspectRatio: PropTypes.string preserveAspectRatio: PropTypes.string
}; };
static defaultProps = { static defaultProps = {
preserveAspectRatio: 'xMidYMid meet' preserveAspectRatio: "xMidYMid meet"
}; };
constructor() { constructor() {
super(...arguments); super(...arguments);
id++; this.id = ++id;
this.id = id;
//noinspection JSUnusedGlobalSymbols
this.onDataURLCallbacks = [];
} }
measureInWindow = (...args) => { measureInWindow = (...args) => {
this.root.measureInWindow(...args); this.root.measureInWindow(...args);
@@ -64,18 +60,27 @@ class Svg extends Component{
this.root.setNativeProps(...args); this.root.setNativeProps(...args);
}; };
toDataURL = (callback) => { toDataURL = callback => {
callback && RNSVGSvgViewManager.toDataURL(findNodeHandle(this.root), callback); callback &&
RNSVGSvgViewManager.toDataURL(findNodeHandle(this.root), callback);
}; };
render() { render() {
const {opacity, width, height, viewBox, preserveAspectRatio, style, ...props} = this.props; const {
opacity,
width,
height,
viewBox,
preserveAspectRatio,
style,
...props
} = this.props;
let dimensions; let dimensions;
if (width && height) { if (width && height) {
dimensions = { dimensions = {
width: width[width.length - 1] === '%' ? width : +width, width: width[width.length - 1] === "%" ? width : +width,
height: height[height.length - 1] === '%' ? height : +height, height: height[height.length - 1] === "%" ? height : +height,
flex: 0 flex: 0
}; };
} }
@@ -83,29 +88,33 @@ class Svg extends Component{
const w = `${width}`; const w = `${width}`;
const h = `${height}`; const h = `${height}`;
return <NativeSvgView return (
{...props} <NativeSvgView
bbWidth={w} {...props}
bbHeight={h} bbWidth={w}
{...extractViewBox({ viewBox, preserveAspectRatio })} bbHeight={h}
ref={ele => {this.root = ele;}} {...extractViewBox({ viewBox, preserveAspectRatio })}
style={[ ref={ele => {
styles.svg, this.root = ele;
style, }}
!isNaN(+opacity) && { style={[
opacity: +opacity styles.svg,
}, style,
dimensions !isNaN(+opacity) && {
]} opacity: +opacity
/>; },
dimensions
]}
/>
);
} }
} }
const NativeSvgView = requireNativeComponent('RNSVGSvgView', null, { const NativeSvgView = requireNativeComponent("RNSVGSvgView", null, {
nativeOnly: { nativeOnly: {
...ViewBoxAttributes, ...ViewBoxAttributes,
width: true, width: true,
height: true, height: true
} }
}); });

View File

@@ -1,29 +1,27 @@
import React, {Component} from 'react'; import React, { Component } from "react";
import PropTypes from 'prop-types'; import PropTypes from "prop-types";
import extractViewBox from '../lib/extract/extractViewBox'; import extractViewBox from "../lib/extract/extractViewBox";
import createReactNativeComponentClass from '../lib/createReactNativeComponentClass'; import { requireNativeComponent } from "react-native";
import {SymbolAttributes} from '../lib/attributes'; import { SymbolAttributes } from "../lib/attributes";
export default class extends Component{ export default class extends Component {
static displayName = 'Symbol'; static displayName = "Symbol";
static propTypes = { static propTypes = {
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
viewBox: PropTypes.string, viewBox: PropTypes.string,
preserveAspectRatio: PropTypes.string preserveAspectRatio: PropTypes.string
}; };
render() { render() {
let {props} = this; let { props } = this;
return <RNSVGSymbol return (
name={props.id} <RNSVGSymbol name={props.id} {...extractViewBox(props)}>
{...extractViewBox(props)} {props.children}
> </RNSVGSymbol>
{props.children} );
</RNSVGSymbol>;
} }
} }
const RNSVGSymbol = createReactNativeComponentClass('RNSVGSymbol', () => ({ const RNSVGSymbol = requireNativeComponent("RNSVGSymbol", null, {
validAttributes: SymbolAttributes, nativeOnly: SymbolAttributes
uiViewClassName: 'RNSVGSymbol' });
}));

View File

@@ -1,15 +1,15 @@
import React from 'react'; import React from "react";
import PropTypes from 'prop-types'; import PropTypes from "prop-types";
import createReactNativeComponentClass from '../lib/createReactNativeComponentClass'; import { requireNativeComponent } from "react-native";
import extractText from '../lib/extract/extractText'; import extractText from "../lib/extract/extractText";
import {textProps} from '../lib/props'; import { textProps } from "../lib/props";
import {TSpanAttibutes} from '../lib/attributes'; import { TSpanAttibutes } from "../lib/attributes";
import extractProps from '../lib/extract/extractProps'; import extractProps from "../lib/extract/extractProps";
import Shape from './Shape'; import Shape from "./Shape";
// TSpan elements are shadow components // TSpan elements are shadow components
export default class extends Shape { export default class extends Shape {
static displayName = 'TSpan'; static displayName = "TSpan";
static propTypes = textProps; static propTypes = textProps;
@@ -38,19 +38,25 @@ export default class extends Shape {
render() { render() {
let props = this.props; let props = this.props;
return <RNSVGTSpan return (
ref={ele => {this.root = ele;}} <RNSVGTSpan
{...extractProps({ ref={ele => {
...props, this.root = ele;
x: null, }}
y: null {...extractProps(
}, this)} {
{...extractText(props)} ...props,
/>; x: null,
y: null
},
this
)}
{...extractText(props)}
/>
);
} }
} }
const RNSVGTSpan = createReactNativeComponentClass('RNSVGTSpan', () => ({ const RNSVGTSpan = requireNativeComponent("RNSVGTSpan", null, {
validAttributes: TSpanAttibutes, nativeOnly: TSpanAttibutes
uiViewClassName: 'RNSVGTSpan' });
}));

View File

@@ -1,14 +1,14 @@
import React from 'react'; import React from "react";
import PropTypes from 'prop-types'; import PropTypes from "prop-types";
import createReactNativeComponentClass from '../lib/createReactNativeComponentClass'; import { requireNativeComponent } from "react-native";
import extractText from '../lib/extract/extractText'; import extractText from "../lib/extract/extractText";
import {textProps} from '../lib/props'; import { textProps } from "../lib/props";
import {TextAttributes} from '../lib/attributes'; import { TextAttributes } from "../lib/attributes";
import extractProps from '../lib/extract/extractProps'; import extractProps from "../lib/extract/extractProps";
import Shape from './Shape'; import Shape from "./Shape";
export default class extends Shape { export default class extends Shape {
static displayName = 'Text'; static displayName = "Text";
static propTypes = textProps; static propTypes = textProps;
@@ -38,19 +38,25 @@ export default class extends Shape {
render() { render() {
const props = this.props; const props = this.props;
return <RNSVGText return (
ref={ele => {this.root = ele;}} <RNSVGText
{...extractProps({ ref={ele => {
...props, this.root = ele;
x: null, }}
y: null {...extractProps(
}, this)} {
{...extractText(props, true)} ...props,
/>; x: null,
y: null
},
this
)}
{...extractText(props, true)}
/>
);
} }
} }
const RNSVGText = createReactNativeComponentClass('RNSVGText', () => ({ const RNSVGText = requireNativeComponent("RNSVGText", null, {
validAttributes: TextAttributes, nativeOnly: TextAttributes
uiViewClassName: 'RNSVGText' });
}));

View File

@@ -1,49 +1,77 @@
import React from 'react'; import React from "react";
import createReactNativeComponentClass from '../lib/createReactNativeComponentClass'; import { requireNativeComponent } from "react-native";
import {TextPathAttributes} from '../lib/attributes'; import { TextPathAttributes } from "../lib/attributes";
import extractText from '../lib/extract/extractText'; import extractText from "../lib/extract/extractText";
import Shape from './Shape'; import Shape from "./Shape";
import {textPathProps} from '../lib/props'; import { textPathProps } from "../lib/props";
import extractProps from '../lib/extract/extractProps'; import extractProps from "../lib/extract/extractProps";
import TSpan from './TSpan'; import TSpan from "./TSpan";
const idExpReg = /^#(.+)$/; const idExpReg = /^#(.+)$/;
export default class extends Shape { export default class extends Shape {
static displayName = 'Span'; static displayName = "TextPath";
static propTypes = textPathProps; static propTypes = textPathProps;
render() { render() {
let {children, href, startOffset, method, spacing, side, alignmentBaseline, midLine, ...props} = this.props; let {
children,
href,
startOffset,
method,
spacing,
side,
alignmentBaseline,
midLine,
...props
} = this.props;
if (href) { if (href) {
let matched = href.match(idExpReg); let matched = href.match(idExpReg);
if (matched) { if (matched) {
href = matched[1]; href = matched[1];
startOffset = `${startOffset || 0}`; startOffset = `${startOffset || 0}`;
return <RNSVGTextPath return (
{...{href, startOffset, method, spacing, side, alignmentBaseline, midLine}} <RNSVGTextPath
{...extractProps({ {...{
...props, href,
x: null, startOffset,
y: null, method,
}, this)} spacing,
{...extractText({ side,
children, alignmentBaseline,
}, true)} midLine
/>; }}
{...extractProps(
{
...props,
x: null,
y: null
},
this
)}
{...extractText(
{
children
},
true
)}
/>
);
} }
} }
console.warn('Invalid `href` prop for `TextPath` element, expected a href like `"#id"`, but got: "' + props.href + '"'); console.warn(
'Invalid `href` prop for `TextPath` element, expected a href like `"#id"`, but got: "' +
props.href +
'"'
);
return <TSpan>{children}</TSpan>; return <TSpan>{children}</TSpan>;
} }
} }
const RNSVGTextPath = createReactNativeComponentClass('RNSVGTextPath', () => ({ const RNSVGTextPath = requireNativeComponent("RNSVGTextPath", null, {
validAttributes: TextPathAttributes, nativeOnly: TextPathAttributes
uiViewClassName: 'RNSVGTextPath' });
}));

View File

@@ -1,14 +1,14 @@
import React from 'react'; import React from "react";
import PropTypes from 'prop-types'; import PropTypes from "prop-types";
import createReactNativeComponentClass from '../lib/createReactNativeComponentClass'; import { requireNativeComponent } from "react-native";
import extractProps from '../lib/extract/extractProps'; import extractProps from "../lib/extract/extractProps";
import {pathProps, numberProp} from '../lib/props'; import { pathProps, numberProp } from "../lib/props";
import {UseAttributes} from '../lib/attributes'; import { UseAttributes } from "../lib/attributes";
import Shape from './Shape'; import Shape from "./Shape";
const idExpReg = /^#(.+)$/; const idExpReg = /^#(.+)$/;
export default class extends Shape { export default class extends Shape {
static displayName = 'Use'; static displayName = "Use";
static propTypes = { static propTypes = {
href: PropTypes.string.isRequired, href: PropTypes.string.isRequired,
@@ -17,12 +17,18 @@ export default class extends Shape {
...pathProps ...pathProps
}; };
static defaultProps = {
width: 0,
height: 0
};
setNativeProps = (...args) => { setNativeProps = (...args) => {
this.root.setNativeProps(...args); this.root.setNativeProps(...args);
}; };
render() { render() {
const {props} = this; const { props } = this;
const { children, width, height } = props;
// match "url(#pattern)" // match "url(#pattern)"
const matched = props.href.match(idExpReg); const matched = props.href.match(idExpReg);
let href; let href;
@@ -32,20 +38,29 @@ export default class extends Shape {
} }
if (!href) { if (!href) {
console.warn('Invalid `href` prop for `Use` element, expected a href like `"#id"`, but got: "' + props.href + '"'); console.warn(
'Invalid `href` prop for `Use` element, expected a href like `"#id"`, but got: "' +
props.href +
'"'
);
} }
return <RNSVGUse return (
ref={ele => {this.root = ele;}} <RNSVGUse
{...extractProps(props, this)} ref={ele => {
href={href} this.root = ele;
usewidth={props.width} }}
useheight={props.height} {...extractProps(props, this)}
>{props.children}</RNSVGUse>; href={href}
usewidth={width !== undefined ? width.toString() : ""}
useheight={height !== undefined ? height.toString() : ""}
>
{children}
</RNSVGUse>
);
} }
} }
const RNSVGUse = createReactNativeComponentClass('RNSVGUse', () => ({ const RNSVGUse = requireNativeComponent("RNSVGUse", null, {
validAttributes: UseAttributes, nativeOnly: UseAttributes
uiViewClassName: 'RNSVGUse' });
}));

1
index.d.ts vendored
View File

@@ -267,7 +267,6 @@ export interface StopProps {
export const Stop: React.ComponentClass<StopProps>; export const Stop: React.ComponentClass<StopProps>;
export interface SvgProps extends ReactNative.ViewProperties { export interface SvgProps extends ReactNative.ViewProperties {
opacity?: NumberProp,
width: NumberProp, width: NumberProp,
height: NumberProp, height: NumberProp,
viewBox?: string, viewBox?: string,

View File

@@ -1,23 +1,23 @@
import Rect from './elements/Rect'; import Rect from "./elements/Rect";
import Circle from './elements/Circle'; import Circle from "./elements/Circle";
import Ellipse from './elements/Ellipse'; import Ellipse from "./elements/Ellipse";
import Polygon from './elements/Polygon'; import Polygon from "./elements/Polygon";
import Polyline from './elements/Polyline'; import Polyline from "./elements/Polyline";
import Line from './elements/Line'; import Line from "./elements/Line";
import Svg from './elements/Svg'; import Svg from "./elements/Svg";
import Path from './elements/Path'; import Path from "./elements/Path";
import G from './elements/G'; import G from "./elements/G";
import Text from './elements/Text'; import Text from "./elements/Text";
import TSpan from './elements/TSpan'; import TSpan from "./elements/TSpan";
import TextPath from './elements/TextPath'; import TextPath from "./elements/TextPath";
import Use from './elements/Use'; import Use from "./elements/Use";
import Image from './elements/Image'; import Image from "./elements/Image";
import Symbol from './elements/Symbol'; import Symbol from "./elements/Symbol";
import Defs from './elements/Defs'; import Defs from "./elements/Defs";
import LinearGradient from './elements/LinearGradient'; import LinearGradient from "./elements/LinearGradient";
import RadialGradient from './elements/RadialGradient'; import RadialGradient from "./elements/RadialGradient";
import Stop from './elements/Stop'; import Stop from "./elements/Stop";
import ClipPath from './elements/ClipPath'; import ClipPath from "./elements/ClipPath";
export { export {
Svg, Svg,

View File

@@ -17,7 +17,7 @@
- (void)parseReference - (void)parseReference
{ {
[[self getSvgView] defineClipPath:self clipPathName:self.name]; [self.svgView defineClipPath:self clipPathName:self.name];
} }

View File

@@ -33,12 +33,14 @@
- (void)renderGroupTo:(CGContextRef)context rect:(CGRect)rect - (void)renderGroupTo:(CGContextRef)context rect:(CGRect)rect
{ {
[self pushGlyphContext]; [self pushGlyphContext];
RNSVGSvgView* svg = [self getSvgView];
__block CGRect groupRect = CGRectNull;
[self traverseSubviews:^(UIView *node) { [self traverseSubviews:^(UIView *node) {
if ([node isKindOfClass:[RNSVGNode class]]) { if ([node isKindOfClass:[RNSVGNode class]]) {
RNSVGNode* svgNode = (RNSVGNode*)node; RNSVGNode* svgNode = (RNSVGNode*)node;
if (svgNode.responsible && !svg.responsible) { if (svgNode.responsible && !self.svgView.responsible) {
svg.responsible = YES; self.svgView.responsible = YES;
} }
if ([node isKindOfClass:[RNSVGRenderable class]]) { if ([node isKindOfClass:[RNSVGRenderable class]]) {
@@ -46,6 +48,11 @@
} }
[svgNode renderTo:context rect:rect]; [svgNode renderTo:context rect:rect];
CGRect nodeRect = svgNode.clientRect;
if (!CGRectIsEmpty(nodeRect)) {
groupRect = CGRectUnion(groupRect, nodeRect);
}
if ([node isKindOfClass:[RNSVGRenderable class]]) { if ([node isKindOfClass:[RNSVGRenderable class]]) {
[(RNSVGRenderable*)node resetProperties]; [(RNSVGRenderable*)node resetProperties];
@@ -63,6 +70,8 @@
return YES; return YES;
}]; }];
[self setHitArea:[self getPath:context]];
self.clientRect = groupRect;
[self popGlyphContext]; [self popGlyphContext];
} }
@@ -84,12 +93,13 @@
- (void)pushGlyphContext - (void)pushGlyphContext
{ {
[[[self getTextRoot] getGlyphContext] pushContext:self font:self.font]; __weak typeof(self) weakSelf = self;
[[self.textRoot getGlyphContext] pushContext:weakSelf font:self.font];
} }
- (void)popGlyphContext - (void)popGlyphContext
{ {
[[[self getTextRoot] getGlyphContext] popContext]; [[self.textRoot getGlyphContext] popContext];
} }
- (void)renderPathTo:(CGContextRef)context rect:(CGRect)rect - (void)renderPathTo:(CGContextRef)context rect:(CGRect)rect
@@ -114,16 +124,19 @@
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{ {
CGPoint transformed = CGPointApplyAffineTransform(point, self.invmatrix); CGPoint transformed = CGPointApplyAffineTransform(point, self.invmatrix);
UIView *hitSelf = [super hitTest:transformed withEvent:event];
if (hitSelf) {
return hitSelf;
}
CGPathRef clip = [self getClipPath]; CGPathRef clip = [self getClipPath];
if (clip && !CGPathContainsPoint(clip, nil, transformed, self.clipRule == kRNSVGCGFCRuleEvenodd)) { if (clip && !CGPathContainsPoint(clip, nil, transformed, self.clipRule == kRNSVGCGFCRuleEvenodd)) {
return nil; return nil;
} }
if (!event) {
NSPredicate *const anyActive = [NSPredicate predicateWithFormat:@"active == TRUE"];
NSArray *const filtered = [self.subviews filteredArrayUsingPredicate:anyActive];
if ([filtered count] != 0) {
return filtered.firstObject;
}
}
for (RNSVGNode *node in [self.subviews reverseObjectEnumerator]) { for (RNSVGNode *node in [self.subviews reverseObjectEnumerator]) {
if (![node isKindOfClass:[RNSVGNode class]]) { if (![node isKindOfClass:[RNSVGNode class]]) {
@@ -143,15 +156,20 @@
return (node.responsible || (node != hitChild)) ? hitChild : self; return (node.responsible || (node != hitChild)) ? hitChild : self;
} }
} }
UIView *hitSelf = [super hitTest:transformed withEvent:event];
if (hitSelf) {
return hitSelf;
}
return nil; return nil;
} }
- (void)parseReference - (void)parseReference
{ {
if (self.name) { if (self.name) {
RNSVGSvgView* svg = [self getSvgView]; typeof(self) __weak weakSelf = self;
[svg defineTemplate:self templateName:self.name]; [self.svgView defineTemplate:weakSelf templateName:self.name];
} }
[self traverseSubviews:^(__kindof RNSVGNode *node) { [self traverseSubviews:^(__kindof RNSVGNode *node) {

View File

@@ -8,11 +8,13 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import <React/RCTBridge.h>
#import "RNSVGRenderable.h" #import "RNSVGRenderable.h"
#import "RNSVGVBMOS.h" #import "RNSVGVBMOS.h"
@interface RNSVGImage : RNSVGRenderable @interface RNSVGImage : RNSVGRenderable
@property (nonatomic, weak) RCTBridge *bridge;
@property (nonatomic, assign) id src; @property (nonatomic, assign) id src;
@property (nonatomic, strong) NSString* x; @property (nonatomic, strong) NSString* x;
@property (nonatomic, strong) NSString* y; @property (nonatomic, strong) NSString* y;

View File

@@ -9,6 +9,7 @@
#import "RNSVGImage.h" #import "RNSVGImage.h"
#import "RCTConvert+RNSVG.h" #import "RCTConvert+RNSVG.h"
#import <React/RCTImageSource.h> #import <React/RCTImageSource.h>
#import <React/RCTImageLoader.h>
#import <React/RCTLog.h> #import <React/RCTLog.h>
#import "RNSVGViewBox.h" #import "RNSVGViewBox.h"
@@ -16,6 +17,7 @@
{ {
CGImageRef _image; CGImageRef _image;
CGSize _imageSize; CGSize _imageSize;
RCTImageLoaderCancellationBlock _reloadImageCancellationBlock;
} }
- (void)setSrc:(id)src - (void)setSrc:(id)src
@@ -25,14 +27,26 @@
} }
_src = src; _src = src;
CGImageRelease(_image); CGImageRelease(_image);
_image = CGImageRetain([RCTConvert CGImage:src]);
RCTImageSource *source = [RCTConvert RCTImageSource:src]; RCTImageSource *source = [RCTConvert RCTImageSource:src];
if (source.size.width != 0 && source.size.height != 0) { if (source.size.width != 0 && source.size.height != 0) {
_imageSize = source.size; _imageSize = source.size;
} else { } else {
_imageSize = CGSizeMake(CGImageGetWidth(_image), CGImageGetHeight(_image)); _imageSize = CGSizeMake(0, 0);
} }
[self invalidate];
RCTImageLoaderCancellationBlock previousCancellationBlock = _reloadImageCancellationBlock;
if (previousCancellationBlock) {
previousCancellationBlock();
_reloadImageCancellationBlock = nil;
}
_reloadImageCancellationBlock = [self.bridge.imageLoader loadImageWithURLRequest:[RCTConvert NSURLRequest:src] callback:^(NSError *error, UIImage *image) {
dispatch_async(dispatch_get_main_queue(), ^{
self->_image = CGImageRetain(image.CGImage);
self->_imageSize = CGSizeMake(CGImageGetWidth(self->_image), CGImageGetHeight(self->_image));
[self invalidate];
});
}];
} }
- (void)setX:(NSString *)x - (void)setX:(NSString *)x
@@ -94,7 +108,7 @@
CGImageRelease(_image); CGImageRelease(_image);
} }
- (void)renderLayerTo:(CGContextRef)context - (void)renderLayerTo:(CGContextRef)context rect:(CGRect)rect
{ {
CGContextSaveGState(context); CGContextSaveGState(context);

View File

@@ -34,12 +34,11 @@
[painter setTransform:self.gradientTransform]; [painter setTransform:self.gradientTransform];
[painter setLinearGradientColors:self.gradient]; [painter setLinearGradientColors:self.gradient];
RNSVGSvgView *svg = [self getSvgView];
if (self.gradientUnits == kRNSVGUnitsUserSpaceOnUse) { if (self.gradientUnits == kRNSVGUnitsUserSpaceOnUse) {
[painter setUserSpaceBoundingBox:[svg getContextBounds]]; [painter setUserSpaceBoundingBox:[self.svgView getContextBounds]];
} }
[svg definePainter:painter painterName:self.name]; [self.svgView definePainter:painter painterName:self.name];
} }
@end @end

View File

@@ -32,12 +32,11 @@
[painter setTransform:self.gradientTransform]; [painter setTransform:self.gradientTransform];
[painter setRadialGradientColors:self.gradient]; [painter setRadialGradientColors:self.gradient];
RNSVGSvgView *svg = [self getSvgView];
if (self.gradientUnits == kRNSVGUnitsUserSpaceOnUse) { if (self.gradientUnits == kRNSVGUnitsUserSpaceOnUse) {
[painter setUserSpaceBoundingBox:[svg getContextBounds]]; [painter setUserSpaceBoundingBox:[self.svgView getContextBounds]];
} }
[svg definePainter:painter painterName:self.name]; [self.svgView definePainter:painter painterName:self.name];
} }
@end @end

View File

@@ -24,7 +24,11 @@
@property (nonatomic, strong) NSString *align; @property (nonatomic, strong) NSString *align;
@property (nonatomic, assign) RNSVGVBMOS meetOrSlice; @property (nonatomic, assign) RNSVGVBMOS meetOrSlice;
@property (nonatomic, assign) BOOL responsible; @property (nonatomic, assign) BOOL responsible;
@property (nonatomic, assign) BOOL active;
@property (nonatomic, assign) CGRect boundingBox; @property (nonatomic, assign) CGRect boundingBox;
@property (nonatomic, assign) CGAffineTransform initialCTM;
@property (nonatomic, assign) CGAffineTransform invInitialCTM;
/** /**

View File

@@ -20,6 +20,16 @@
CGAffineTransform _invviewBoxTransform; CGAffineTransform _invviewBoxTransform;
} }
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
// This is necessary to ensure that [self setNeedsDisplay] actually triggers
// a redraw when our parent transitions between hidden and visible.
self.contentMode = UIViewContentModeRedraw;
}
return self;
}
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex - (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
{ {
[super insertReactSubview:subview atIndex:atIndex]; [super insertReactSubview:subview atIndex:atIndex];
@@ -125,6 +135,8 @@
- (void)drawToContext:(CGContextRef)context withRect:(CGRect)rect { - (void)drawToContext:(CGContextRef)context withRect:(CGRect)rect {
self.initialCTM = CGContextGetCTM(context);
self.invInitialCTM = CGAffineTransformInvert(self.initialCTM);
if (self.align) { if (self.align) {
_viewBoxTransform = [RNSVGViewBox getTransform:CGRectMake(self.minX, self.minY, self.vbWidth, self.vbHeight) _viewBoxTransform = [RNSVGViewBox getTransform:CGRectMake(self.minX, self.minY, self.vbWidth, self.vbHeight)
eRect:rect eRect:rect
@@ -137,7 +149,8 @@
for (UIView *node in self.subviews) { for (UIView *node in self.subviews) {
if ([node isKindOfClass:[RNSVGNode class]]) { if ([node isKindOfClass:[RNSVGNode class]]) {
RNSVGNode *svg = (RNSVGNode *)node; RNSVGNode *svg = (RNSVGNode *)node;
[svg renderTo:context rect:rect]; [svg renderTo:context
rect:rect];
} else { } else {
[node drawRect:rect]; [node drawRect:rect];
} }

View File

@@ -24,7 +24,7 @@
- (void)renderLayerTo:(CGContextRef)context rect:(CGRect)rect - (void)renderLayerTo:(CGContextRef)context rect:(CGRect)rect
{ {
RNSVGNode* template = [[self getSvgView] getDefinedTemplate:self.href]; RNSVGNode* template = [self.svgView getDefinedTemplate:self.href];
if (template) { if (template) {
[self beginTransparencyLayer:context]; [self beginTransparencyLayer:context];
[self clip:context]; [self clip:context];
@@ -49,6 +49,23 @@
// TODO: calling yellow box here // TODO: calling yellow box here
RCTLogWarn(@"`Use` element expected a pre-defined svg template as `href` prop, template named: %@ is not defined.", self.href); RCTLogWarn(@"`Use` element expected a pre-defined svg template as `href` prop, template named: %@ is not defined.", self.href);
} }
self.clientRect = template.clientRect;
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
const CGPoint transformed = CGPointApplyAffineTransform(point, self.invmatrix);
RNSVGNode const* template = [self.svgView getDefinedTemplate:self.href];
if (event) {
self.active = NO;
} else if (self.active) {
return self;
}
UIView const* hitChild = [template hitTest:transformed withEvent:event];
if (hitChild) {
self.active = YES;
return self;
}
return nil;
} }
@end @end

View File

@@ -45,6 +45,11 @@
10ED4A9E1CF0656A0078BC02 /* RNSVGClipPathManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10ED4A9D1CF0656A0078BC02 /* RNSVGClipPathManager.m */; }; 10ED4A9E1CF0656A0078BC02 /* RNSVGClipPathManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10ED4A9D1CF0656A0078BC02 /* RNSVGClipPathManager.m */; };
10ED4AA21CF078830078BC02 /* RNSVGNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 10ED4AA11CF078830078BC02 /* RNSVGNode.m */; }; 10ED4AA21CF078830078BC02 /* RNSVGNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 10ED4AA11CF078830078BC02 /* RNSVGNode.m */; };
10FDEEB21D3FB60500A5C46C /* RNSVGPainterBrush.m in Sources */ = {isa = PBXBuildFile; fileRef = 10FDEEB11D3FB60500A5C46C /* RNSVGPainterBrush.m */; }; 10FDEEB21D3FB60500A5C46C /* RNSVGPainterBrush.m in Sources */ = {isa = PBXBuildFile; fileRef = 10FDEEB11D3FB60500A5C46C /* RNSVGPainterBrush.m */; };
167AF4572087C26F0035AA75 /* RNSVGBezierElement.m in Sources */ = {isa = PBXBuildFile; fileRef = B56895A820352B35004DBF1E /* RNSVGBezierElement.m */; };
167AF4582087C2910035AA75 /* RNSVGFontData.m in Sources */ = {isa = PBXBuildFile; fileRef = B56895B020352B9B004DBF1E /* RNSVGFontData.m */; };
167AF4592087C2950035AA75 /* RNSVGGlyphContext.m in Sources */ = {isa = PBXBuildFile; fileRef = B56895AF20352B9B004DBF1E /* RNSVGGlyphContext.m */; };
167AF45A2087C2990035AA75 /* RNSVGPropHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = B56895AB20352B9B004DBF1E /* RNSVGPropHelper.m */; };
167AF45B2087C2A10035AA75 /* RNSVGTextProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = B56895A62035274F004DBF1E /* RNSVGTextProperties.m */; };
7F08CE9A1E23476900650F83 /* RNSVGTextPathManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F08CE971E23476900650F83 /* RNSVGTextPathManager.m */; }; 7F08CE9A1E23476900650F83 /* RNSVGTextPathManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F08CE971E23476900650F83 /* RNSVGTextPathManager.m */; };
7F08CE9B1E23476900650F83 /* RNSVGTSpanManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F08CE991E23476900650F83 /* RNSVGTSpanManager.m */; }; 7F08CE9B1E23476900650F83 /* RNSVGTSpanManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F08CE991E23476900650F83 /* RNSVGTSpanManager.m */; };
7F08CEA01E23479700650F83 /* RNSVGTextPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F08CE9D1E23479700650F83 /* RNSVGTextPath.m */; }; 7F08CEA01E23479700650F83 /* RNSVGTextPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F08CE9D1E23479700650F83 /* RNSVGTextPath.m */; };
@@ -614,7 +619,9 @@
A361E7811EB0C33D00646005 /* RNSVGClipPath.m in Sources */, A361E7811EB0C33D00646005 /* RNSVGClipPath.m in Sources */,
A361E7821EB0C33D00646005 /* RNSVGSvgViewManager.m in Sources */, A361E7821EB0C33D00646005 /* RNSVGSvgViewManager.m in Sources */,
A361E7831EB0C33D00646005 /* RNSVGSolidColorBrush.m in Sources */, A361E7831EB0C33D00646005 /* RNSVGSolidColorBrush.m in Sources */,
167AF45A2087C2990035AA75 /* RNSVGPropHelper.m in Sources */,
A361E7841EB0C33D00646005 /* RNSVGPathManager.m in Sources */, A361E7841EB0C33D00646005 /* RNSVGPathManager.m in Sources */,
167AF4572087C26F0035AA75 /* RNSVGBezierElement.m in Sources */,
A361E7861EB0C33D00646005 /* RNSVGRenderableManager.m in Sources */, A361E7861EB0C33D00646005 /* RNSVGRenderableManager.m in Sources */,
A361E7871EB0C33D00646005 /* RNSVGRadialGradient.m in Sources */, A361E7871EB0C33D00646005 /* RNSVGRadialGradient.m in Sources */,
A361E7881EB0C33D00646005 /* RNSVGRadialGradientManager.m in Sources */, A361E7881EB0C33D00646005 /* RNSVGRadialGradientManager.m in Sources */,
@@ -623,6 +630,7 @@
A361E78B1EB0C33D00646005 /* RNSVGSymbol.m in Sources */, A361E78B1EB0C33D00646005 /* RNSVGSymbol.m in Sources */,
A361E78C1EB0C33D00646005 /* RNSVGDefs.m in Sources */, A361E78C1EB0C33D00646005 /* RNSVGDefs.m in Sources */,
A361E78D1EB0C33D00646005 /* RNSVGLineManager.m in Sources */, A361E78D1EB0C33D00646005 /* RNSVGLineManager.m in Sources */,
167AF4592087C2950035AA75 /* RNSVGGlyphContext.m in Sources */,
A361E78E1EB0C33D00646005 /* RNSVGCircle.m in Sources */, A361E78E1EB0C33D00646005 /* RNSVGCircle.m in Sources */,
A361E78F1EB0C33D00646005 /* RNSVGEllipseManager.m in Sources */, A361E78F1EB0C33D00646005 /* RNSVGEllipseManager.m in Sources */,
A361E7901EB0C33D00646005 /* RCTConvert+RNSVG.m in Sources */, A361E7901EB0C33D00646005 /* RCTConvert+RNSVG.m in Sources */,
@@ -631,10 +639,12 @@
A361E7931EB0C33D00646005 /* RNSVGPathParser.m in Sources */, A361E7931EB0C33D00646005 /* RNSVGPathParser.m in Sources */,
A361E7941EB0C33D00646005 /* RNSVGGroupManager.m in Sources */, A361E7941EB0C33D00646005 /* RNSVGGroupManager.m in Sources */,
A361E7951EB0C33D00646005 /* RNSVGTextPathManager.m in Sources */, A361E7951EB0C33D00646005 /* RNSVGTextPathManager.m in Sources */,
167AF45B2087C2A10035AA75 /* RNSVGTextProperties.m in Sources */,
A361E7961EB0C33D00646005 /* RNSVGTSpanManager.m in Sources */, A361E7961EB0C33D00646005 /* RNSVGTSpanManager.m in Sources */,
A361E7971EB0C33D00646005 /* RNSVGViewBox.m in Sources */, A361E7971EB0C33D00646005 /* RNSVGViewBox.m in Sources */,
A361E7981EB0C33D00646005 /* RNSVGTSpan.m in Sources */, A361E7981EB0C33D00646005 /* RNSVGTSpan.m in Sources */,
A361E7991EB0C33D00646005 /* RNSVGLine.m in Sources */, A361E7991EB0C33D00646005 /* RNSVGLine.m in Sources */,
167AF4582087C2910035AA75 /* RNSVGFontData.m in Sources */,
A361E79A1EB0C33D00646005 /* RNSVGPainterBrush.m in Sources */, A361E79A1EB0C33D00646005 /* RNSVGPainterBrush.m in Sources */,
A361E79B1EB0C33D00646005 /* RNSVGSvgView.m in Sources */, A361E79B1EB0C33D00646005 /* RNSVGSvgView.m in Sources */,
A361E79C1EB0C33D00646005 /* RNSVGUseManager.m in Sources */, A361E79C1EB0C33D00646005 /* RNSVGUseManager.m in Sources */,

View File

@@ -35,10 +35,18 @@ extern CGFloat const RNSVG_DEFAULT_FONT_SIZE;
@property (nonatomic, assign) CGAffineTransform invmatrix; @property (nonatomic, assign) CGAffineTransform invmatrix;
@property (nonatomic, assign) BOOL active; @property (nonatomic, assign) BOOL active;
@property (nonatomic, assign) CGPathRef path; @property (nonatomic, assign) CGPathRef path;
@property (nonatomic, assign) CGRect clientRect;
@property (nonatomic, copy) RCTDirectEventBlock onLayout;
/**
* RNSVGSvgView which ownes current RNSVGNode
*/
@property (nonatomic, readonly, weak) RNSVGSvgView *svgView;
@property (nonatomic, readonly, weak) RNSVGGroup *textRoot;
- (void)invalidate; - (void)invalidate;
- (RNSVGGroup *)getTextRoot;
- (RNSVGGroup *)getParentTextRoot; - (RNSVGGroup *)getParentTextRoot;
- (void)renderTo:(CGContextRef)context rect:(CGRect)rect; - (void)renderTo:(CGContextRef)context rect:(CGRect)rect;
@@ -70,11 +78,6 @@ extern CGFloat const RNSVG_DEFAULT_FONT_SIZE;
*/ */
- (CGPathRef)getPath:(CGContextRef) context; - (CGPathRef)getPath:(CGContextRef) context;
/**
* get RNSVGSvgView which ownes current RNSVGNode
*/
- (RNSVGSvgView *)getSvgView;
- (CGFloat)relativeOnWidth:(NSString *)length; - (CGFloat)relativeOnWidth:(NSString *)length;
- (CGFloat)relativeOnHeight:(NSString *)length; - (CGFloat)relativeOnHeight:(NSString *)length;

View File

@@ -12,13 +12,16 @@
#import "RNSVGGroup.h" #import "RNSVGGroup.h"
#import "RNSVGGlyphContext.h" #import "RNSVGGlyphContext.h"
@interface RNSVGNode()
@property (nonatomic, readwrite, weak) RNSVGSvgView *svgView;
@property (nonatomic, readwrite, weak) RNSVGGroup *textRoot;
@end
@implementation RNSVGNode @implementation RNSVGNode
{ {
RNSVGGroup *_textRoot;
RNSVGGlyphContext *glyphContext; RNSVGGlyphContext *glyphContext;
BOOL _transparent; BOOL _transparent;
CGPathRef _cachedClipPath; CGPathRef _cachedClipPath;
RNSVGSvgView *_svgView;
} }
CGFloat const RNSVG_M_SQRT1_2l = 0.707106781186547524400844362104849039; CGFloat const RNSVG_M_SQRT1_2l = 0.707106781186547524400844362104849039;
@@ -60,23 +63,25 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12;
} }
} }
- (RNSVGGroup *)getTextRoot - (RNSVGGroup *)textRoot
{ {
if (_textRoot) {
return _textRoot;
}
RNSVGNode* node = self; RNSVGNode* node = self;
if (_textRoot == nil) { while (node != nil) {
while (node != nil) { if ([node isKindOfClass:[RNSVGGroup class]] && [((RNSVGGroup*) node) getGlyphContext] != nil) {
if ([node isKindOfClass:[RNSVGGroup class]] && [((RNSVGGroup*) node) getGlyphContext] != nil) { _textRoot = (RNSVGGroup*)node;
_textRoot = (RNSVGGroup*)node; break;
break; }
}
UIView* parent = [node superview]; UIView* parent = [node superview];
if (![node isKindOfClass:[RNSVGNode class]]) { if (![node isKindOfClass:[RNSVGNode class]]) {
node = nil; node = nil;
} else { } else {
node = (RNSVGNode*)parent; node = (RNSVGNode*)parent;
}
} }
} }
@@ -89,13 +94,13 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12;
if (![parent isKindOfClass:[RNSVGGroup class]]) { if (![parent isKindOfClass:[RNSVGGroup class]]) {
return nil; return nil;
} else { } else {
return [parent getTextRoot]; return parent.textRoot;
} }
} }
- (CGFloat)getFontSizeFromContext - (CGFloat)getFontSizeFromContext
{ {
RNSVGGroup* root = [self getTextRoot]; RNSVGGroup* root = self.textRoot;
if (root == nil) { if (root == nil) {
return RNSVG_DEFAULT_FONT_SIZE; return RNSVG_DEFAULT_FONT_SIZE;
} }
@@ -140,6 +145,24 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12;
[container invalidate]; [container invalidate];
} }
- (void)setClientRect:(CGRect)clientRect {
if (CGRectEqualToRect(_clientRect, clientRect)) {
return;
}
_clientRect = clientRect;
if (self.onLayout) {
self.onLayout(@{
@"layout": @{
@"x": @(_clientRect.origin.x),
@"y": @(_clientRect.origin.y),
@"width": @(_clientRect.size.width),
@"height": @(_clientRect.size.height),
}
});
}
}
- (void)setClipPath:(NSString *)clipPath - (void)setClipPath:(NSString *)clipPath
{ {
if (_clipPath == clipPath) { if (_clipPath == clipPath) {
@@ -177,8 +200,8 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12;
- (CGPathRef)getClipPath:(CGContextRef)context - (CGPathRef)getClipPath:(CGContextRef)context
{ {
if (self.clipPath && !_cachedClipPath) { if (self.clipPath) {
_cachedClipPath = CGPathRetain([[[self getSvgView] getDefinedClipPath:self.clipPath] getPath:context]); _cachedClipPath = CGPathRetain([[self.svgView getDefinedClipPath:self.clipPath] getPath:context]);
} }
return [self getClipPath]; return [self getClipPath];
@@ -217,7 +240,7 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12;
return nil; return nil;
} }
- (RNSVGSvgView *)getSvgView - (RNSVGSvgView *)svgView
{ {
if (_svgView) { if (_svgView) {
return _svgView; return _svgView;
@@ -228,8 +251,7 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12;
if ([parent class] == [RNSVGSvgView class]) { if ([parent class] == [RNSVGSvgView class]) {
_svgView = parent; _svgView = parent;
} else if ([parent isKindOfClass:[RNSVGNode class]]) { } else if ([parent isKindOfClass:[RNSVGNode class]]) {
RNSVGNode *node = parent; _svgView = ((RNSVGNode *)parent).svgView;
_svgView = [node getSvgView];
} else { } else {
RCTLogError(@"RNSVG: %@ should be descendant of a SvgViewShadow.", NSStringFromClass(self.class)); RCTLogError(@"RNSVG: %@ should be descendant of a SvgViewShadow.", NSStringFromClass(self.class));
} }
@@ -271,10 +293,10 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12;
- (CGFloat)getContextWidth - (CGFloat)getContextWidth
{ {
RNSVGGroup * root = [self getTextRoot]; RNSVGGroup * root = self.textRoot;
RNSVGGlyphContext * gc = [root getGlyphContext]; RNSVGGlyphContext * gc = [root getGlyphContext];
if (root == nil || gc == nil) { if (root == nil || gc == nil) {
return CGRectGetWidth([[self getSvgView] getContextBounds]); return CGRectGetWidth([self.svgView getContextBounds]);
} else { } else {
return [gc getWidth]; return [gc getWidth];
} }
@@ -282,10 +304,10 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12;
- (CGFloat)getContextHeight - (CGFloat)getContextHeight
{ {
RNSVGGroup * root = [self getTextRoot]; RNSVGGroup * root = self.textRoot;
RNSVGGlyphContext * gc = [root getGlyphContext]; RNSVGGlyphContext * gc = [root getGlyphContext];
if (root == nil || gc == nil) { if (root == nil || gc == nil) {
return CGRectGetHeight([[self getSvgView] getContextBounds]); return CGRectGetHeight([self.svgView getContextBounds]);
} else { } else {
return [gc getHeight]; return [gc getHeight];
} }
@@ -293,19 +315,19 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12;
- (CGFloat)getContextLeft - (CGFloat)getContextLeft
{ {
return CGRectGetMinX([[self getSvgView] getContextBounds]); return CGRectGetMinX([self.svgView getContextBounds]);
} }
- (CGFloat)getContextTop - (CGFloat)getContextTop
{ {
return CGRectGetMinY([[self getSvgView] getContextBounds]); return CGRectGetMinY([self.svgView getContextBounds]);
} }
- (void)parseReference - (void)parseReference
{ {
if (self.name) { if (self.name) {
RNSVGSvgView* svg = [self getSvgView]; typeof(self) __weak weakSelf = self;
[svg defineTemplate:self templateName:self.name]; [self.svgView defineTemplate:weakSelf templateName:self.name];
} }
} }

View File

@@ -187,6 +187,10 @@
self.path = CGPathRetain(CFAutorelease(CGPathCreateCopy([self getPath:context]))); self.path = CGPathRetain(CFAutorelease(CGPathCreateCopy([self getPath:context])));
[self setHitArea:self.path]; [self setHitArea:self.path];
} }
const CGRect pathBounding = CGPathGetBoundingBox(self.path);
const CGAffineTransform svgToClientTransform = CGAffineTransformConcat(CGContextGetCTM(context), self.svgView.invInitialCTM);
self.clientRect = CGRectApplyAffineTransform(pathBounding, svgToClientTransform);
CGPathDrawingMode mode = kCGPathStroke; CGPathDrawingMode mode = kCGPathStroke;
BOOL fillColor = NO; BOOL fillColor = NO;
@@ -205,7 +209,7 @@
CGContextClip(context); CGContextClip(context);
[self.fill paint:context [self.fill paint:context
opacity:self.fillOpacity opacity:self.fillOpacity
painter:[[self getSvgView] getDefinedPainter:self.fill.brushRef] painter:[self.svgView getDefinedPainter:self.fill.brushRef]
]; ];
CGContextRestoreGState(context); CGContextRestoreGState(context);
@@ -250,7 +254,7 @@
[self.stroke paint:context [self.stroke paint:context
opacity:self.strokeOpacity opacity:self.strokeOpacity
painter:[[self getSvgView] getDefinedPainter:self.stroke.brushRef] painter:[self.svgView getDefinedPainter:self.stroke.brushRef]
]; ];
return; return;
} }
@@ -264,21 +268,19 @@
{ {
CGPathRelease(_hitArea); CGPathRelease(_hitArea);
_hitArea = nil; _hitArea = nil;
if (self.responsible) { // Add path to hitArea
// Add path to hitArea CGMutablePathRef hitArea = CGPathCreateMutableCopy(path);
CGMutablePathRef hitArea = CGPathCreateMutableCopy(path);
if (self.stroke && self.strokeWidth) {
if (self.stroke && self.strokeWidth) { // Add stroke to hitArea
// Add stroke to hitArea CGFloat width = [self relativeOnOther:self.strokeWidth];
CGFloat width = [self relativeOnOther:self.strokeWidth]; CGPathRef strokePath = CGPathCreateCopyByStrokingPath(hitArea, nil, width, self.strokeLinecap, self.strokeLinejoin, self.strokeMiterlimit);
CGPathRef strokePath = CGPathCreateCopyByStrokingPath(hitArea, nil, width, self.strokeLinecap, self.strokeLinejoin, self.strokeMiterlimit); CGPathAddPath(hitArea, nil, strokePath);
CGPathAddPath(hitArea, nil, strokePath); CGPathRelease(strokePath);
CGPathRelease(strokePath);
}
_hitArea = CGPathRetain(CFAutorelease(CGPathCreateCopy(hitArea)));
CGPathRelease(hitArea);
} }
_hitArea = CGPathRetain(CFAutorelease(CGPathCreateCopy(hitArea)));
CGPathRelease(hitArea);
} }

View File

@@ -96,7 +96,7 @@ static double RNSVGTSpan_radToDeg = 180 / M_PI;
// Create a dictionary for this font // Create a dictionary for this font
CTFontRef fontRef = [self getFontFromContext]; CTFontRef fontRef = [self getFontFromContext];
CGMutablePathRef path = CGPathCreateMutable(); CGMutablePathRef path = CGPathCreateMutable();
RNSVGGlyphContext* gc = [[self getTextRoot] getGlyphContext]; RNSVGGlyphContext* gc = [self.textRoot getGlyphContext];
RNSVGFontData* font = [gc getFont]; RNSVGFontData* font = [gc getFont];
NSUInteger n = str.length; NSUInteger n = str.length;
/* /*
@@ -230,17 +230,30 @@ static double RNSVGTSpan_radToDeg = 180 / M_PI;
*/ */
// OpenType.js font data // OpenType.js font data
NSDictionary * fontData = font->fontData; NSDictionary * fontData = font->fontData;
NSMutableDictionary *attrs = [[NSMutableDictionary alloc] init];
NSNumber *lig = [NSNumber numberWithInt:allowOptionalLigatures ? 2 : 1]; NSNumber *lig = [NSNumber numberWithInt:allowOptionalLigatures ? 2 : 1];
attrs[NSLigatureAttributeName] = lig;
CFDictionaryRef attributes; CFDictionaryRef attributes;
if (fontRef != nil) { if (fontRef != nil) {
attributes = (__bridge CFDictionaryRef)@{ attrs[NSFontAttributeName] = (__bridge id)fontRef;
(NSString *)kCTFontAttributeName: (__bridge id)fontRef,
(NSString *)NSLigatureAttributeName: lig };
} else {
attributes = (__bridge CFDictionaryRef)@{
(NSString *)NSLigatureAttributeName: lig };
} }
if (!autoKerning) {
NSNumber *noAutoKern = [NSNumber numberWithFloat:0.0f];
#if DTCORETEXT_SUPPORT_NS_ATTRIBUTES
if (___useiOS6Attributes)
{
[attrs setObject:noAutoKern forKey:NSKernAttributeName];
}
else
#endif
{
[attrs setObject:noAutoKern forKey:(id)kCTKernAttributeName];
}
}
attributes = (__bridge CFDictionaryRef)attrs;
CFStringRef string = (__bridge CFStringRef)str; CFStringRef string = (__bridge CFStringRef)str;
CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes); CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes);
@@ -509,8 +522,8 @@ static double RNSVGTSpan_radToDeg = 180 / M_PI;
double top = ascenderHeight; double top = ascenderHeight;
double totalHeight = top + bottom; double totalHeight = top + bottom;
double baselineShift = 0; double baselineShift = 0;
NSString *baselineShiftString = [self getBaselineShift]; NSString *baselineShiftString = self.baselineShift;
enum RNSVGAlignmentBaseline baseline = RNSVGAlignmentBaselineFromString([self getAlignmentBaseline]); enum RNSVGAlignmentBaseline baseline = RNSVGAlignmentBaselineFromString(self.alignmentBaseline);
if (baseline != RNSVGAlignmentBaselineBaseline) { if (baseline != RNSVGAlignmentBaselineBaseline) {
// TODO alignment-baseline, test / verify behavior // TODO alignment-baseline, test / verify behavior
// TODO get per glyph baselines from font baseline table, for high-precision alignment // TODO get per glyph baselines from font baseline table, for high-precision alignment
@@ -683,11 +696,7 @@ static double RNSVGTSpan_radToDeg = 180 / M_PI;
/* /*
Determine the glyph's charwidth (i.e., the amount which the current text position Determine the glyph's charwidth (i.e., the amount which the current text position
advances horizontally when the glyph is drawn using horizontal text layout). advances horizontally when the glyph is drawn using horizontal text layout).
*/
double unkernedAdvance = CTFontGetAdvancesForGlyphs(fontRef, kCTFontOrientationHorizontal, &glyph, NULL, 1);
CGFloat charWidth = unkernedAdvance * scaleSpacingAndGlyphs;
/*
For each subsequent glyph, set a new startpoint-on-the-path as the previous For each subsequent glyph, set a new startpoint-on-the-path as the previous
endpoint-on-the-path, but with appropriate adjustments taking into account endpoint-on-the-path, but with appropriate adjustments taking into account
horizontal kerning tables in the font and current values of various attributes horizontal kerning tables in the font and current values of various attributes
@@ -696,10 +705,7 @@ static double RNSVGTSpan_radToDeg = 180 / M_PI;
adjustments are calculated as distance adjustments along the path, calculated adjustments are calculated as distance adjustments along the path, calculated
using the user agent's distance along the path algorithm. using the user agent's distance along the path algorithm.
*/ */
if (autoKerning) { CGFloat charWidth = advances[g].width * scaleSpacingAndGlyphs;
double kerned = advances[g].width * scaleSpacingAndGlyphs;
kerning = kerned - charWidth;
}
CFIndex currIndex = indices[g]; CFIndex currIndex = indices[g];
char currentChar = [str characterAtIndex:currIndex]; char currentChar = [str characterAtIndex:currIndex];

View File

@@ -24,7 +24,5 @@
- (void)releaseCachedPath; - (void)releaseCachedPath;
- (CGPathRef)getGroupPath:(CGContextRef)context; - (CGPathRef)getGroupPath:(CGContextRef)context;
- (CTFontRef)getFontFromContext; - (CTFontRef)getFontFromContext;
- (NSString*) getAlignmentBaseline;
- (NSString*) getBaselineShift;
@end @end

View File

@@ -15,7 +15,6 @@
@implementation RNSVGText @implementation RNSVGText
{ {
RNSVGText *_textRoot;
RNSVGGlyphContext *_glyphContext; RNSVGGlyphContext *_glyphContext;
} }
@@ -76,68 +75,69 @@
[self popGlyphContext]; [self popGlyphContext];
} }
- (RNSVGText *)getTextRoot // TODO: Optimisation required
- (RNSVGText *)textRoot
{ {
if (!_textRoot) { RNSVGText *root = self;
_textRoot = self; while (root && [root class] != [RNSVGText class]) {
while (_textRoot && [_textRoot class] != [RNSVGText class]) { if (![root isKindOfClass:[RNSVGText class]]) {
if (![_textRoot isKindOfClass:[RNSVGText class]]) { //todo: throw exception here
//todo: throw exception here break;
break;
}
_textRoot = (RNSVGText*)[_textRoot superview];
} }
root = (RNSVGText*)[root superview];
} }
return _textRoot; return root;
} }
- (NSString*) getAlignmentBaseline - (NSString *)alignmentBaseline
{ {
if (self.alignmentBaseline != nil) { if (_alignmentBaseline != nil) {
return self.alignmentBaseline; return _alignmentBaseline;
} }
UIView* parent = [self superview];
UIView* parent = self.superview;
while (parent != nil) { while (parent != nil) {
if ([parent isKindOfClass:[RNSVGText class]]) { if ([parent isKindOfClass:[RNSVGText class]]) {
RNSVGText* node = (RNSVGText*)parent; RNSVGText* node = (RNSVGText*)parent;
NSString* baseline = node.alignmentBaseline; NSString* baseline = node.alignmentBaseline;
if (baseline != nil) { if (baseline != nil) {
self.alignmentBaseline = baseline; _alignmentBaseline = baseline;
return baseline; return baseline;
} }
} }
parent = [parent superview]; parent = [parent superview];
} }
if (self.alignmentBaseline == nil) {
self.alignmentBaseline = RNSVGAlignmentBaselineStrings[0]; if (_alignmentBaseline == nil) {
_alignmentBaseline = RNSVGAlignmentBaselineStrings[0];
} }
return self.alignmentBaseline; return _alignmentBaseline;
} }
- (NSString*) getBaselineShift - (NSString *)baselineShift
{ {
if (self.baselineShift != nil) { if (_baselineShift != nil) {
return self.baselineShift; return _baselineShift;
} }
if (self.baselineShift == nil) {
UIView* parent = [self superview]; UIView* parent = [self superview];
while (parent != nil) { while (parent != nil) {
if ([parent isKindOfClass:[RNSVGText class]]) { if ([parent isKindOfClass:[RNSVGText class]]) {
RNSVGText* node = (RNSVGText*)parent; RNSVGText* node = (RNSVGText*)parent;
NSString* baselineShift = node.baselineShift; NSString* baselineShift = node.baselineShift;
if (baselineShift != nil) { if (baselineShift != nil) {
self.baselineShift = baselineShift; _baselineShift = baselineShift;
return baselineShift; return baselineShift;
}
} }
parent = [parent superview];
} }
parent = [parent superview];
} }
if (self.baselineShift == nil) {
self.baselineShift = @""; // set default value
} _baselineShift = @"";
return self.baselineShift;
return _baselineShift;
} }
- (RNSVGGlyphContext *)getGlyphContext - (RNSVGGlyphContext *)getGlyphContext
@@ -147,23 +147,23 @@
- (void)pushGlyphContext - (void)pushGlyphContext
{ {
[[[self getTextRoot] getGlyphContext] pushContext:self [[self.textRoot getGlyphContext] pushContext:self
font:self.font font:self.font
x:self.positionX x:self.positionX
y:self.positionY y:self.positionY
deltaX:self.deltaX deltaX:self.deltaX
deltaY:self.deltaY deltaY:self.deltaY
rotate:self.rotate]; rotate:self.rotate];
} }
- (void)popGlyphContext - (void)popGlyphContext
{ {
[[[self getTextRoot] getGlyphContext] popContext]; [[self.textRoot getGlyphContext] popContext];
} }
- (CTFontRef)getFontFromContext - (CTFontRef)getFontFromContext
{ {
return [[[self getTextRoot] getGlyphContext] getGlyphFont]; return [[self.textRoot getGlyphContext] getGlyphFont];
} }
@end @end

View File

@@ -114,8 +114,7 @@ void RNSVGPerformanceBezier_addLine(CGPoint *last, const CGPoint *next, NSMutabl
- (void)getPathLength:(CGFloat*)lengthP lineCount:(NSUInteger*)lineCountP lengths:(NSArray* __strong *)lengthsP lines:(NSArray* __strong *)linesP isClosed:(BOOL*)isClosedP - (void)getPathLength:(CGFloat*)lengthP lineCount:(NSUInteger*)lineCountP lengths:(NSArray* __strong *)lengthsP lines:(NSArray* __strong *)linesP isClosed:(BOOL*)isClosedP
{ {
RNSVGSvgView *svg = [self getSvgView]; RNSVGNode *template = [self.svgView getDefinedTemplate:self.href];
RNSVGNode *template = [svg getDefinedTemplate:self.href];
CGPathRef path = [template getPath:nil]; CGPathRef path = [template getPath:nil];
if (_path != path) { if (_path != path) {

View File

@@ -17,7 +17,10 @@ RCT_EXPORT_MODULE()
- (RNSVGRenderable *)node - (RNSVGRenderable *)node
{ {
return [RNSVGImage new]; RNSVGImage *svgImage = [RNSVGImage new];
svgImage.bridge = self.bridge;
return svgImage;
} }
RCT_EXPORT_VIEW_PROPERTY(x, NSString) RCT_EXPORT_VIEW_PROPERTY(x, NSString)

View File

@@ -30,5 +30,7 @@ RCT_EXPORT_VIEW_PROPERTY(matrix, CGAffineTransform)
RCT_EXPORT_VIEW_PROPERTY(clipPath, NSString) RCT_EXPORT_VIEW_PROPERTY(clipPath, NSString)
RCT_EXPORT_VIEW_PROPERTY(clipRule, RNSVGCGFCRule) RCT_EXPORT_VIEW_PROPERTY(clipRule, RNSVGCGFCRule)
RCT_EXPORT_VIEW_PROPERTY(responsible, BOOL) RCT_EXPORT_VIEW_PROPERTY(responsible, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onLayout, RCTDirectEventBlock)
@end @end

View File

@@ -119,7 +119,14 @@ export default class Matrix2D {
*/ */
copy = function(matrix) { copy = function(matrix) {
//noinspection JSUnresolvedVariable //noinspection JSUnresolvedVariable
return this.setTransform(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty); return this.setTransform(
matrix.a,
matrix.b,
matrix.c,
matrix.d,
matrix.tx,
matrix.ty
);
}; };
//noinspection JSUnusedGlobalSymbols //noinspection JSUnusedGlobalSymbols
@@ -150,10 +157,10 @@ export default class Matrix2D {
const c1 = this.c; const c1 = this.c;
const tx1 = this.tx; const tx1 = this.tx;
this.a = a * a1 + c * this.b; this.a = a * a1 + c * this.b;
this.b = b * a1 + d * this.b; this.b = b * a1 + d * this.b;
this.c = a * c1 + c * this.d; this.c = a * c1 + c * this.d;
this.d = b * c1 + d * this.d; this.d = b * c1 + d * this.d;
this.tx = a * tx1 + c * this.ty + tx; this.tx = a * tx1 + c * this.ty + tx;
this.ty = b * tx1 + d * this.ty + ty; this.ty = b * tx1 + d * this.ty + ty;
//noinspection JSValidateTypes //noinspection JSValidateTypes
@@ -178,10 +185,10 @@ export default class Matrix2D {
const c1 = this.c; const c1 = this.c;
const d1 = this.d; const d1 = this.d;
if (a !== 1 || b !== 0 || c !== 0 || d !== 1) { if (a !== 1 || b !== 0 || c !== 0 || d !== 1) {
this.a = a1 * a + c1 * b; this.a = a1 * a + c1 * b;
this.b = b1 * a + d1 * b; this.b = b1 * a + d1 * b;
this.c = a1 * c + c1 * d; this.c = a1 * c + c1 * d;
this.d = b1 * c + d1 * d; this.d = b1 * c + d1 * d;
} }
this.tx = a1 * tx + c1 * ty + this.tx; this.tx = a1 * tx + c1 * ty + this.tx;
this.ty = b1 * tx + d1 * ty + this.ty; this.ty = b1 * tx + d1 * ty + this.ty;
@@ -207,7 +214,17 @@ export default class Matrix2D {
* @param {Number} regY Optional. * @param {Number} regY Optional.
* @return {Matrix2D} This matrix. Useful for chaining method calls. * @return {Matrix2D} This matrix. Useful for chaining method calls.
**/ **/
appendTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, regX, regY) { appendTransform = function(
x,
y,
scaleX,
scaleY,
rotation,
skewX,
skewY,
regX,
regY
) {
let cos, sin; let cos, sin;
if (rotation % 360) { if (rotation % 360) {
const r = rotation * DEG_TO_RAD; const r = rotation * DEG_TO_RAD;
@@ -222,10 +239,31 @@ export default class Matrix2D {
// TODO: can this be combined into a single append operation? // TODO: can this be combined into a single append operation?
skewX *= DEG_TO_RAD; skewX *= DEG_TO_RAD;
skewY *= DEG_TO_RAD; skewY *= DEG_TO_RAD;
this.append(Math.cos(skewY), Math.sin(skewY), Math.sin(skewX), Math.cos(skewX), x, y); this.append(
this.append(cos * scaleX, sin * scaleX, -sin * scaleY, cos * scaleY, 0, 0); Math.cos(skewY),
Math.sin(skewY),
Math.sin(skewX),
Math.cos(skewX),
x,
y
);
this.append(
cos * scaleX,
sin * scaleX,
-sin * scaleY,
cos * scaleY,
0,
0
);
} else { } else {
this.append(cos * scaleX, sin * scaleX, -sin * scaleY, cos * scaleY, x, y); this.append(
cos * scaleX,
sin * scaleX,
-sin * scaleY,
cos * scaleY,
x,
y
);
} }
if (regX || regY) { if (regX || regY) {
@@ -245,9 +283,9 @@ export default class Matrix2D {
* var o = myDisplayObject; * var o = myDisplayObject;
* var mtx = new createjs.Matrix2D(); * var mtx = new createjs.Matrix2D();
* do { * do {
* // prepend each parent's transformation in turn: * // prepend each parent's transformation in turn:
* mtx.prependTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation, o.skewX, o.skewY, o.regX, o.regY); * mtx.prependTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation, o.skewX, o.skewY, o.regX, o.regY);
* } while (o = o.parent); * } while (o = o.parent);
* *
* Note that the above example would not account for {{#crossLink "DisplayObject/transformMatrix:property"}}{{/crossLink}} * Note that the above example would not account for {{#crossLink "DisplayObject/transformMatrix:property"}}{{/crossLink}}
* values. See {{#crossLink "Matrix2D/prependMatrix"}}{{/crossLink}} for an example that does. * values. See {{#crossLink "Matrix2D/prependMatrix"}}{{/crossLink}} for an example that does.
@@ -263,7 +301,17 @@ export default class Matrix2D {
* @param {Number} regY Optional. * @param {Number} regY Optional.
* @return {Matrix2D} This matrix. Useful for chaining method calls. * @return {Matrix2D} This matrix. Useful for chaining method calls.
**/ **/
prependTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, regX, regY) { prependTransform = function(
x,
y,
scaleX,
scaleY,
rotation,
skewX,
skewY,
regX,
regY
) {
let cos, sin; let cos, sin;
if (rotation % 360) { if (rotation % 360) {
const r = rotation * DEG_TO_RAD; const r = rotation * DEG_TO_RAD;
@@ -276,16 +324,38 @@ export default class Matrix2D {
if (regX || regY) { if (regX || regY) {
// prepend the registration offset: // prepend the registration offset:
this.tx -= regX; this.ty -= regY; this.tx -= regX;
this.ty -= regY;
} }
if (skewX || skewY) { if (skewX || skewY) {
// TODO: can this be combined into a single prepend operation? // TODO: can this be combined into a single prepend operation?
skewX *= DEG_TO_RAD; skewX *= DEG_TO_RAD;
skewY *= DEG_TO_RAD; skewY *= DEG_TO_RAD;
this.prepend(cos * scaleX, sin * scaleX, -sin * scaleY, cos * scaleY, 0, 0); this.prepend(
this.prepend(Math.cos(skewY), Math.sin(skewY), -Math.sin(skewX), Math.cos(skewX), x, y); cos * scaleX,
sin * scaleX,
-sin * scaleY,
cos * scaleY,
0,
0
);
this.prepend(
Math.cos(skewY),
Math.sin(skewY),
-Math.sin(skewX),
Math.cos(skewX),
x,
y
);
} else { } else {
this.prepend(cos * scaleX, sin * scaleX, -sin * scaleY, cos * scaleY, x, y); this.prepend(
cos * scaleX,
sin * scaleX,
-sin * scaleY,
cos * scaleY,
x,
y
);
} }
//noinspection JSValidateTypes //noinspection JSValidateTypes
return this; return this;

View File

@@ -1,4 +1,3 @@
export default { export default {
objectBoundingBox: 0, objectBoundingBox: 0,
userSpaceOnUse: 1 userSpaceOnUse: 1

View File

@@ -1,28 +1,33 @@
import Touchable from 'react-native/Libraries/Components/Touchable/Touchable'; import { Touchable } from "react-native";
const PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30}; const PRESS_RETENTION_OFFSET = { top: 20, left: 20, right: 20, bottom: 30 };
//noinspection JSUnusedGlobalSymbols //noinspection JSUnusedGlobalSymbols
export default { export default {
...Touchable.Mixin, ...Touchable.Mixin,
touchableHandleStartShouldSetResponder: function (e) { touchableHandleStartShouldSetResponder: function(e) {
if (this.props.onStartShouldSetResponder) { if (this.props.onStartShouldSetResponder) {
return this.props.onStartShouldSetResponder(e); return this.props.onStartShouldSetResponder(e);
} else { } else {
return Touchable.Mixin.touchableHandleStartShouldSetResponder.call(this, e); return Touchable.Mixin.touchableHandleStartShouldSetResponder.call(
this,
e
);
} }
}, },
touchableHandleResponderTerminationRequest: function (e) { touchableHandleResponderTerminationRequest: function(e) {
if (this.props.onResponderTerminationRequest) { if (this.props.onResponderTerminationRequest) {
return this.props.onResponderTerminationRequest(e); return this.props.onResponderTerminationRequest(e);
} else { } else {
return Touchable.Mixin.touchableHandleResponderTerminationRequest.call(this, e); return Touchable.Mixin.touchableHandleResponderTerminationRequest.call(
this,
e
);
} }
}, },
touchableHandleResponderGrant: function (e) { touchableHandleResponderGrant: function(e) {
if (this.props.onResponderGrant) { if (this.props.onResponderGrant) {
return this.props.onResponderGrant(e); return this.props.onResponderGrant(e);
} else { } else {
@@ -30,7 +35,7 @@ export default {
} }
}, },
touchableHandleResponderMove: function (e) { touchableHandleResponderMove: function(e) {
if (this.props.onResponderMove) { if (this.props.onResponderMove) {
return this.props.onResponderMove(e); return this.props.onResponderMove(e);
} else { } else {
@@ -38,19 +43,25 @@ export default {
} }
}, },
touchableHandleResponderRelease: function (e) { touchableHandleResponderRelease: function(e) {
if (this.props.onResponderRelease) { if (this.props.onResponderRelease) {
return this.props.onResponderRelease(e); return this.props.onResponderRelease(e);
} else { } else {
return Touchable.Mixin.touchableHandleResponderRelease.call(this, e); return Touchable.Mixin.touchableHandleResponderRelease.call(
this,
e
);
} }
}, },
touchableHandleResponderTerminate: function (e) { touchableHandleResponderTerminate: function(e) {
if (this.props.onResponderTerminate) { if (this.props.onResponderTerminate) {
return this.props.onResponderTerminate(e); return this.props.onResponderTerminate(e);
} else { } else {
return Touchable.Mixin.touchableHandleResponderTerminate.call(this, e); return Touchable.Mixin.touchableHandleResponderTerminate.call(
this,
e
);
} }
}, },
@@ -83,8 +94,9 @@ export default {
}, },
touchableGetLongPressDelayMS: function() { touchableGetLongPressDelayMS: function() {
return this.props.delayLongPress === 0 ? 0 : return this.props.delayLongPress === 0
this.props.delayLongPress || 500; ? 0
: this.props.delayLongPress || 500;
}, },
touchableGetPressOutDelayMS: function() { touchableGetPressOutDelayMS: function() {

View File

@@ -1,4 +1,3 @@
function arrayDiffer(a, b) { function arrayDiffer(a, b) {
if (!a || !b) { if (!a || !b) {
return true; return true;
@@ -91,7 +90,7 @@ const GroupAttributes = {
...RenderableAttributes, ...RenderableAttributes,
font: { font: {
diff: fontDiffer diff: fontDiffer
}, }
}; };
const UseAttributes = { const UseAttributes = {
@@ -103,12 +102,12 @@ const UseAttributes = {
const SymbolAttributes = { const SymbolAttributes = {
...ViewBoxAttributes, ...ViewBoxAttributes,
name: true, name: true
}; };
const PathAttributes = { const PathAttributes = {
...RenderableAttributes, ...RenderableAttributes,
d: true, d: true
}; };
const TextSpecificAttributes = { const TextSpecificAttributes = {
@@ -117,7 +116,7 @@ const TextSpecificAttributes = {
baselineShift: true, baselineShift: true,
verticalAlign: true, verticalAlign: true,
lengthAdjust: true, lengthAdjust: true,
textLength: true, textLength: true
}; };
const TextAttributes = { const TextAttributes = {
@@ -129,7 +128,7 @@ const TextAttributes = {
deltaY: arrayDiffer, deltaY: arrayDiffer,
rotate: arrayDiffer, rotate: arrayDiffer,
positionX: arrayDiffer, positionX: arrayDiffer,
positionY: arrayDiffer, positionY: arrayDiffer
}; };
const TextPathAttributes = { const TextPathAttributes = {
@@ -139,12 +138,12 @@ const TextPathAttributes = {
method: true, method: true,
spacing: true, spacing: true,
side: true, side: true,
midLine: true, midLine: true
}; };
const TSpanAttibutes = { const TSpanAttibutes = {
...TextAttributes, ...TextAttributes,
content: true, content: true
}; };
const ClipPathAttributes = { const ClipPathAttributes = {
@@ -159,7 +158,7 @@ const GradientAttributes = {
gradientUnits: true, gradientUnits: true,
gradientTransform: { gradientTransform: {
diff: arrayDiffer diff: arrayDiffer
}, }
}; };
const LinearGradientAttributes = { const LinearGradientAttributes = {
@@ -167,7 +166,7 @@ const LinearGradientAttributes = {
x1: true, x1: true,
y1: true, y1: true,
x2: true, x2: true,
y2: true, y2: true
}; };
const RadialGradientAttributes = { const RadialGradientAttributes = {
@@ -178,15 +177,14 @@ const RadialGradientAttributes = {
ry: true, ry: true,
cx: true, cx: true,
cy: true, cy: true,
r: true, r: true
}; };
const CircleAttributes = { const CircleAttributes = {
...RenderableAttributes, ...RenderableAttributes,
cx: true, cx: true,
cy: true, cy: true,
r: true, r: true
}; };
const EllipseAttributes = { const EllipseAttributes = {
@@ -194,7 +192,7 @@ const EllipseAttributes = {
cx: true, cx: true,
cy: true, cy: true,
rx: true, rx: true,
ry: true, ry: true
}; };
const ImageAttributes = { const ImageAttributes = {
@@ -205,7 +203,7 @@ const ImageAttributes = {
imageheight: true, imageheight: true,
src: true, src: true,
align: true, align: true,
meetOrSlice: true, meetOrSlice: true
}; };
const LineAttributes = { const LineAttributes = {
@@ -213,7 +211,7 @@ const LineAttributes = {
x1: true, x1: true,
y1: true, y1: true,
x2: true, x2: true,
y2: true, y2: true
}; };
const RectAttributes = { const RectAttributes = {
@@ -223,7 +221,7 @@ const RectAttributes = {
rectwidth: true, rectwidth: true,
rectheight: true, rectheight: true,
rx: true, rx: true,
ry: true, ry: true
}; };
export { export {

View File

@@ -1,6 +0,0 @@
import createReactNativeComponentClass from 'react-native/Libraries/Renderer/shims/createReactNativeComponentClass.js'
export default (uiViewClassName, getViewConfig) =>
createReactNativeComponentClass.length >= 2
? createReactNativeComponentClass(uiViewClassName, getViewConfig)
: createReactNativeComponentClass(getViewConfig())

View File

@@ -1,8 +1,8 @@
import Color from 'color'; import Color from "color";
import patternReg from './patternReg'; import patternReg from "./patternReg";
export default function (colorOrBrush) { export default function(colorOrBrush) {
if (colorOrBrush === 'none' || !colorOrBrush) { if (colorOrBrush === "none" || !colorOrBrush) {
return null; return null;
} }
@@ -12,8 +12,11 @@ export default function (colorOrBrush) {
if (matched) { if (matched) {
return [1, matched[1]]; return [1, matched[1]];
//todo: //todo:
} else { // solid color } else {
let [r, g, b, a = 1] = Color(colorOrBrush).rgb().array(); // solid color
let [r, g, b, a = 1] = Color(colorOrBrush)
.rgb()
.array();
return [0, r / 255, g / 255, b / 255, a]; return [0, r / 255, g / 255, b / 255, a];
} }
} catch (err) { } catch (err) {

View File

@@ -1,12 +1,12 @@
import clipReg from './patternReg'; import clipReg from "./patternReg";
const clipRules = { const clipRules = {
evenodd: 0, evenodd: 0,
nonzero: 1 nonzero: 1
}; };
export default function (props) { export default function(props) {
let {clipPath, clipRule} = props; let { clipPath, clipRule } = props;
let clipPathProps = {}; let clipPathProps = {};
if (clipPath) { if (clipPath) {
@@ -17,7 +17,11 @@ export default function (props) {
if (matched) { if (matched) {
clipPathProps.clipPath = matched[1]; clipPathProps.clipPath = matched[1];
} else { } else {
console.warn('Invalid `clipPath` prop, expected a clipPath like `"#id"`, but got: "' + clipPath + '"'); console.warn(
'Invalid `clipPath` prop, expected a clipPath like `"#id"`, but got: "' +
clipPath +
'"'
);
} }
} }

View File

@@ -1,6 +1,6 @@
import extractBrush from './extractBrush'; import extractBrush from "./extractBrush";
import extractOpacity from './extractOpacity'; import extractOpacity from "./extractOpacity";
import {fillProps} from '../props'; import { fillProps } from "../props";
const fillRules = { const fillRules = {
evenodd: 0, evenodd: 0,
@@ -10,7 +10,7 @@ const fillRules = {
const fillKeys = Object.keys(fillProps); const fillKeys = Object.keys(fillProps);
export default function(props, styleProperties) { export default function(props, styleProperties) {
fillKeys.forEach((name) => { fillKeys.forEach(name => {
if (props.hasOwnProperty(name)) { if (props.hasOwnProperty(name)) {
styleProperties.push(name); styleProperties.push(name);
} }
@@ -18,7 +18,7 @@ export default function(props, styleProperties) {
return { return {
// default fill is black // default fill is black
fill: extractBrush(props.fill || '#000'), fill: extractBrush(props.fill || "#000"),
fillOpacity: extractOpacity(props.fillOpacity), fillOpacity: extractOpacity(props.fillOpacity),
fillRule: fillRules[props.fillRule] === 0 ? 0 : 1 fillRule: fillRules[props.fillRule] === 0 ? 0 : 1
}; };

View File

@@ -1,12 +1,12 @@
import {Children} from 'react'; import { Children } from "react";
import _ from 'lodash'; import _ from "lodash";
import Color from 'color'; import Color from "color";
import extractOpacity from './extractOpacity'; import extractOpacity from "./extractOpacity";
import extractTransform from './extractTransform'; import extractTransform from "./extractTransform";
import PATTERN_UNITS from '../PATTERN_UNITS'; import PATTERN_UNITS from "../PATTERN_UNITS";
import percentToFloat from '../percentToFloat'; import percentToFloat from "../percentToFloat";
import Stop from '../../elements/Stop'; import Stop from "../../elements/Stop";
export default function(props) { export default function(props) {
if (!props.id) { if (!props.id) {
@@ -15,27 +15,28 @@ export default function(props) {
const stops = {}; const stops = {};
Children.forEach(props.children, child => { Children.forEach(props.children, child => {
if (child.type === Stop) { if (child.props.stopColor && child.props.offset) {
if (child.props.stopColor && child.props.offset) { // convert percent to float.
// convert percent to float. let offset = percentToFloat(child.props.offset);
let offset = percentToFloat(child.props.offset);
// add stop // add stop
//noinspection JSUnresolvedFunction //noinspection JSUnresolvedFunction
stops[offset] = Color(child.props.stopColor).alpha(extractOpacity(child.props.stopOpacity)); stops[offset] = Color(child.props.stopColor).alpha(
} extractOpacity(child.props.stopOpacity)
} else { );
console.warn('`Gradient` elements only accept `Stop` elements as children');
} }
}); });
const sorted = _.sortBy(_.map(stops, (stop, offset) => { const sorted = _.sortBy(
return {stop, offset}; _.map(stops, (stop, offset) => {
}), 'offset'); return { stop, offset };
}),
"offset"
);
const gradient = []; const gradient = [];
sorted.forEach(({stop}) => { sorted.forEach(({ stop }) => {
let [r, g, b, a = 1] = stop.rgb().array(); let [r, g, b, a = 1] = stop.rgb().array();
gradient.push(r / 255); gradient.push(r / 255);
gradient.push(g / 255); gradient.push(g / 255);
@@ -43,8 +44,7 @@ export default function(props) {
gradient.push(a); gradient.push(a);
}); });
gradient.push(...sorted.map(({offset}) => +offset)); gradient.push(...sorted.map(({ offset }) => +offset));
let gradientTransform; let gradientTransform;
if (props.gradientTransform) { if (props.gradientTransform) {

View File

@@ -1,12 +1,15 @@
const spaceReg = /\s+/; const spaceReg = /\s+/;
const commaReg = /,/g; const commaReg = /,/g;
export default function (lengthList) { export default function(lengthList) {
if (typeof lengthList === 'string') { if (typeof lengthList === "string") {
return lengthList.trim().replace(commaReg, ' ').split(spaceReg); return lengthList
} else if (typeof lengthList === 'number') { .trim()
.replace(commaReg, " ")
.split(spaceReg);
} else if (typeof lengthList === "number") {
return [`${lengthList}`]; return [`${lengthList}`];
} else if (lengthList && typeof lengthList.map === 'function') { } else if (lengthList && typeof lengthList.map === "function") {
return lengthList.map(d => `${d}`); return lengthList.map(d => `${d}`);
} else { } else {
return []; return [];

View File

@@ -1,4 +1,4 @@
export default function (opacity) { export default function(opacity) {
const value = +opacity; const value = +opacity;
return (typeof value !== 'number' || isNaN(value)) ? 1 : value; return typeof value !== "number" || isNaN(value) ? 1 : value;
} }

View File

@@ -1,4 +1,6 @@
export default function(polyPoints) {
export default function (polyPoints) { return polyPoints
return polyPoints.replace(/[^e]-/, ' -').split(/(?:\s+|\s*,\s*)/g).join(' '); .replace(/[^e]-/, " -")
.split(/(?:\s+|\s*,\s*)/g)
.join(" ");
} }

View File

@@ -1,16 +1,17 @@
import extractFill from './extractFill'; import extractFill from "./extractFill";
import extractStroke from './extractStroke'; import extractStroke from "./extractStroke";
import extractTransform, {props2transform} from './extractTransform'; import extractTransform, { props2transform } from "./extractTransform";
import extractClipPath from './extractClipPath'; import extractClipPath from "./extractClipPath";
import extractResponder from './extractResponder'; import extractResponder from "./extractResponder";
import extractOpacity from './extractOpacity'; import extractOpacity from "./extractOpacity";
export default function(props, ref) { export default function(props, ref) {
const styleProperties = []; const styleProperties = [];
const extractedProps = { const extractedProps = {
opacity: extractOpacity(props.opacity), opacity: extractOpacity(props.opacity),
propList: styleProperties propList: styleProperties,
onLayout: props.onLayout
}; };
if (props.id) { if (props.id) {

View File

@@ -1,13 +1,13 @@
import {responderProps, touchableProps} from '../props'; import { responderProps, touchableProps } from "../props";
import _ from 'lodash'; import _ from "lodash";
export default function (props, ref) { export default function(props, ref) {
const extractedProps = {}; const extractedProps = {};
_.forEach(responderProps, (v, key) => { _.forEach(responderProps, (v, key) => {
const value = props[key]; const value = props[key];
if (props[key]) { if (props[key]) {
if (!extractedProps.responsible && key !== 'pointerEvents') { if (!extractedProps.responsible && key !== "pointerEvents") {
extractedProps.responsible = true; extractedProps.responsible = true;
} }
@@ -22,8 +22,10 @@ export default function (props, ref) {
extractedProps.responsible = true; extractedProps.responsible = true;
Object.assign(extractedProps, { Object.assign(extractedProps, {
onStartShouldSetResponder: ref.touchableHandleStartShouldSetResponder, onStartShouldSetResponder:
onResponderTerminationRequest: ref.touchableHandleResponderTerminationRequest, ref.touchableHandleStartShouldSetResponder,
onResponderTerminationRequest:
ref.touchableHandleResponderTerminationRequest,
onResponderGrant: ref.touchableHandleResponderGrant, onResponderGrant: ref.touchableHandleResponderGrant,
onResponderMove: ref.touchableHandleResponderMove, onResponderMove: ref.touchableHandleResponderMove,
onResponderRelease: ref.touchableHandleResponderRelease, onResponderRelease: ref.touchableHandleResponderRelease,

View File

@@ -1,6 +1,6 @@
import extractBrush from './extractBrush'; import extractBrush from "./extractBrush";
import extractOpacity from './extractOpacity'; import extractOpacity from "./extractOpacity";
import {strokeProps} from '../props'; import { strokeProps } from "../props";
import extractLengthList from "./extractLengthList"; import extractLengthList from "./extractLengthList";
const caps = { const caps = {
@@ -18,19 +18,16 @@ const joins = {
const strokeKeys = Object.keys(strokeProps); const strokeKeys = Object.keys(strokeProps);
export default function(props, styleProperties) { export default function(props, styleProperties) {
strokeKeys.forEach((name) => { strokeKeys.forEach(name => {
if (props.hasOwnProperty(name)) { if (props.hasOwnProperty(name)) {
styleProperties.push(name); styleProperties.push(name);
} }
}); });
const {stroke} = props; const { stroke } = props;
let { let { strokeWidth, strokeDasharray } = props;
strokeWidth,
strokeDasharray
} = props;
if (!strokeDasharray || strokeDasharray === 'none') { if (!strokeDasharray || strokeDasharray === "none") {
strokeDasharray = null; strokeDasharray = null;
} else { } else {
// <dasharray> It's a list of comma and/or white space separated <length>s // <dasharray> It's a list of comma and/or white space separated <length>s
@@ -38,12 +35,12 @@ export default function(props, styleProperties) {
// If an odd number of values is provided, then the list of values is repeated // If an odd number of values is provided, then the list of values is repeated
// to yield an even number of values. Thus, 5,3,2 is equivalent to 5,3,2,5,3,2. // to yield an even number of values. Thus, 5,3,2 is equivalent to 5,3,2,5,3,2.
strokeDasharray = extractLengthList(strokeDasharray); strokeDasharray = extractLengthList(strokeDasharray);
if (strokeDasharray && (strokeDasharray.length % 2) === 1) { if (strokeDasharray && strokeDasharray.length % 2 === 1) {
strokeDasharray = strokeDasharray.concat(strokeDasharray); strokeDasharray = strokeDasharray.concat(strokeDasharray);
} }
} }
if (!strokeWidth || typeof strokeWidth !== 'string') { if (!strokeWidth || typeof strokeWidth !== "string") {
strokeWidth = `${strokeWidth || 1}`; strokeWidth = `${strokeWidth || 1}`;
} }
@@ -54,8 +51,7 @@ export default function(props, styleProperties) {
strokeLinejoin: joins[props.strokeLinejoin] || 0, strokeLinejoin: joins[props.strokeLinejoin] || 0,
strokeDasharray: strokeDasharray, strokeDasharray: strokeDasharray,
strokeWidth: strokeWidth, strokeWidth: strokeWidth,
strokeDashoffset: strokeDasharray ? (+props.strokeDashoffset || 0) : null, strokeDashoffset: strokeDasharray ? +props.strokeDashoffset || 0 : null,
strokeMiterlimit: props.strokeMiterlimit || 4, strokeMiterlimit: parseFloat(props.strokeMiterlimit) || 4
}; };
} }

View File

@@ -1,8 +1,8 @@
import _ from 'lodash'; import _ from "lodash";
//noinspection JSUnresolvedVariable //noinspection JSUnresolvedVariable
import React, {Children} from 'react'; import React, { Children } from "react";
import TSpan from '../../elements/TSpan'; import TSpan from "../../elements/TSpan";
import extractLengthList from './extractLengthList'; import extractLengthList from "./extractLengthList";
const fontRegExp = /^\s*((?:(?:normal|bold|italic)\s+)*)(?:(\d+(?:\.\d+)?[ptexm%])*(?:\s*\/.*?)?\s+)?\s*"?([^"]*)/i; const fontRegExp = /^\s*((?:(?:normal|bold|italic)\s+)*)(?:(\d+(?:\.\d+)?[ptexm%])*(?:\s*\/.*?)?\s+)?\s*"?([^"]*)/i;
const fontFamilyPrefix = /^[\s"']*/; const fontFamilyPrefix = /^[\s"']*/;
@@ -15,9 +15,12 @@ function extractSingleFontFamily(fontFamilyString) {
// SVG on the web allows for multiple font-families to be specified. // SVG on the web allows for multiple font-families to be specified.
// For compatibility, we extract the first font-family, hoping // For compatibility, we extract the first font-family, hoping
// we'll get a match. // we'll get a match.
return fontFamilyString ? fontFamilyString.split(commaReg)[0] return fontFamilyString
.replace(fontFamilyPrefix, '') ? fontFamilyString
.replace(fontFamilySuffix, '') : null; .split(commaReg)[0]
.replace(fontFamilyPrefix, "")
.replace(fontFamilySuffix, "")
: null;
} }
function parseFontString(font) { function parseFontString(font) {
@@ -29,16 +32,16 @@ function parseFontString(font) {
return null; return null;
} }
const fontFamily = extractSingleFontFamily(match[3]); const fontFamily = extractSingleFontFamily(match[3]);
const fontSize = match[2] || '12'; const fontSize = match[2] || "12";
const isBold = /bold/.exec(match[1]); const isBold = /bold/.exec(match[1]);
const isItalic = /italic/.exec(match[1]); const isItalic = /italic/.exec(match[1]);
const fontWeight = isBold ? 'bold' : 'normal'; const fontWeight = isBold ? "bold" : "normal";
const fontStyle = isItalic ? 'italic' : 'normal'; const fontStyle = isItalic ? "italic" : "normal";
cachedFontObjectsFromString[font] = { cachedFontObjectsFromString[font] = {
fontSize, fontSize,
fontFamily, fontFamily,
fontWeight, fontWeight,
fontStyle, fontStyle
}; };
return cachedFontObjectsFromString[font]; return cachedFontObjectsFromString[font];
} }
@@ -56,35 +59,34 @@ export function extractFont(props) {
wordSpacing, wordSpacing,
kerning, kerning,
fontVariantLigatures, fontVariantLigatures,
fontFeatureSettings, fontFeatureSettings
} = props;
let {
fontSize,
fontFamily,
font,
} = props; } = props;
let { fontSize, fontFamily, font } = props;
fontFamily = extractSingleFontFamily(fontFamily); fontFamily = extractSingleFontFamily(fontFamily);
fontSize = fontSize ? '' + fontSize : null; fontSize = fontSize ? "" + fontSize : null;
const ownedFont = _.pickBy({ const ownedFont = _.pickBy(
fontData, {
fontStyle, fontData,
fontVariant, fontStyle,
fontWeight, fontVariant,
fontStretch, fontWeight,
fontSize, fontStretch,
fontFamily, fontSize,
textAnchor, fontFamily,
textDecoration, textAnchor,
letterSpacing, textDecoration,
wordSpacing, letterSpacing,
kerning, wordSpacing,
fontVariantLigatures, kerning,
fontFeatureSettings, fontVariantLigatures,
}, prop => !_.isNil(prop)); fontFeatureSettings
},
prop => !_.isNil(prop)
);
if (typeof font === 'string') { if (typeof font === "string") {
font = parseFontString(font); font = parseFontString(font);
} }
@@ -99,12 +101,9 @@ export default function(props, container) {
dy, dy,
alignmentBaseline, alignmentBaseline,
baselineShift, baselineShift,
verticalAlign, verticalAlign
} = props;
let {
rotate,
children
} = props; } = props;
let { rotate, children } = props;
const positionX = extractLengthList(x); const positionX = extractLengthList(x);
const positionY = extractLengthList(y); const positionY = extractLengthList(y);
@@ -113,7 +112,7 @@ export default function(props, container) {
rotate = extractLengthList(rotate); rotate = extractLengthList(rotate);
let content = null; let content = null;
if (typeof children === 'string' || typeof children === 'number') { if (typeof children === "string" || typeof children === "number") {
const childrenString = children.toString(); const childrenString = children.toString();
if (container) { if (container) {
children = <TSpan>{childrenString}</TSpan>; children = <TSpan>{childrenString}</TSpan>;
@@ -123,7 +122,7 @@ export default function(props, container) {
} }
} else if (Children.count(children) > 1 || Array.isArray(children)) { } else if (Children.count(children) > 1 || Array.isArray(children)) {
children = Children.map(children, child => { children = Children.map(children, child => {
if (typeof child === 'string' || typeof child === 'number') { if (typeof child === "string" || typeof child === "number") {
return <TSpan>{child.toString()}</TSpan>; return <TSpan>{child.toString()}</TSpan>;
} else { } else {
return child; return child;
@@ -144,6 +143,6 @@ export default function(props, container) {
deltaY, deltaY,
alignmentBaseline, alignmentBaseline,
baselineShift, baselineShift,
verticalAlign, verticalAlign
}; };
} }

View File

@@ -1,7 +1,7 @@
import Matrix2D from '../Matrix2D'; import Matrix2D from "../Matrix2D";
import _ from 'lodash'; import _ from "lodash";
let pooledMatrix = new Matrix2D(); let pooledMatrix = new Matrix2D();
import peg from 'pegjs'; import peg from "pegjs";
function transformToMatrix(props, transform) { function transformToMatrix(props, transform) {
pooledMatrix.reset(); pooledMatrix.reset();
@@ -172,7 +172,7 @@ wsp
function appendTransform(transform) { function appendTransform(transform) {
if (transform) { if (transform) {
if (typeof transform === 'string') { if (typeof transform === "string") {
try { try {
const [a, c, e, b, d, f] = transformParser.parse(transform); const [a, c, e, b, d, f] = transformParser.parse(transform);
pooledMatrix.append(...[a, b, c, d, e, f]); pooledMatrix.append(...[a, b, c, d, e, f]);
@@ -180,17 +180,17 @@ function appendTransform(transform) {
console.error(e); console.error(e);
} }
} else { } else {
pooledMatrix pooledMatrix.appendTransform(
.appendTransform( transform.x + transform.originX,
transform.x + transform.originX, transform.y + transform.originY,
transform.y + transform.originY, transform.scaleX,
transform.scaleX, transform.scaleY, transform.scaleY,
transform.rotation, transform.rotation,
transform.skewX, transform.skewX,
transform.skewY, transform.skewY,
transform.originX, transform.originX,
transform.originY transform.originY
); );
} }
} }
} }
@@ -204,7 +204,7 @@ function universal2axis(universal, axisX, axisY, defaultValue) {
if (coords.length === 2) { if (coords.length === 2) {
x = +coords[0]; x = +coords[0];
y = +coords[1]; y = +coords[1];
} else if (coords.length === 1) { } else if (coords.length === 1) {
x = y = +coords[0]; x = y = +coords[0];
} }
} else if (_.isNumber(universal)) { } else if (_.isNumber(universal)) {
@@ -225,16 +225,25 @@ function universal2axis(universal, axisX, axisY, defaultValue) {
} }
export function props2transform(props) { export function props2transform(props) {
if (props && (typeof props === 'string')) { if (props && typeof props === "string") {
return props; return props;
} }
let [originX, originY] = universal2axis(props.origin, props.originX, props.originY); let [originX, originY] = universal2axis(
let [scaleX, scaleY] = universal2axis(props.scale, props.scaleX, props.scaleY, 1); props.origin,
props.originX,
props.originY
);
let [scaleX, scaleY] = universal2axis(
props.scale,
props.scaleX,
props.scaleY,
1
);
let [skewX, skewY] = universal2axis(props.skew, props.skewX, props.skewY); let [skewX, skewY] = universal2axis(props.skew, props.skewX, props.skewY);
let [translateX, translateY] = universal2axis( let [translateX, translateY] = universal2axis(
props.translate, props.translate,
_.isNil(props.translateX) ? (props.x || 0) : props.translateX, _.isNil(props.translateX) ? props.x || 0 : props.translateX,
_.isNil(props.translateY) ? (props.y || 0) : props.translateY _.isNil(props.translateY) ? props.y || 0 : props.translateY
); );
return { return {
@@ -250,6 +259,9 @@ export function props2transform(props) {
}; };
} }
export default function (props) { export default function(props) {
return transformToMatrix(props2transform(props), props.transform ? props2transform(props.transform) : null); return transformToMatrix(
props2transform(props),
props.transform ? props2transform(props.transform) : null
);
} }

View File

@@ -5,10 +5,16 @@ const meetOrSliceTypes = {
}; };
const alignEnum = [ const alignEnum = [
'xMinYMin', 'xMidYMin', 'xMaxYMin', "xMinYMin",
'xMinYMid', 'xMidYMid', 'xMaxYMid', "xMidYMin",
'xMinYMax', 'xMidYMax', 'xMaxYMax', "xMaxYMin",
'none' "xMinYMid",
"xMidYMid",
"xMaxYMid",
"xMinYMax",
"xMidYMax",
"xMaxYMax",
"none"
].reduce((prev, name) => { ].reduce((prev, name) => {
prev[name] = name; prev[name] = name;
return prev; return prev;
@@ -16,8 +22,8 @@ const alignEnum = [
const spacesRegExp = /\s+/; const spacesRegExp = /\s+/;
export default function (props) { export default function(props) {
const {viewBox, preserveAspectRatio} = props; const { viewBox, preserveAspectRatio } = props;
if (!viewBox) { if (!viewBox) {
return null; return null;
@@ -25,15 +31,17 @@ export default function (props) {
let params = viewBox.trim().split(spacesRegExp); let params = viewBox.trim().split(spacesRegExp);
if (params.length === 4 && params.every(param => !isNaN(+params))) { if (params.length === 4 && params.every(param => !isNaN(+params))) {
console.warn('Invalid `viewBox` prop:' + viewBox); console.warn("Invalid `viewBox` prop:" + viewBox);
return null; return null;
} }
let modes = preserveAspectRatio ? preserveAspectRatio.trim().split(spacesRegExp) : []; let modes = preserveAspectRatio
? preserveAspectRatio.trim().split(spacesRegExp)
: [];
let meetOrSlice = meetOrSliceTypes[modes[1]] || 0; let meetOrSlice = meetOrSliceTypes[modes[1]] || 0;
let align = alignEnum[modes[0]] || 'xMidYMid'; let align = alignEnum[modes[0]] || "xMidYMid";
return { return {
minX: +params[0], minX: +params[0],
@@ -45,7 +53,4 @@ export default function (props) {
}; };
} }
export { export { meetOrSliceTypes, alignEnum };
meetOrSliceTypes,
alignEnum
};

View File

@@ -1,8 +1,10 @@
let percentReg = /^([+\-]?\d+(?:\.\d+)?(?:[eE][+\-]?\d+)?)(%?)$/; let percentReg = /^([+\-]?\d+(?:\.\d+)?(?:[eE][+\-]?\d+)?)(%?)$/;
export default function (percent) { export default function(percent) {
let matched = percent.match(percentReg); let matched = percent.match(percentReg);
if (!matched) { if (!matched) {
console.warn(`\`${percent}\` is not a valid number or percentage string.`); console.warn(
`\`${percent}\` is not a valid number or percentage string.`
);
return 0; return 0;
} }

View File

@@ -1,8 +1,11 @@
import PropTypes from 'prop-types'; import PropTypes from "prop-types";
import {PanResponder} from 'react-native'; import { PanResponder } from "react-native";
const numberProp = PropTypes.oneOfType([PropTypes.string, PropTypes.number]); const numberProp = PropTypes.oneOfType([PropTypes.string, PropTypes.number]);
const numberArrayProp = PropTypes.oneOfType([PropTypes.arrayOf(numberProp), numberProp]); const numberArrayProp = PropTypes.oneOfType([
PropTypes.arrayOf(numberProp),
numberProp
]);
const touchableProps = { const touchableProps = {
disabled: PropTypes.bool, disabled: PropTypes.bool,
@@ -15,9 +18,13 @@ const touchableProps = {
delayLongPress: PropTypes.number delayLongPress: PropTypes.number
}; };
const responderProps = [ const layoutProps = {
onLayout: PropTypes.func
};
const responderProps = [
...Object.keys(PanResponder.create({}).panHandlers), ...Object.keys(PanResponder.create({}).panHandlers),
'pointerEvents' "pointerEvents"
].reduce((props, name) => { ].reduce((props, name) => {
props[name] = PropTypes.func; props[name] = PropTypes.func;
return props; return props;
@@ -26,11 +33,11 @@ const responderProps = [
const fillProps = { const fillProps = {
fill: PropTypes.string, fill: PropTypes.string,
fillOpacity: numberProp, fillOpacity: numberProp,
fillRule: PropTypes.oneOf(['evenodd', 'nonzero']) fillRule: PropTypes.oneOf(["evenodd", "nonzero"])
}; };
const clipProps = { const clipProps = {
clipRule: PropTypes.oneOf(['evenodd', 'nonzero']), clipRule: PropTypes.oneOf(["evenodd", "nonzero"]),
clipPath: PropTypes.string clipPath: PropTypes.string
}; };
@@ -44,8 +51,8 @@ const strokeProps = {
strokeOpacity: numberProp, strokeOpacity: numberProp,
strokeDasharray: numberArrayProp, strokeDasharray: numberArrayProp,
strokeDashoffset: numberProp, strokeDashoffset: numberProp,
strokeLinecap: PropTypes.oneOf(['butt', 'square', 'round']), strokeLinecap: PropTypes.oneOf(["butt", "square", "round"]),
strokeLinejoin: PropTypes.oneOf(['miter', 'bevel', 'round']), strokeLinejoin: PropTypes.oneOf(["miter", "bevel", "round"]),
strokeMiterlimit: numberProp strokeMiterlimit: numberProp
}; };
@@ -76,24 +83,51 @@ const pathProps = {
...transformProps, ...transformProps,
...responderProps, ...responderProps,
...touchableProps, ...touchableProps,
...layoutProps,
...definationProps ...definationProps
}; };
// normal | italic | oblique | inherit // normal | italic | oblique | inherit
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-style // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-style
const fontStyle = PropTypes.oneOf(['normal', 'italic', 'oblique']); const fontStyle = PropTypes.oneOf(["normal", "italic", "oblique"]);
// normal | small-caps | inherit // normal | small-caps | inherit
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-variant // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-variant
const fontVariant = PropTypes.oneOf(['normal', 'small-caps']); const fontVariant = PropTypes.oneOf(["normal", "small-caps"]);
// normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 // normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-weight // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-weight
const fontWeight = PropTypes.oneOf(['normal', 'bold', 'bolder', 'lighter', '100', '200', '300', '400', '500', '600', '700', '800', '900']); const fontWeight = PropTypes.oneOf([
"normal",
"bold",
"bolder",
"lighter",
"100",
"200",
"300",
"400",
"500",
"600",
"700",
"800",
"900"
]);
// normal | wider | narrower | ultra-condensed | extra-condensed | condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded | inherit // normal | wider | narrower | ultra-condensed | extra-condensed | condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded | inherit
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-stretch // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-stretch
const fontStretch = PropTypes.oneOf(['normal', 'wider', 'narrower', 'ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed', 'semi-expanded', 'expanded', 'extra-expanded', 'ultra-expanded']); const fontStretch = PropTypes.oneOf([
"normal",
"wider",
"narrower",
"ultra-condensed",
"extra-condensed",
"condensed",
"semi-condensed",
"semi-expanded",
"expanded",
"extra-expanded",
"ultra-expanded"
]);
// <absolute-size> | <relative-size> | <length> | <percentage> | inherit // <absolute-size> | <relative-size> | <length> | <percentage> | inherit
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-size // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-size
@@ -125,11 +159,17 @@ const font = PropTypes.object;
// start | middle | end | inherit // start | middle | end | inherit
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor
const textAnchor = PropTypes.oneOf(['start', 'middle', 'end']); const textAnchor = PropTypes.oneOf(["start", "middle", "end"]);
// none | underline | overline | line-through | blink | inherit // none | underline | overline | line-through | blink | inherit
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-decoration // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-decoration
const textDecoration = PropTypes.oneOf(['none', 'underline', 'overline', 'line-through', 'blink']); const textDecoration = PropTypes.oneOf([
"none",
"underline",
"overline",
"line-through",
"blink"
]);
// normal | <length> | inherit // normal | <length> | inherit
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/letter-spacing // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/letter-spacing
@@ -164,7 +204,7 @@ Value: normal | none | [ <common-lig-values> || <discretionary-lig-values> || <h
https://developer.mozilla.org/en/docs/Web/CSS/font-variant-ligatures https://developer.mozilla.org/en/docs/Web/CSS/font-variant-ligatures
https://www.w3.org/TR/css-fonts-3/#font-variant-ligatures-prop https://www.w3.org/TR/css-fonts-3/#font-variant-ligatures-prop
*/ */
const fontVariantLigatures = PropTypes.oneOf(['normal', 'none']); const fontVariantLigatures = PropTypes.oneOf(["normal", "none"]);
const fontProps = { const fontProps = {
fontStyle, fontStyle,
@@ -188,7 +228,7 @@ const fontProps = {
https://svgwg.org/svg2-draft/text.html#TextElementLengthAdjustAttribute https://svgwg.org/svg2-draft/text.html#TextElementLengthAdjustAttribute
https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/lengthAdjust https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/lengthAdjust
*/ */
const lengthAdjust = PropTypes.oneOf(['spacing', 'spacingAndGlyphs']); const lengthAdjust = PropTypes.oneOf(["spacing", "spacingAndGlyphs"]);
/* /*
Name Value Initial value Animatable Name Value Initial value Animatable
@@ -239,7 +279,24 @@ const verticalAlign = numberProp;
https://svgwg.org/svg2-draft/text.html#AlignmentBaselineProperty https://svgwg.org/svg2-draft/text.html#AlignmentBaselineProperty
https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/alignment-baseline https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/alignment-baseline
*/ */
const alignmentBaseline = PropTypes.oneOf(['baseline', 'text-bottom', 'alphabetic', 'ideographic', 'middle', 'central', 'mathematical', 'text-top', 'bottom', 'center', 'top', 'text-before-edge', 'text-after-edge', 'before-edge', 'after-edge', 'hanging']); const alignmentBaseline = PropTypes.oneOf([
"baseline",
"text-bottom",
"alphabetic",
"ideographic",
"middle",
"central",
"mathematical",
"text-top",
"bottom",
"center",
"top",
"text-before-edge",
"text-after-edge",
"before-edge",
"after-edge",
"hanging"
]);
/* /*
2.2.2. Alignment Shift: baseline-shift longhand 2.2.2. Alignment Shift: baseline-shift longhand
@@ -259,7 +316,11 @@ const alignmentBaseline = PropTypes.oneOf(['baseline', 'text-bottom', 'alphabeti
https://www.w3.org/TR/css-inline-3/#propdef-baseline-shift https://www.w3.org/TR/css-inline-3/#propdef-baseline-shift
*/ */
const baselineShift = PropTypes.oneOfType([PropTypes.oneOf(['sub', 'super', 'baseline']), PropTypes.arrayOf(numberProp), numberProp]); const baselineShift = PropTypes.oneOfType([
PropTypes.oneOf(["sub", "super", "baseline"]),
PropTypes.arrayOf(numberProp),
numberProp
]);
/* /*
6.12. Low-level font feature settings control: the font-feature-settings property 6.12. Low-level font feature settings control: the font-feature-settings property
@@ -370,14 +431,14 @@ const textSpecificProps = {
lengthAdjust, lengthAdjust,
textLength, textLength,
fontData: PropTypes.object, fontData: PropTypes.object,
fontFeatureSettings, fontFeatureSettings
}; };
// https://svgwg.org/svg2-draft/text.html#TSpanAttributes // https://svgwg.org/svg2-draft/text.html#TSpanAttributes
const textProps = { const textProps = {
...textSpecificProps, ...textSpecificProps,
dx: numberArrayProp, dx: numberArrayProp,
dy: numberArrayProp, dy: numberArrayProp
}; };
/* /*
@@ -391,7 +452,7 @@ const textProps = {
yes yes
https://svgwg.org/svg2-draft/text.html#TextPathElementSideAttribute https://svgwg.org/svg2-draft/text.html#TextPathElementSideAttribute
*/ */
const side = PropTypes.oneOf(['left', 'right']); const side = PropTypes.oneOf(["left", "right"]);
/* /*
Name Name
@@ -419,7 +480,7 @@ const startOffset = numberProp;
https://svgwg.org/svg2-draft/text.html#TextPathElementMethodAttribute https://svgwg.org/svg2-draft/text.html#TextPathElementMethodAttribute
https://developer.mozilla.org/en/docs/Web/SVG/Element/textPath https://developer.mozilla.org/en/docs/Web/SVG/Element/textPath
*/ */
const method = PropTypes.oneOf(['align', 'stretch']); const method = PropTypes.oneOf(["align", "stretch"]);
/* /*
Name Name
@@ -433,7 +494,7 @@ const method = PropTypes.oneOf(['align', 'stretch']);
https://svgwg.org/svg2-draft/text.html#TextPathElementSpacingAttribute https://svgwg.org/svg2-draft/text.html#TextPathElementSpacingAttribute
https://developer.mozilla.org/en/docs/Web/SVG/Element/textPath https://developer.mozilla.org/en/docs/Web/SVG/Element/textPath
*/ */
const spacing = PropTypes.oneOf(['auto', 'exact']); const spacing = PropTypes.oneOf(["auto", "exact"]);
/* /*
Name Name
@@ -445,7 +506,7 @@ const spacing = PropTypes.oneOf(['auto', 'exact']);
Animatable Animatable
yes yes
*/ */
const midLine = PropTypes.oneOf(['sharp', 'smooth']); const midLine = PropTypes.oneOf(["sharp", "smooth"]);
// https://svgwg.org/svg2-draft/text.html#TextPathAttributes // https://svgwg.org/svg2-draft/text.html#TextPathAttributes
// https://developer.mozilla.org/en/docs/Web/SVG/Element/textPath // https://developer.mozilla.org/en/docs/Web/SVG/Element/textPath
@@ -456,7 +517,7 @@ const textPathProps = {
method, method,
spacing, spacing,
side, side,
midLine, midLine
}; };
export { export {

View File

@@ -1,5 +1,5 @@
{ {
"version": "6.2.2", "version": "6.5.2",
"name": "react-native-svg", "name": "react-native-svg",
"description": "SVG library for react-native", "description": "SVG library for react-native",
"repository": { "repository": {