mirror of
https://github.com/zoriya/react-native-svg.git
synced 2026-06-02 06:35:04 +00:00
complete elements percentage props support
Support Rect, Circle, Line, Ellipse percentage props And support percentage props for gradients
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ public class RNSVGRenderableViewManager extends ViewManager<View, ReactShadowNod
|
||||
/* package */ static final String CLASS_GROUP = "RNSVGGroup";
|
||||
/* package */ static final String CLASS_SVG = "RNSVGPath";
|
||||
/* package */ static final String CLASS_TEXT = "RNSVGText";
|
||||
/* package */ static final String CLASS_SHAPE = "RNSVGShape";
|
||||
|
||||
private final String mClassName;
|
||||
|
||||
@@ -42,6 +43,10 @@ public class RNSVGRenderableViewManager extends ViewManager<View, ReactShadowNod
|
||||
return new RNSVGRenderableViewManager(CLASS_TEXT);
|
||||
}
|
||||
|
||||
public static RNSVGRenderableViewManager createRNSVGShapeViewManager() {
|
||||
return new RNSVGRenderableViewManager(CLASS_SHAPE);
|
||||
}
|
||||
|
||||
private RNSVGRenderableViewManager(String className) {
|
||||
mClassName = className;
|
||||
}
|
||||
@@ -57,6 +62,8 @@ public class RNSVGRenderableViewManager extends ViewManager<View, ReactShadowNod
|
||||
return new RNSVGGroupShadowNode();
|
||||
} else if (mClassName == CLASS_SVG) {
|
||||
return new RNSVGPathShadowNode();
|
||||
} else if (mClassName == CLASS_SHAPE) {
|
||||
return new RNSVGShapeShadowNode();
|
||||
} else if (mClassName == CLASS_TEXT) {
|
||||
return new RNSVGTextShadowNode();
|
||||
} else {
|
||||
@@ -70,9 +77,11 @@ public class RNSVGRenderableViewManager extends ViewManager<View, ReactShadowNod
|
||||
return RNSVGGroupShadowNode.class;
|
||||
} else if (mClassName == CLASS_SVG) {
|
||||
return RNSVGPathShadowNode.class;
|
||||
} else if (mClassName == CLASS_SHAPE) {
|
||||
return RNSVGShapeShadowNode.class;
|
||||
} else if (mClassName == CLASS_TEXT) {
|
||||
return RNSVGTextShadowNode.class;
|
||||
} else {
|
||||
}else {
|
||||
throw new IllegalStateException("Unexpected type " + mClassName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Horcrux.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the MIT-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
|
||||
package com.horcrux.svg;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Shadow node for virtual RNSVGPath view
|
||||
*/
|
||||
public class RNSVGShapeShadowNode extends RNSVGPathShadowNode {
|
||||
|
||||
protected ReadableMap mShape;
|
||||
|
||||
@ReactProp(name = "shape")
|
||||
public void setShape(@Nullable ReadableMap shape) {
|
||||
mShape = shape;
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas, Paint paint, float opacity) {
|
||||
|
||||
if (mShape != null) {
|
||||
int type = mShape.getInt("type");
|
||||
Rect box = canvas.getClipBounds();
|
||||
float height = box.height();
|
||||
float width = box.width();
|
||||
mPath = new Path();
|
||||
|
||||
switch (type) {
|
||||
case 0: {
|
||||
// draw circle
|
||||
// TODO:
|
||||
float cx = getActualProp("cx", width);
|
||||
float cy = getActualProp("cy", height);
|
||||
float r = getActualProp("r", width);
|
||||
mPath.addCircle(cx, cy, r, Path.Direction.CW);
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
// draw ellipse
|
||||
float cx = getActualProp("cx", width);
|
||||
float cy = getActualProp("cy", height);
|
||||
float rx = getActualProp("rx", width);
|
||||
float ry = getActualProp("ry", height);
|
||||
RectF oval = new RectF(cx - rx, cy - ry, cx + rx, cy + ry);
|
||||
mPath.addOval(oval, Path.Direction.CW);
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// draw line
|
||||
float x1 = getActualProp("x1", width);
|
||||
float y1 = getActualProp("y1", height);
|
||||
float x2 = getActualProp("x2", width);
|
||||
float y2 = getActualProp("y2", height);
|
||||
mPath.moveTo(x1, y1);
|
||||
mPath.lineTo(x2, y2);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
// draw rect
|
||||
float x = getActualProp("x", width);
|
||||
float y = getActualProp("y", height);
|
||||
float w = getActualProp("width", width);
|
||||
float h = getActualProp("height", height);
|
||||
float rx = getActualProp("rx", width);
|
||||
float ry = getActualProp("ry", height);
|
||||
|
||||
if (rx != 0 || ry != 0) {
|
||||
if (rx == 0) {
|
||||
rx = ry;
|
||||
} else if (ry == 0) {
|
||||
ry = rx;
|
||||
}
|
||||
|
||||
if (rx > 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -30,6 +30,7 @@ public class RNSvgPackage implements ReactPackage {
|
||||
RNSVGRenderableViewManager.createRNSVGGroupViewManager(),
|
||||
RNSVGRenderableViewManager.createRNSVGPathViewManager(),
|
||||
RNSVGRenderableViewManager.createRNSVGTextViewManager(),
|
||||
RNSVGRenderableViewManager.createRNSVGShapeViewManager(),
|
||||
new RNSVGSvgViewManager());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user