From 05faac3cf11ecfc03e8142a93567c1bd70106aac Mon Sep 17 00:00:00 2001 From: Horcrux Date: Sat, 23 Apr 2016 14:44:48 +0800 Subject: [PATCH] add some android support add path for Text add stroke pattern add text dashed and fix dasharray bug --- Example/examples/Stroking.js | 6 +- Example/examples/Text.js | 3 + .../com/horcrux/svg/RNSVGPathShadowNode.java | 175 +++++++++--------- .../com/horcrux/svg/RNSVGTextShadowNode.java | 11 ++ .../com/horcrux/svg/RNSVGVirtualNode.java | 41 ++-- lib/extract/extractClipping.js | 2 +- lib/extract/extractStroke.js | 8 +- lib/extract/extractText.js | 13 +- 8 files changed, 148 insertions(+), 111 deletions(-) diff --git a/Example/examples/Stroking.js b/Example/examples/Stroking.js index 099e0ee5..ffc76105 100644 --- a/Example/examples/Stroking.js +++ b/Example/examples/Stroking.js @@ -32,9 +32,9 @@ class StrokeLinecap extends Component{ render() { return - - - + + + ; } diff --git a/Example/examples/Text.js b/Example/examples/Text.js index f53a3954..a1b3236d 100644 --- a/Example/examples/Text.js +++ b/Example/examples/Text.js @@ -131,7 +131,10 @@ class TextPath extends Component{ C 20 10 30 0 40 10 C 50 20 60 30 70 20 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 ; } diff --git a/android/src/main/java/com/horcrux/svg/RNSVGPathShadowNode.java b/android/src/main/java/com/horcrux/svg/RNSVGPathShadowNode.java index 18826de3..c33f867d 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGPathShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RNSVGPathShadowNode.java @@ -12,6 +12,7 @@ package com.horcrux.svg; import javax.annotation.Nullable; import android.graphics.Canvas; +import android.graphics.DashPathEffect; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Point; @@ -144,6 +145,38 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode { 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} * if the stroke should be drawn, {@code false} if not. @@ -185,107 +218,79 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode { } paint.setStrokeWidth(mStrokeWidth * mScale); - paint.setARGB( - (int) (mStrokeColor.length > 3 ? mStrokeColor[3] * opacity * 255 : opacity * 255), - (int) (mStrokeColor[0] * 255), - (int) (mStrokeColor[1] * 255), - (int) (mStrokeColor[2] * 255)); + + setupPaint(paint, opacity, mStrokeColor); + if (mStrokeDash != null && mStrokeDash.length > 0) { - // TODO(6352067): Support dashes - FLog.w(ReactConstants.TAG, "RNSVG: Dashes are not supported yet!"); + // todo: dashoffset + paint.setPathEffect(new DashPathEffect(mStrokeDash, 0)); } 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)); - } - } - - - /** - * 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) { + private void setupPaint(Paint paint, float opacity, float[] colors) { int stopsCount; int [] stopsColors; 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); - paint.setShader( - new LinearGradient( - mFillColor[1] * mScale, - mFillColor[2] * mScale, - mFillColor[3] * mScale, - mFillColor[4] * mScale, - stopsColors, - stops, - Shader.TileMode.CLAMP)); - break; - case 2: - stopsCount = (mFillColor.length - 7) / 5; - stopsColors = new int [stopsCount]; - stops = new float[stopsCount]; - parseGradientStops(mFillColor, stopsCount, stops, stopsColors, 7); + 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]; - // TODO: support focus - float focusX = mFillColor[1]; - float focusY = mFillColor[2]; - - float radius = mFillColor[3]; - float radiusRatio = mFillColor[4] / radius; - Shader radialGradient = new RadialGradient( - mFillColor[5] * mScale, - mFillColor[6] * mScale / radiusRatio, - radius * mScale, + parseGradientStops(colors, stopsCount, stops, stopsColors, 5); + paint.setShader( + new LinearGradient( + colors[1] * mScale, + colors[2] * mScale, + colors[3] * mScale, + colors[4] * mScale, stopsColors, 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? - 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 true; + float radius = colors[3]; + float radiusRatio = colors[4] / radius; + Shader radialGradient = new RadialGradient( + colors[5] * mScale, + colors[6] * mScale / radiusRatio, + radius * mScale, + stopsColors, + stops, + Shader.TileMode.CLAMP + ); + + 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; } } diff --git a/android/src/main/java/com/horcrux/svg/RNSVGTextShadowNode.java b/android/src/main/java/com/horcrux/svg/RNSVGTextShadowNode.java index ffeb62ac..d7f16218 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGTextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RNSVGTextShadowNode.java @@ -13,9 +13,11 @@ import javax.annotation.Nullable; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.Path; import android.graphics.Typeface; import android.text.TextUtils; +import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.uimanager.annotations.ReactProp; @@ -41,6 +43,7 @@ public class RNSVGTextShadowNode extends RNSVGPathShadowNode { private @Nullable ReadableMap mFrame; private int mTextAlignment = TEXT_ALIGNMENT_LEFT; + private Path mPath; @ReactProp(name = "frame") public void setFrame(@Nullable ReadableMap frame) { @@ -52,6 +55,14 @@ public class RNSVGTextShadowNode extends RNSVGPathShadowNode { 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 public void draw(Canvas canvas, Paint paint, float opacity) { if (mFrame == null) { diff --git a/android/src/main/java/com/horcrux/svg/RNSVGVirtualNode.java b/android/src/main/java/com/horcrux/svg/RNSVGVirtualNode.java index c60d4bcd..5f1029fe 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGVirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/RNSVGVirtualNode.java @@ -52,6 +52,9 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode { private static final int CLIP_RULE_NONZERO = 1; protected final float mScale; private float[] mClipData; + private int mClipRule; + private boolean mClipRuleSet; + private boolean mClipDataSet; public RNSVGVirtualNode() { mScale = DisplayMetricsHolder.getWindowDisplayMetrics().density; @@ -93,24 +96,16 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode { @ReactProp(name = "clipPath") public void setClipPath(@Nullable ReadableArray clipPath) { mClipData = PropHelper.toFloatArray(clipPath); + mClipDataSet = true; + setupClip(); markUpdated(); } @ReactProp(name = "clipRule", defaultInt = CLIP_RULE_NONZERO) public void setClipRule(int clipRule) { - Path path = new Path(); - switch (clipRule) { - case CLIP_RULE_EVENODD: - path.setFillType(Path.FillType.EVEN_ODD); - break; - case CLIP_RULE_NONZERO: - break; - default: - throw new JSApplicationIllegalArgumentException( - "clipRule " + clipRule + " unrecognized"); - } - - mClipPath = createPath(mClipData, path); + mClipRule = clipRule; + mClipRuleSet = true; + setupClip(); markUpdated(); } @@ -135,6 +130,24 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode { 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() { sRawMatrix[0] = sMatrixData[0]; sRawMatrix[1] = sMatrixData[2]; @@ -163,7 +176,9 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode { protected Path createPath(float[] data, Path path) { path.moveTo(0, 0); int i = 0; + while (i < data.length) { + int type = (int) data[i++]; switch (type) { case PATH_TYPE_MOVETO: diff --git a/lib/extract/extractClipping.js b/lib/extract/extractClipping.js index 31477e20..db0dc3f8 100644 --- a/lib/extract/extractClipping.js +++ b/lib/extract/extractClipping.js @@ -31,13 +31,13 @@ export default function (props) { if (pattern) { clippingProps.clipPath = new SerializablePath(pattern).toJSON(); } else { + clippingProps = {}; // TODO: warn } } else { clippingProps.clipPath = new SerializablePath(clipPath).toJSON(); } } - return clippingProps; } diff --git a/lib/extract/extractStroke.js b/lib/extract/extractStroke.js index e88e4f39..6e04be93 100644 --- a/lib/extract/extractStroke.js +++ b/lib/extract/extractStroke.js @@ -28,15 +28,21 @@ function strokeFilter(props, dimensions) { 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) { stroke = '#000'; } + + // TODO: dashoffset // TODO: propTypes check return { stroke: patterns(stroke, +props.strokeOpacity, dimensions, props.svgId), - strokeLinecap: caps[props.strokeLinecap] || 2, + strokeLinecap: caps[props.strokeLinecap] || 0, strokeLinejoin: joins[props.strokeLinejoin] || 0, strokeDash: strokeDasharray || null, strokeWidth: strokeWidth || 1 diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index 6b80fa11..1ad1ae0f 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -82,15 +82,12 @@ const alignments = { }; export default function(props) { - let textPath = props.path ? new SerializablePath(props.path).toJSON() : null; - var textFrame = extractFontAndLines( - props, - childrenAsString(props.children) - ); - return { alignment: alignments[props.textAnchor] || 0, - frame: textFrame, - path: textPath + frame: extractFontAndLines( + props, + childrenAsString(props.children) + ), + path: props.path ? new SerializablePath(props.path).toJSON() : undefined } }