add some android support

add path for Text
add stroke pattern
add text dashed and fix dasharray bug
This commit is contained in:
Horcrux
2016-04-23 14:44:48 +08:00
parent d990eaa472
commit 05faac3cf1
8 changed files with 148 additions and 111 deletions

View File

@@ -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>;
} }

View File

@@ -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>;
} }

View File

@@ -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,88 +218,63 @@ 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(); int colorType = (int) colors[0];
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.FILL);
int colorType = (int) mFillColor[0];
switch (colorType) { switch (colorType) {
case 0: case 0:
paint.setARGB( paint.setARGB(
(int) (mFillColor.length > 4 ? mFillColor[4] * opacity * 255 : opacity * 255), (int) (colors.length > 4 ? colors[4] * opacity * 255 : opacity * 255),
(int) (mFillColor[1] * 255), (int) (colors[1] * 255),
(int) (mFillColor[2] * 255), (int) (colors[2] * 255),
(int) (mFillColor[3] * 255)); (int) (colors[3] * 255));
break; break;
case 1: case 1:
stopsCount = (mFillColor.length - 5) / 5; stopsCount = (colors.length - 5) / 5;
stopsColors = new int [stopsCount]; stopsColors = new int [stopsCount];
stops = new float[stopsCount]; stops = new float[stopsCount];
parseGradientStops(mFillColor, stopsCount, stops, stopsColors, 5); parseGradientStops(colors, stopsCount, stops, stopsColors, 5);
paint.setShader( paint.setShader(
new LinearGradient( new LinearGradient(
mFillColor[1] * mScale, colors[1] * mScale,
mFillColor[2] * mScale, colors[2] * mScale,
mFillColor[3] * mScale, colors[3] * mScale,
mFillColor[4] * mScale, colors[4] * mScale,
stopsColors, stopsColors,
stops, stops,
Shader.TileMode.CLAMP)); Shader.TileMode.CLAMP));
break; break;
case 2: case 2:
stopsCount = (mFillColor.length - 7) / 5; stopsCount = (colors.length - 7) / 5;
stopsColors = new int [stopsCount]; stopsColors = new int [stopsCount];
stops = new float[stopsCount]; stops = new float[stopsCount];
parseGradientStops(mFillColor, stopsCount, stops, stopsColors, 7); parseGradientStops(colors, stopsCount, stops, stopsColors, 7);
// TODO: support focus // TODO: support focus
float focusX = mFillColor[1]; float focusX = colors[1];
float focusY = mFillColor[2]; float focusY = colors[2];
float radius = mFillColor[3]; float radius = colors[3];
float radiusRatio = mFillColor[4] / radius; float radiusRatio = colors[4] / radius;
Shader radialGradient = new RadialGradient( Shader radialGradient = new RadialGradient(
mFillColor[5] * mScale, colors[5] * mScale,
mFillColor[6] * mScale / radiusRatio, colors[6] * mScale / radiusRatio,
radius * mScale, radius * mScale,
stopsColors, stopsColors,
stops, stops,
@@ -284,8 +292,5 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
// TODO: Support pattern. // TODO: Support pattern.
FLog.w(ReactConstants.TAG, "RNSVG: Color type " + colorType + " not supported!"); FLog.w(ReactConstants.TAG, "RNSVG: Color type " + colorType + " not supported!");
} }
return true;
}
return false;
} }
} }

View File

@@ -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) {

View File

@@ -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:

View File

@@ -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;
} }

View File

@@ -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

View File

@@ -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
} }
} }