mirror of
https://github.com/zoriya/react-native-svg.git
synced 2025-12-20 05:55:10 +00:00
add some android support
add path for Text add stroke pattern add text dashed and fix dasharray bug
This commit is contained in:
@@ -32,9 +32,9 @@ class StrokeLinecap extends Component{
|
|||||||
render() {
|
render() {
|
||||||
return <Svg height="80" width="225">
|
return <Svg height="80" width="225">
|
||||||
<G fill="none" stroke="black">
|
<G fill="none" stroke="black">
|
||||||
<Path strokeLinecap="butt" strokeWidth="2" d="M5 20 l215 0" />
|
<Path strokeLinecap="butt" strokeWidth="8" d="M5 20 l215 0" />
|
||||||
<Path strokeLinecap="round" strokeWidth="4" d="M5 40 l215 0" />
|
<Path strokeLinecap="round" strokeWidth="8" d="M5 40 l215 0" />
|
||||||
<Path strokeLinecap="square" strokeWidth="6" d="M5 60 l215 0" />
|
<Path strokeLinecap="square" strokeWidth="8" d="M5 60 l215 0" />
|
||||||
</G>
|
</G>
|
||||||
</Svg>;
|
</Svg>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,7 +131,10 @@ class TextPath extends Component{
|
|||||||
C 20 10 30 0 40 10
|
C 20 10 30 0 40 10
|
||||||
C 50 20 60 30 70 20
|
C 50 20 60 30 70 20
|
||||||
C 80 10 90 10 90 10
|
C 80 10 90 10 90 10
|
||||||
|
C 110 20 120 30 120 20
|
||||||
|
C 140 10 150 10 150 10
|
||||||
`}
|
`}
|
||||||
|
y="20"
|
||||||
>We go up, then we go down, then up again</Text>
|
>We go up, then we go down, then up again</Text>
|
||||||
</Svg>;
|
</Svg>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ package com.horcrux.svg;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.DashPathEffect;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Path;
|
import android.graphics.Path;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
@@ -144,6 +145,38 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
|
|||||||
markUpdateSeen();
|
markUpdateSeen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
for (int i = 0; i < stopsCount; i++) {
|
||||||
|
stops[i] = value[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));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
paint.reset();
|
||||||
|
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
paint.setStyle(Paint.Style.FILL);
|
||||||
|
setupPaint(paint, opacity, mFillColor);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up {@link #mPaint} according to the props set on a shadow view. Returns {@code true}
|
* 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.
|
* if the stroke should be drawn, {@code false} if not.
|
||||||
@@ -185,107 +218,79 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
paint.setStrokeWidth(mStrokeWidth * mScale);
|
paint.setStrokeWidth(mStrokeWidth * mScale);
|
||||||
paint.setARGB(
|
|
||||||
(int) (mStrokeColor.length > 3 ? mStrokeColor[3] * opacity * 255 : opacity * 255),
|
setupPaint(paint, opacity, mStrokeColor);
|
||||||
(int) (mStrokeColor[0] * 255),
|
|
||||||
(int) (mStrokeColor[1] * 255),
|
|
||||||
(int) (mStrokeColor[2] * 255));
|
|
||||||
if (mStrokeDash != null && mStrokeDash.length > 0) {
|
if (mStrokeDash != null && mStrokeDash.length > 0) {
|
||||||
// TODO(6352067): Support dashes
|
// todo: dashoffset
|
||||||
FLog.w(ReactConstants.TAG, "RNSVG: Dashes are not supported yet!");
|
paint.setPathEffect(new DashPathEffect(mStrokeDash, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* 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;
|
|
||||||
for (int i = 0; i < stopsCount; i++) {
|
|
||||||
stops[i] = value[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));
|
|
||||||
|
|
||||||
}
|
private void setupPaint(Paint paint, float opacity, float[] colors) {
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
|
||||||
int stopsCount;
|
int stopsCount;
|
||||||
int [] stopsColors;
|
int [] stopsColors;
|
||||||
float [] stops;
|
float [] stops;
|
||||||
if (mFillColor != null && mFillColor.length > 0) {
|
|
||||||
paint.reset();
|
|
||||||
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
|
|
||||||
paint.setStyle(Paint.Style.FILL);
|
|
||||||
int colorType = (int) mFillColor[0];
|
|
||||||
switch (colorType) {
|
|
||||||
case 0:
|
|
||||||
paint.setARGB(
|
|
||||||
(int) (mFillColor.length > 4 ? mFillColor[4] * opacity * 255 : opacity * 255),
|
|
||||||
(int) (mFillColor[1] * 255),
|
|
||||||
(int) (mFillColor[2] * 255),
|
|
||||||
(int) (mFillColor[3] * 255));
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
stopsCount = (mFillColor.length - 5) / 5;
|
|
||||||
stopsColors = new int [stopsCount];
|
|
||||||
stops = new float[stopsCount];
|
|
||||||
|
|
||||||
parseGradientStops(mFillColor, stopsCount, stops, stopsColors, 5);
|
int colorType = (int) colors[0];
|
||||||
paint.setShader(
|
switch (colorType) {
|
||||||
new LinearGradient(
|
case 0:
|
||||||
mFillColor[1] * mScale,
|
paint.setARGB(
|
||||||
mFillColor[2] * mScale,
|
(int) (colors.length > 4 ? colors[4] * opacity * 255 : opacity * 255),
|
||||||
mFillColor[3] * mScale,
|
(int) (colors[1] * 255),
|
||||||
mFillColor[4] * mScale,
|
(int) (colors[2] * 255),
|
||||||
stopsColors,
|
(int) (colors[3] * 255));
|
||||||
stops,
|
break;
|
||||||
Shader.TileMode.CLAMP));
|
case 1:
|
||||||
break;
|
stopsCount = (colors.length - 5) / 5;
|
||||||
case 2:
|
stopsColors = new int [stopsCount];
|
||||||
stopsCount = (mFillColor.length - 7) / 5;
|
stops = new float[stopsCount];
|
||||||
stopsColors = new int [stopsCount];
|
|
||||||
stops = new float[stopsCount];
|
|
||||||
parseGradientStops(mFillColor, stopsCount, stops, stopsColors, 7);
|
|
||||||
|
|
||||||
// TODO: support focus
|
parseGradientStops(colors, stopsCount, stops, stopsColors, 5);
|
||||||
float focusX = mFillColor[1];
|
paint.setShader(
|
||||||
float focusY = mFillColor[2];
|
new LinearGradient(
|
||||||
|
colors[1] * mScale,
|
||||||
float radius = mFillColor[3];
|
colors[2] * mScale,
|
||||||
float radiusRatio = mFillColor[4] / radius;
|
colors[3] * mScale,
|
||||||
Shader radialGradient = new RadialGradient(
|
colors[4] * mScale,
|
||||||
mFillColor[5] * mScale,
|
|
||||||
mFillColor[6] * mScale / radiusRatio,
|
|
||||||
radius * mScale,
|
|
||||||
stopsColors,
|
stopsColors,
|
||||||
stops,
|
stops,
|
||||||
Shader.TileMode.CLAMP
|
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);
|
||||||
|
|
||||||
Matrix radialMatrix = new Matrix();
|
// TODO: support focus
|
||||||
|
float focusX = colors[1];
|
||||||
|
float focusY = colors[2];
|
||||||
|
|
||||||
// seems like a bug here?
|
float radius = colors[3];
|
||||||
radialMatrix.preScale(1f, radiusRatio);
|
float radiusRatio = colors[4] / radius;
|
||||||
radialGradient.setLocalMatrix(radialMatrix);
|
Shader radialGradient = new RadialGradient(
|
||||||
paint.setShader(radialGradient);
|
colors[5] * mScale,
|
||||||
break;
|
colors[6] * mScale / radiusRatio,
|
||||||
default:
|
radius * mScale,
|
||||||
// TODO: Support pattern.
|
stopsColors,
|
||||||
FLog.w(ReactConstants.TAG, "RNSVG: Color type " + colorType + " not supported!");
|
stops,
|
||||||
}
|
Shader.TileMode.CLAMP
|
||||||
return true;
|
);
|
||||||
|
|
||||||
|
Matrix radialMatrix = new Matrix();
|
||||||
|
|
||||||
|
// seems like a bug here?
|
||||||
|
radialMatrix.preScale(1f, radiusRatio);
|
||||||
|
radialGradient.setLocalMatrix(radialMatrix);
|
||||||
|
paint.setShader(radialGradient);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// TODO: Support pattern.
|
||||||
|
FLog.w(ReactConstants.TAG, "RNSVG: Color type " + colorType + " not supported!");
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,11 @@ import javax.annotation.Nullable;
|
|||||||
|
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Path;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
||||||
import com.facebook.react.bridge.ReadableArray;
|
import com.facebook.react.bridge.ReadableArray;
|
||||||
import com.facebook.react.bridge.ReadableMap;
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||||
@@ -41,6 +43,7 @@ public class RNSVGTextShadowNode extends RNSVGPathShadowNode {
|
|||||||
|
|
||||||
private @Nullable ReadableMap mFrame;
|
private @Nullable ReadableMap mFrame;
|
||||||
private int mTextAlignment = TEXT_ALIGNMENT_LEFT;
|
private int mTextAlignment = TEXT_ALIGNMENT_LEFT;
|
||||||
|
private Path mPath;
|
||||||
|
|
||||||
@ReactProp(name = "frame")
|
@ReactProp(name = "frame")
|
||||||
public void setFrame(@Nullable ReadableMap frame) {
|
public void setFrame(@Nullable ReadableMap frame) {
|
||||||
@@ -52,6 +55,14 @@ public class RNSVGTextShadowNode extends RNSVGPathShadowNode {
|
|||||||
mTextAlignment = alignment;
|
mTextAlignment = alignment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "path")
|
||||||
|
public void setPath(@Nullable ReadableArray textPath) {
|
||||||
|
float[] pathData = PropHelper.toFloatArray(textPath);
|
||||||
|
Path path = new Path();
|
||||||
|
mPath = super.createPath(pathData, path);
|
||||||
|
markUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(Canvas canvas, Paint paint, float opacity) {
|
public void draw(Canvas canvas, Paint paint, float opacity) {
|
||||||
if (mFrame == null) {
|
if (mFrame == null) {
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode {
|
|||||||
private static final int CLIP_RULE_NONZERO = 1;
|
private static final int CLIP_RULE_NONZERO = 1;
|
||||||
protected final float mScale;
|
protected final float mScale;
|
||||||
private float[] mClipData;
|
private float[] mClipData;
|
||||||
|
private int mClipRule;
|
||||||
|
private boolean mClipRuleSet;
|
||||||
|
private boolean mClipDataSet;
|
||||||
|
|
||||||
public RNSVGVirtualNode() {
|
public RNSVGVirtualNode() {
|
||||||
mScale = DisplayMetricsHolder.getWindowDisplayMetrics().density;
|
mScale = DisplayMetricsHolder.getWindowDisplayMetrics().density;
|
||||||
@@ -93,24 +96,16 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode {
|
|||||||
@ReactProp(name = "clipPath")
|
@ReactProp(name = "clipPath")
|
||||||
public void setClipPath(@Nullable ReadableArray clipPath) {
|
public void setClipPath(@Nullable ReadableArray clipPath) {
|
||||||
mClipData = PropHelper.toFloatArray(clipPath);
|
mClipData = PropHelper.toFloatArray(clipPath);
|
||||||
|
mClipDataSet = true;
|
||||||
|
setupClip();
|
||||||
markUpdated();
|
markUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReactProp(name = "clipRule", defaultInt = CLIP_RULE_NONZERO)
|
@ReactProp(name = "clipRule", defaultInt = CLIP_RULE_NONZERO)
|
||||||
public void setClipRule(int clipRule) {
|
public void setClipRule(int clipRule) {
|
||||||
Path path = new Path();
|
mClipRule = clipRule;
|
||||||
switch (clipRule) {
|
mClipRuleSet = true;
|
||||||
case CLIP_RULE_EVENODD:
|
setupClip();
|
||||||
path.setFillType(Path.FillType.EVEN_ODD);
|
|
||||||
break;
|
|
||||||
case CLIP_RULE_NONZERO:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new JSApplicationIllegalArgumentException(
|
|
||||||
"clipRule " + clipRule + " unrecognized");
|
|
||||||
}
|
|
||||||
|
|
||||||
mClipPath = createPath(mClipData, path);
|
|
||||||
markUpdated();
|
markUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,6 +130,24 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode {
|
|||||||
markUpdated();
|
markUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setupClip() {
|
||||||
|
if (mClipDataSet && mClipRuleSet) {
|
||||||
|
Path path = new Path();
|
||||||
|
switch (mClipRule) {
|
||||||
|
case CLIP_RULE_EVENODD:
|
||||||
|
path.setFillType(Path.FillType.EVEN_ODD);
|
||||||
|
break;
|
||||||
|
case CLIP_RULE_NONZERO:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new JSApplicationIllegalArgumentException(
|
||||||
|
"clipRule " + mClipRule + " unrecognized");
|
||||||
|
}
|
||||||
|
|
||||||
|
mClipPath = createPath(mClipData, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void setupMatrix() {
|
protected void setupMatrix() {
|
||||||
sRawMatrix[0] = sMatrixData[0];
|
sRawMatrix[0] = sMatrixData[0];
|
||||||
sRawMatrix[1] = sMatrixData[2];
|
sRawMatrix[1] = sMatrixData[2];
|
||||||
@@ -163,7 +176,9 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode {
|
|||||||
protected Path createPath(float[] data, Path path) {
|
protected Path createPath(float[] data, Path path) {
|
||||||
path.moveTo(0, 0);
|
path.moveTo(0, 0);
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
while (i < data.length) {
|
while (i < data.length) {
|
||||||
|
|
||||||
int type = (int) data[i++];
|
int type = (int) data[i++];
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case PATH_TYPE_MOVETO:
|
case PATH_TYPE_MOVETO:
|
||||||
|
|||||||
@@ -31,13 +31,13 @@ export default function (props) {
|
|||||||
if (pattern) {
|
if (pattern) {
|
||||||
clippingProps.clipPath = new SerializablePath(pattern).toJSON();
|
clippingProps.clipPath = new SerializablePath(pattern).toJSON();
|
||||||
} else {
|
} else {
|
||||||
|
clippingProps = {};
|
||||||
// TODO: warn
|
// TODO: warn
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
clippingProps.clipPath = new SerializablePath(clipPath).toJSON();
|
clippingProps.clipPath = new SerializablePath(clipPath).toJSON();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return clippingProps;
|
return clippingProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,15 +28,21 @@ function strokeFilter(props, dimensions) {
|
|||||||
strokeDasharray = strokeDasharray.split(separator).map(dash => +dash);
|
strokeDasharray = strokeDasharray.split(separator).map(dash => +dash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// strokeDasharray length must be more than 1.
|
||||||
|
if (strokeDasharray && strokeDasharray.length === 1) {
|
||||||
|
strokeDasharray.push(strokeDasharray[0]);
|
||||||
|
}
|
||||||
|
|
||||||
if (!stroke) {
|
if (!stroke) {
|
||||||
stroke = '#000';
|
stroke = '#000';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: dashoffset
|
||||||
// TODO: propTypes check
|
// TODO: propTypes check
|
||||||
return {
|
return {
|
||||||
stroke: patterns(stroke, +props.strokeOpacity, dimensions, props.svgId),
|
stroke: patterns(stroke, +props.strokeOpacity, dimensions, props.svgId),
|
||||||
strokeLinecap: caps[props.strokeLinecap] || 2,
|
strokeLinecap: caps[props.strokeLinecap] || 0,
|
||||||
strokeLinejoin: joins[props.strokeLinejoin] || 0,
|
strokeLinejoin: joins[props.strokeLinejoin] || 0,
|
||||||
strokeDash: strokeDasharray || null,
|
strokeDash: strokeDasharray || null,
|
||||||
strokeWidth: strokeWidth || 1
|
strokeWidth: strokeWidth || 1
|
||||||
|
|||||||
@@ -82,15 +82,12 @@ const alignments = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function(props) {
|
export default function(props) {
|
||||||
let textPath = props.path ? new SerializablePath(props.path).toJSON() : null;
|
|
||||||
var textFrame = extractFontAndLines(
|
|
||||||
props,
|
|
||||||
childrenAsString(props.children)
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
alignment: alignments[props.textAnchor] || 0,
|
alignment: alignments[props.textAnchor] || 0,
|
||||||
frame: textFrame,
|
frame: extractFontAndLines(
|
||||||
path: textPath
|
props,
|
||||||
|
childrenAsString(props.children)
|
||||||
|
),
|
||||||
|
path: props.path ? new SerializablePath(props.path).toJSON() : undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user