diff --git a/Example/examples/Text.js b/Example/examples/Text.js
index 485e60ba..cb17ee13 100644
--- a/Example/examples/Text.js
+++ b/Example/examples/Text.js
@@ -60,6 +60,34 @@ class TextRotate extends Component{
}
}
+// TODO: iOS not support text stroke with pattern
+class TextStroke extends Component{
+ static title = 'Stroke the text';
+ render() {
+ return ;
+ }
+}
+
class TextFill extends Component{
static title = 'Fill the text with LinearGradient';
render() {
@@ -68,8 +96,8 @@ class TextFill extends Component{
width="200"
>
-
-
+
+
@@ -87,34 +115,6 @@ class TextFill extends Component{
}
}
-// TODO: iOS not support text stroke with pattern
-class TextStroke extends Component{
- static title = 'Stroke the text';
- render() {
- return ;
- }
-}
-
class TextPath extends Component{
static title = 'Transform the text';
@@ -154,7 +154,13 @@ const icon = ;
-const samples = [TextExample, TextRotate, TextStroke, TextFill, TextPath];
+const samples = [
+ TextExample,
+ TextRotate,
+ TextStroke,
+ TextFill,
+ TextPath
+];
export {
icon,
diff --git a/README.md b/README.md
index bcd47915..a4b2ae27 100644
--- a/README.md
+++ b/README.md
@@ -562,9 +562,8 @@ npm install
4. [morph animations](https://github.com/gorangajic/react-svg-morph)
5. fix propTypes
6. more Text features support
-7. support percent props
-8. Pattern element
-9. Image element
+7. Pattern element
+8. Image element
#### Thanks:
diff --git a/android/src/main/java/com/horcrux/svg/PropHelper.java b/android/src/main/java/com/horcrux/svg/PropHelper.java
index df8b745e..d3405101 100644
--- a/android/src/main/java/com/horcrux/svg/PropHelper.java
+++ b/android/src/main/java/com/horcrux/svg/PropHelper.java
@@ -9,10 +9,15 @@
package com.horcrux.svg;
+import android.util.Log;
+
import javax.annotation.Nullable;
import com.facebook.react.bridge.ReadableArray;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
/**
* Contains static helper methods for accessing props.
*/
@@ -51,4 +56,20 @@ import com.facebook.react.bridge.ReadableArray;
return value.size();
}
+ /**
+ * Converts percentage string into actual based on a relative number
+ *
+ * @param percentage percentage string
+ * @param relative relative number
+ * @return actual float based on relative number
+ */
+ /*package*/ static float fromPercentageToFloat(String percentage, float relative, float offset, float scale) {
+ Pattern pattern = Pattern.compile("^(\\-?\\d+(?:\\.\\d+)?)%$");
+ Matcher matched = pattern.matcher(percentage);
+ if (matched.matches()) {
+ return Float.valueOf(matched.group(1)) / 100 * relative + offset;
+ } else {
+ return Float.valueOf(percentage) * scale;
+ }
+ }
}
diff --git a/android/src/main/java/com/horcrux/svg/RNSVGPathShadowNode.java b/android/src/main/java/com/horcrux/svg/RNSVGPathShadowNode.java
index 697865a9..0bcdfc2b 100644
--- a/android/src/main/java/com/horcrux/svg/RNSVGPathShadowNode.java
+++ b/android/src/main/java/com/horcrux/svg/RNSVGPathShadowNode.java
@@ -17,6 +17,7 @@ import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.RadialGradient;
+import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Color;
@@ -48,9 +49,9 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
private static final int FILL_RULE_EVENODD = 0;
private static final int FILL_RULE_NONZERO = 1;
- protected @Nullable Path mPath;
- private @Nullable float[] mStrokeColor;
- private @Nullable float[] mFillColor;
+ protected Path mPath;
+ private @Nullable ReadableArray mStrokeColor;
+ private @Nullable ReadableArray mFillColor;
private @Nullable float[] mStrokeDasharray;
private float mStrokeWidth = 1;
private float mStrokeDashoffset = 0;
@@ -60,6 +61,7 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
private boolean mFillRuleSet;
private boolean mPathSet;
private float[] mShapePath;
+ protected RectF mContentBoundingBox;
private Point mPaint;
@ReactProp(name = "d")
@@ -72,7 +74,7 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
@ReactProp(name = "fill")
public void setFill(@Nullable ReadableArray fillColors) {
- mFillColor = PropHelper.toFloatArray(fillColors);
+ mFillColor = fillColors;
markUpdated();
}
@@ -87,7 +89,7 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
@ReactProp(name = "stroke")
public void setStroke(@Nullable ReadableArray strokeColors) {
- mStrokeColor = PropHelper.toFloatArray(strokeColors);
+ mStrokeColor = strokeColors;
markUpdated();
}
@@ -139,10 +141,10 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
clip(canvas, paint);
- if (setupFillPaint(paint, opacity)) {
+ if (setupFillPaint(paint, opacity, null)) {
canvas.drawPath(mPath, paint);
}
- if (setupStrokePaint(paint, opacity)) {
+ if (setupStrokePaint(paint, opacity, null)) {
canvas.drawPath(mPath, paint);
}
@@ -168,21 +170,24 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
}
mPath = super.createPath(mShapePath, path);
+ RectF box = new RectF();
+ mPath.computeBounds(box, true);
+ mContentBoundingBox = box;
}
}
- /*
+ /**
* sorting stops and stopsColors from array
*/
- private static void parseGradientStops(float[] value, int stopsCount, float[] stops, int[] stopsColors, int startColorsPosition) {
- int startStops = value.length - stopsCount;
+ private static void parseGradientStops(ReadableArray value, int stopsCount, float[] stops, int[] stopsColors, int startColorsPosition) {
+ int startStops = value.size() - stopsCount;
for (int i = 0; i < stopsCount; i++) {
- stops[i] = value[startStops + i];
+ stops[i] = (float)value.getDouble(startStops + i);
stopsColors[i] = Color.argb(
- (int) (value[startColorsPosition + i * 4 + 3] * 255),
- (int) (value[startColorsPosition + i * 4] * 255),
- (int) (value[startColorsPosition + i * 4 + 1] * 255),
- (int) (value[startColorsPosition + i * 4 + 2] * 255));
+ (int) (value.getDouble(startColorsPosition + i * 4 + 3) * 255),
+ (int) (value.getDouble(startColorsPosition + i * 4) * 255),
+ (int) (value.getDouble(startColorsPosition + i * 4 + 1) * 255),
+ (int) (value.getDouble(startColorsPosition + i * 4 + 2) * 255));
}
}
@@ -192,12 +197,12 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
* Sets up {@link #mPaint} according to the props set on a shadow view. Returns {@code true}
* if the fill should be drawn, {@code false} if not.
*/
- protected boolean setupFillPaint(Paint paint, float opacity) {
- if (mFillColor != null && mFillColor.length > 0) {
+ protected boolean setupFillPaint(Paint paint, float opacity, @Nullable RectF box) {
+ if (mFillColor != null && mFillColor.size() > 0) {
paint.reset();
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.FILL);
- setupPaint(paint, opacity, mFillColor);
+ setupPaint(paint, opacity, mFillColor, box);
return true;
}
return false;
@@ -207,8 +212,8 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
* Sets up {@link #mPaint} according to the props set on a shadow view. Returns {@code true}
* if the stroke should be drawn, {@code false} if not.
*/
- protected boolean setupStrokePaint(Paint paint, float opacity) {
- if (mStrokeWidth == 0 || mStrokeColor == null || mStrokeColor.length == 0) {
+ protected boolean setupStrokePaint(Paint paint, float opacity, @Nullable RectF box) {
+ if (mStrokeWidth == 0 || mStrokeColor == null || mStrokeColor.size() == 0) {
return false;
}
paint.reset();
@@ -244,8 +249,7 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
}
paint.setStrokeWidth(mStrokeWidth * mScale);
-
- setupPaint(paint, opacity, mStrokeColor);
+ setupPaint(paint, opacity, mStrokeColor, box);
if (mStrokeDasharray != null && mStrokeDasharray.length > 0) {
paint.setPathEffect(new DashPathEffect(mStrokeDasharray, mStrokeDashoffset));
@@ -255,67 +259,73 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
}
- private void setupPaint(Paint paint, float opacity, float[] colors) {
- int stopsCount;
- int [] stopsColors;
- float [] stops;
+ private void setupPaint(Paint paint, float opacity, ReadableArray colors, @Nullable RectF box) {
+ int colorType = colors.getInt(0);
+ if (colorType == 0) {
+ // solid color
+ paint.setARGB(
+ (int) (colors.size() > 4 ? colors.getDouble(4) * opacity * 255 : opacity * 255),
+ (int) (colors.getDouble(1) * 255),
+ (int) (colors.getDouble(2) * 255),
+ (int) (colors.getDouble(3) * 255));
+ } else if (colorType == 1 || colorType == 2) {
+ if (box == null) {
+ box = mContentBoundingBox;
+ }
- int colorType = (int) colors[0];
- switch (colorType) {
- case 0:
- paint.setARGB(
- (int) (colors.length > 4 ? colors[4] * opacity * 255 : opacity * 255),
- (int) (colors[1] * 255),
- (int) (colors[2] * 255),
- (int) (colors[3] * 255));
- break;
- case 1:
- stopsCount = (colors.length - 5) / 5;
- stopsColors = new int [stopsCount];
- stops = new float[stopsCount];
+ int startColorsPosition = colorType == 1 ? 5 : 7;
- parseGradientStops(colors, stopsCount, stops, stopsColors, 5);
+ int stopsCount = (colors.size() - startColorsPosition) / 5;
+ int [] stopsColors = new int [stopsCount];
+ float [] stops = new float[stopsCount];
+ float height = box.height();
+ float width = box.width();
+ float midX = box.centerX();
+ float midY = box.centerY();
+ float offsetX = (midX - width / 2);
+ float offsetY = (midY - height / 2);
+
+ parseGradientStops(colors, stopsCount, stops, stopsColors, startColorsPosition);
+
+ if (colorType == 1) {
+ float x1 = PropHelper.fromPercentageToFloat(colors.getString(1), width, offsetX, mScale);
+ float y1 = PropHelper.fromPercentageToFloat(colors.getString(2), height, offsetY, mScale);
+ float x2 = PropHelper.fromPercentageToFloat(colors.getString(3), width, offsetX, mScale);
+ float y2 = PropHelper.fromPercentageToFloat(colors.getString(4), height, offsetY, mScale);
paint.setShader(
new LinearGradient(
- colors[1] * mScale,
- colors[2] * mScale,
- colors[3] * mScale,
- colors[4] * mScale,
+ x1,
+ y1,
+ x2,
+ y2,
stopsColors,
stops,
Shader.TileMode.CLAMP));
- break;
- case 2:
- stopsCount = (colors.length - 7) / 5;
- stopsColors = new int [stopsCount];
- stops = new float[stopsCount];
- parseGradientStops(colors, stopsCount, stops, stopsColors, 7);
-
- // TODO: support focus
- float focusX = colors[1];
- float focusY = colors[2];
-
- float radius = colors[3];
- float radiusRatio = colors[4] / radius;
+ } else {
+ float rx = PropHelper.fromPercentageToFloat(colors.getString(3), width, 0f, mScale);
+ float ry = PropHelper.fromPercentageToFloat(colors.getString(4), height, 0f, mScale);
+ float cx = PropHelper.fromPercentageToFloat(colors.getString(5), width, offsetX, mScale);
+ float cy = PropHelper.fromPercentageToFloat(colors.getString(6), height, offsetY, mScale) / (ry / rx);
+ // TODO: do not support focus point.
+ float fx = PropHelper.fromPercentageToFloat(colors.getString(1), width, offsetX, mScale);
+ float fy = PropHelper.fromPercentageToFloat(colors.getString(2), height, offsetY, mScale) / (ry / rx);
Shader radialGradient = new RadialGradient(
- colors[5] * mScale,
- colors[6] * mScale / radiusRatio,
- radius * mScale,
+ cx,
+ cy,
+ rx,
stopsColors,
stops,
Shader.TileMode.CLAMP
);
Matrix radialMatrix = new Matrix();
-
- // seems like a bug here?
- radialMatrix.preScale(1f, radiusRatio);
+ radialMatrix.preScale(1f, ry / rx);
radialGradient.setLocalMatrix(radialMatrix);
paint.setShader(radialGradient);
- break;
- default:
- // TODO: Support pattern.
- FLog.w(ReactConstants.TAG, "RNSVG: Color type " + colorType + " not supported!");
+ }
+ } else {
+ // TODO: Support pattern.
+ FLog.w(ReactConstants.TAG, "RNSVG: Color type " + colorType + " not supported!");
}
}
}
diff --git a/android/src/main/java/com/horcrux/svg/RNSVGRenderableViewManager.java b/android/src/main/java/com/horcrux/svg/RNSVGRenderableViewManager.java
index cbcdb1ab..3b2f5672 100644
--- a/android/src/main/java/com/horcrux/svg/RNSVGRenderableViewManager.java
+++ b/android/src/main/java/com/horcrux/svg/RNSVGRenderableViewManager.java
@@ -27,6 +27,7 @@ public class RNSVGRenderableViewManager extends ViewManager w / 2) {
+ rx = w / 2;
+ }
+
+ if (ry > h / 2) {
+ ry = h / 2;
+ }
+ mPath.addRoundRect(new RectF(x, y, x + w, y + h), rx, ry, Path.Direction.CW);
+ } else {
+ mPath.addRect(x, y, x + w, y + h, Path.Direction.CW);
+ }
+ break;
+ }
+ default:
+ FLog.e(ReactConstants.TAG, "RNSVG: Invalid Shape type " + type + " at " + mShape);
+ }
+
+ RectF shapeBox = new RectF();
+ mPath.computeBounds(shapeBox, true);
+ mContentBoundingBox = shapeBox;
+ super.draw(canvas, paint, opacity);
+ }
+ }
+
+ private float getActualProp(String name, float relative) {
+ if (mShape.hasKey(name)) {
+ ReadableMap value = mShape.getMap(name);
+
+ if (value.getBoolean("percentage")) {
+ return (float)value.getDouble("value") * relative * mScale;
+ } else {
+ return (float)value.getDouble("value") * mScale;
+ }
+ } else {
+ return 0f;
+ }
+ }
+}
diff --git a/android/src/main/java/com/horcrux/svg/RNSVGTextShadowNode.java b/android/src/main/java/com/horcrux/svg/RNSVGTextShadowNode.java
index d7f16218..c7127914 100644
--- a/android/src/main/java/com/horcrux/svg/RNSVGTextShadowNode.java
+++ b/android/src/main/java/com/horcrux/svg/RNSVGTextShadowNode.java
@@ -14,8 +14,11 @@ import javax.annotation.Nullable;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
import android.graphics.Typeface;
import android.text.TextUtils;
+import android.util.Log;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.ReadableArray;
@@ -89,7 +92,11 @@ public class RNSVGTextShadowNode extends RNSVGPathShadowNode {
lines[i] = linesProp.getString(i);
}
String text = TextUtils.join("\n", lines);
- if (setupStrokePaint(paint, opacity)) {
+
+ Rect bound = new Rect();
+ paint.getTextBounds(text, 0, text.length(), bound);
+ RectF box = new RectF(bound);
+ if (setupStrokePaint(paint, opacity, box)) {
applyTextPropertiesToPaint(paint);
if (mPath == null) {
canvas.drawText(text, 0, -paint.ascent(), paint);
@@ -97,7 +104,7 @@ public class RNSVGTextShadowNode extends RNSVGPathShadowNode {
canvas.drawTextOnPath(text, mPath, 0, 0, paint);
}
}
- if (setupFillPaint(paint, opacity)) {
+ if (setupFillPaint(paint, opacity, box)) {
applyTextPropertiesToPaint(paint);
if (mPath == null) {
canvas.drawText(text, 0, -paint.ascent(), paint);
diff --git a/android/src/main/java/com/horcrux/svg/RNSvgPackage.java b/android/src/main/java/com/horcrux/svg/RNSvgPackage.java
index 340b30e2..0763b9d8 100644
--- a/android/src/main/java/com/horcrux/svg/RNSvgPackage.java
+++ b/android/src/main/java/com/horcrux/svg/RNSvgPackage.java
@@ -30,6 +30,7 @@ public class RNSvgPackage implements ReactPackage {
RNSVGRenderableViewManager.createRNSVGGroupViewManager(),
RNSVGRenderableViewManager.createRNSVGPathViewManager(),
RNSVGRenderableViewManager.createRNSVGTextViewManager(),
+ RNSVGRenderableViewManager.createRNSVGShapeViewManager(),
new RNSVGSvgViewManager());
}
diff --git a/ios/Brushes/RNSVGRadialGradient.m b/ios/Brushes/RNSVGRadialGradient.m
index 6ae6f505..bda357a7 100644
--- a/ios/Brushes/RNSVGRadialGradient.m
+++ b/ios/Brushes/RNSVGRadialGradient.m
@@ -63,9 +63,9 @@
CGFloat rx = [self getActualProp:2 relative:width offset:0];
CGFloat ry = [self getActualProp:3 relative:height offset:0];
CGFloat fx = [self getActualProp:0 relative:width offset:offsetX];
- CGFloat fy = [self getActualProp:1 relative:height offset:offsetY] / (ry / rx); // fx == fy
+ CGFloat fy = [self getActualProp:1 relative:height offset:offsetY] / (ry / rx);
CGFloat cx = [self getActualProp:4 relative:width offset:offsetX];
- CGFloat cy = [self getActualProp:5 relative:height offset:offsetY] / (ry / rx); // rx == ry
+ CGFloat cy = [self getActualProp:5 relative:height offset:offsetY] / (ry / rx);
CGAffineTransform transform = CGAffineTransformMakeScale(1, ry / rx);
CGContextConcatCTM(context, transform);
diff --git a/ios/RNSVGShape.m b/ios/RNSVGShape.m
index acd595a8..2b30bff5 100644
--- a/ios/RNSVGShape.m
+++ b/ios/RNSVGShape.m
@@ -58,7 +58,6 @@
case 3:
{
// draw rect
- CGPathMoveToPoint(path, NULL, 0, 0);
CGFloat x = [self getActualProp:@"x" relative:width];
CGFloat y = [self getActualProp:@"y" relative:height];
CGFloat w = [self getActualProp:@"width" relative:width];
diff --git a/lib/stopsOpacity.js b/lib/stopsOpacity.js
index ebf9bcd0..11d9a862 100644
--- a/lib/stopsOpacity.js
+++ b/lib/stopsOpacity.js
@@ -1,7 +1,7 @@
import _ from 'lodash';
export default function (stops, opacity) {
return _.reduce(stops, (ret, color, key) => {
- ret[key] = color.alpha(opacity).rgbaString();
+ ret[key] = color.alpha(color.alpha() * opacity).rgbaString();
return ret;
}, {});
}