From f173726b9c2e6446302183935787c15199bae195 Mon Sep 17 00:00:00 2001 From: Horcrux Date: Thu, 28 Apr 2016 01:01:47 +0800 Subject: [PATCH] complete ClipPath element on android --- README.md | 1 + .../com/horcrux/svg/RNSVGGroupShadowNode.java | 39 +++- .../com/horcrux/svg/RNSVGPathShadowNode.java | 38 ++-- .../com/horcrux/svg/RNSVGShapeShadowNode.java | 167 +++++++++--------- .../com/horcrux/svg/RNSVGTextShadowNode.java | 4 +- .../com/horcrux/svg/RNSVGVirtualNode.java | 51 ++++-- 6 files changed, 181 insertions(+), 119 deletions(-) diff --git a/README.md b/README.md index 8bf8dfde..4651a194 100644 --- a/README.md +++ b/README.md @@ -565,6 +565,7 @@ npm install 7. Pattern element 8. Image element 9. calculate bounding box only if necessary. +10. alignment to textAnchor #### Thanks: diff --git a/android/src/main/java/com/horcrux/svg/RNSVGGroupShadowNode.java b/android/src/main/java/com/horcrux/svg/RNSVGGroupShadowNode.java index f77d1667..bae7ef74 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGGroupShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RNSVGGroupShadowNode.java @@ -9,17 +9,27 @@ package com.horcrux.svg; -import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; -import android.graphics.Region; +import android.graphics.Path; import android.util.Log; +import com.facebook.react.uimanager.annotations.ReactProp; + /** * Shadow node for virtual RNSVGGroup view */ public class RNSVGGroupShadowNode extends RNSVGVirtualNode { + private String mAsClipPath = null; + + @ReactProp(name = "asClipPath") + public void setAsClipPath(String asClipPath) { + mAsClipPath = asClipPath; + markUpdated(); + } + + @Override public boolean isVirtual() { return true; @@ -29,16 +39,27 @@ public class RNSVGGroupShadowNode extends RNSVGVirtualNode { opacity *= mOpacity; if (opacity > MIN_OPACITY_FOR_DRAW) { saveAndSetupCanvas(canvas); - clip(canvas, paint); - - for (int i = 0; i < getChildCount(); i++) { - RNSVGVirtualNode child = (RNSVGVirtualNode) getChildAt(i); - child.draw(canvas, paint, opacity); - child.markUpdateSeen(); + if (mAsClipPath == null) { + for (int i = 0; i < getChildCount(); i++) { + RNSVGVirtualNode child = (RNSVGVirtualNode) getChildAt(i); + child.draw(canvas, paint, opacity); + child.markUpdateSeen(); + } + } else { + defineClipPath(getPath(canvas), mAsClipPath); } - restoreCanvas(canvas); } } + + @Override + protected Path getPath(Canvas canvas) { + Path path = new Path(); + for (int i = 0; i < getChildCount(); i++) { + RNSVGVirtualNode child = (RNSVGVirtualNode) getChildAt(i); + path.addPath(child.getPath(canvas)); + } + return path; + } } diff --git a/android/src/main/java/com/horcrux/svg/RNSVGPathShadowNode.java b/android/src/main/java/com/horcrux/svg/RNSVGPathShadowNode.java index 0bcdfc2b..c708cd18 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGPathShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RNSVGPathShadowNode.java @@ -48,8 +48,6 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode { private static final int FILL_RULE_EVENODD = 0; private static final int FILL_RULE_NONZERO = 1; - - protected Path mPath; private @Nullable ReadableArray mStrokeColor; private @Nullable ReadableArray mFillColor; private @Nullable float[] mStrokeDasharray; @@ -59,14 +57,15 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode { private int mStrokeLinejoin = JOIN_ROUND; private int mFillRule = FILL_RULE_NONZERO; private boolean mFillRuleSet; + protected Path mPath; private boolean mPathSet; - private float[] mShapePath; + private float[] mD; protected RectF mContentBoundingBox; private Point mPaint; @ReactProp(name = "d") public void setPath(@Nullable ReadableArray shapePath) { - mShapePath = PropHelper.toFloatArray(shapePath); + mD = PropHelper.toFloatArray(shapePath); mPathSet = true; setupPath(); markUpdated(); @@ -156,20 +155,7 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode { private void setupPath() { // init path after both fillRule and path have been set if (mFillRuleSet && mPathSet) { - Path path = new Path(); - - switch (mFillRule) { - case FILL_RULE_EVENODD: - path.setFillType(Path.FillType.EVEN_ODD); - break; - case FILL_RULE_NONZERO: - break; - default: - throw new JSApplicationIllegalArgumentException( - "fillRule " + mFillRule + " unrecognized"); - } - - mPath = super.createPath(mShapePath, path); + mPath = getPath(null); RectF box = new RectF(); mPath.computeBounds(box, true); mContentBoundingBox = box; @@ -328,4 +314,20 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode { FLog.w(ReactConstants.TAG, "RNSVG: Color type " + colorType + " not supported!"); } } + + protected Path getPath(@Nullable Canvas canvas) { + Path path = new Path(); + switch (mFillRule) { + case FILL_RULE_EVENODD: + path.setFillType(Path.FillType.EVEN_ODD); + break; + case FILL_RULE_NONZERO: + break; + default: + throw new JSApplicationIllegalArgumentException( + "fillRule " + mFillRule + " unrecognized"); + } + super.createPath(mD, path); + return path; + } } diff --git a/android/src/main/java/com/horcrux/svg/RNSVGShapeShadowNode.java b/android/src/main/java/com/horcrux/svg/RNSVGShapeShadowNode.java index aa08454a..843fc392 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGShapeShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RNSVGShapeShadowNode.java @@ -17,6 +17,7 @@ import android.graphics.RectF; import android.util.Log; import com.facebook.common.logging.FLog; +import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.common.ReactConstants; import com.facebook.react.uimanager.annotations.ReactProp; @@ -40,86 +41,7 @@ public class RNSVGShapeShadowNode extends RNSVGPathShadowNode { 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; - ReadableMap value = mShape.getMap("r"); - if (value.getBoolean("percentage")) { - float percent = (float)value.getDouble("value"); - float powX = (float)Math.pow((width * percent), 2); - float powY = (float)Math.pow((height*percent), 2); - r = (float)Math.sqrt(powX + powY) / (float)Math.sqrt(2); - } else { - r = (float)value.getDouble("value") * mScale; - } - - 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); - } - + mPath = getPath(canvas); RectF shapeBox = new RectF(); mPath.computeBounds(shapeBox, true); mContentBoundingBox = shapeBox; @@ -140,4 +62,89 @@ public class RNSVGShapeShadowNode extends RNSVGPathShadowNode { return 0f; } } + + @Override + protected Path getPath(Canvas canvas) { + Path path = new Path(); + + int type = mShape.getInt("type"); + Rect box = canvas.getClipBounds(); + float height = box.height(); + float width = box.width(); + + switch (type) { + case 0: { + // draw circle + // TODO: + float cx = getActualProp("cx", width); + float cy = getActualProp("cy", height); + + float r; + ReadableMap value = mShape.getMap("r"); + if (value.getBoolean("percentage")) { + float percent = (float)value.getDouble("value"); + float powX = (float)Math.pow((width * percent), 2); + float powY = (float)Math.pow((height*percent), 2); + r = (float)Math.sqrt(powX + powY) / (float)Math.sqrt(2); + } else { + r = (float)value.getDouble("value") * mScale; + } + + path.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); + path.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); + path.moveTo(x1, y1); + path.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; + } + path.addRoundRect(new RectF(x, y, x + w, y + h), rx, ry, Path.Direction.CW); + } else { + path.addRect(x, y, x + w, y + h, Path.Direction.CW); + } + break; + } + default: + FLog.e(ReactConstants.TAG, "RNSVG: Invalid Shape type " + type + " at " + mShape); + } + return path; + } } diff --git a/android/src/main/java/com/horcrux/svg/RNSVGTextShadowNode.java b/android/src/main/java/com/horcrux/svg/RNSVGTextShadowNode.java index c7127914..bd7d4631 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGTextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RNSVGTextShadowNode.java @@ -61,8 +61,8 @@ public class RNSVGTextShadowNode extends RNSVGPathShadowNode { @ReactProp(name = "path") public void setPath(@Nullable ReadableArray textPath) { float[] pathData = PropHelper.toFloatArray(textPath); - Path path = new Path(); - mPath = super.createPath(pathData, path); + mPath = new Path(); + super.createPath(pathData, mPath); markUpdated(); } diff --git a/android/src/main/java/com/horcrux/svg/RNSVGVirtualNode.java b/android/src/main/java/com/horcrux/svg/RNSVGVirtualNode.java index 056b6057..84bcf6e5 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGVirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/RNSVGVirtualNode.java @@ -19,29 +19,35 @@ import android.graphics.Path; import android.graphics.RectF; import android.graphics.Region; import android.util.Log; +import android.view.View; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.uimanager.DisplayMetricsHolder; +import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.ReactShadowNode; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + /** * Base class for RNSVGView virtual nodes: {@link RNSVGGroupShadowNode}, {@link RNSVGPathShadowNode} and * indirectly for {@link RNSVGTextShadowNode}. */ public abstract class RNSVGVirtualNode extends ReactShadowNode { + protected static Map CLIP_PATHS = new HashMap<>(); protected static final float MIN_OPACITY_FOR_DRAW = 0.01f; private static final float[] sMatrixData = new float[9]; private static final float[] sRawMatrix = new float[9]; - + private @Nullable String mDefinedClipPathId; protected float mOpacity = 1f; private @Nullable Matrix mMatrix = new Matrix(); - protected @Nullable Path mClipPath; - + private @Nullable String mClipPathId; private static final int PATH_TYPE_ARC = 4; private static final int PATH_TYPE_CLOSE = 1; private static final int PATH_TYPE_CURVETO = 3; @@ -101,6 +107,12 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode { markUpdated(); } + @ReactProp(name = "clipPathId") + public void setClipPathId(String clipPathId) { + mClipPathId = clipPathId; + markUpdated(); + } + @ReactProp(name = "clipRule", defaultInt = CLIP_RULE_NONZERO) public void setClipRule(int clipRule) { mClipRule = clipRule; @@ -132,10 +144,10 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode { private void setupClip() { if (mClipDataSet && mClipRuleSet) { - Path path = new Path(); + mClipPath = new Path(); switch (mClipRule) { case CLIP_RULE_EVENODD: - path.setFillType(Path.FillType.EVEN_ODD); + mClipPath.setFillType(Path.FillType.EVEN_ODD); break; case CLIP_RULE_NONZERO: break; @@ -144,7 +156,7 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode { "clipRule " + mClipRule + " unrecognized"); } - mClipPath = createPath(mClipData, path); + createPath(mClipData, mClipPath); } } @@ -185,9 +197,9 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode { * 2 (PATH_LINE_TO), x, y. This will draw a line from the last draw point (or 0,0) to x,y. * * @param data the array of instructions - * @return the {@link Path} that can be drawn to a canvas + * @param path the {@link Path} that can be drawn to a canvas */ - protected Path createPath(float[] data, Path path) { + protected void createPath(float[] data, Path path) { path.moveTo(0, 0); int i = 0; @@ -242,13 +254,32 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode { "Unrecognized drawing instruction " + type); } } - return path; } protected void clip(Canvas canvas, Paint paint) { + Path clip = null; if (mClipPath != null) { - canvas.clipPath(mClipPath, Region.Op.REPLACE); + clip = mClipPath; + } else if (mClipPathId != null) { + clip = CLIP_PATHS.get(mClipPathId); + } + + if (clip != null) { + canvas.clipPath(clip, Region.Op.REPLACE); canvas.saveLayer(0f, 0f, 0f, 0f, paint, Canvas.CLIP_SAVE_FLAG); } } + + protected void defineClipPath(Path clipPath, String clipPathId) { + CLIP_PATHS.put(clipPathId, clipPath); + mDefinedClipPathId = clipPathId; + } + + protected void finalize() { + if (mDefinedClipPathId != null) { + CLIP_PATHS.remove(mDefinedClipPathId); + } + } + + abstract protected Path getPath(Canvas canvas); }