mirror of
https://github.com/zoriya/react-native-svg.git
synced 2025-12-06 07:06:11 +00:00
add Gradients
This commit is contained in:
@@ -49,5 +49,6 @@ import com.facebook.react.bridge.ReadableArray;
|
||||
into[i] = (float) value.getDouble(i);
|
||||
}
|
||||
return value.size();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
package com.horcrux.svg;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
|
||||
@@ -17,23 +18,23 @@ import android.graphics.Paint;
|
||||
*/
|
||||
public class RNSVGGroupShadowNode extends RNSVGVirtualNode {
|
||||
|
||||
@Override
|
||||
public boolean isVirtual() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void draw(Canvas canvas, Paint paint, float opacity) {
|
||||
opacity *= mOpacity;
|
||||
if (opacity > MIN_OPACITY_FOR_DRAW) {
|
||||
saveAndSetupCanvas(canvas);
|
||||
// TODO(6352006): apply clipping (iOS doesn't do it yet, it seems to cause issues)
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
RNSVGVirtualNode child = (RNSVGVirtualNode) getChildAt(i);
|
||||
child.draw(canvas, paint, opacity);
|
||||
child.markUpdateSeen();
|
||||
}
|
||||
|
||||
restoreCanvas(canvas);
|
||||
@Override
|
||||
public boolean isVirtual() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void draw(Canvas canvas, Paint paint, float opacity) {
|
||||
opacity *= mOpacity;
|
||||
if (opacity > MIN_OPACITY_FOR_DRAW) {
|
||||
saveAndSetupCanvas(canvas);
|
||||
// TODO(6352006): apply clipping (iOS doesn't do it yet, it seems to cause issues)
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
RNSVGVirtualNode child = (RNSVGVirtualNode) getChildAt(i);
|
||||
child.draw(canvas, paint, opacity);
|
||||
child.markUpdateSeen();
|
||||
}
|
||||
|
||||
restoreCanvas(canvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,241 +11,333 @@ package com.horcrux.svg;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.RadialGradient;
|
||||
import android.graphics.RectF;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.Matrix;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Shadow node for virtual RNSVGPath view
|
||||
*/
|
||||
public class RNSVGPathShadowNode extends RNSVGVirtualNode {
|
||||
|
||||
private static final int CAP_BUTT = 0;
|
||||
private static final int CAP_ROUND = 1;
|
||||
private static final int CAP_SQUARE = 2;
|
||||
private static final int CAP_BUTT = 0;
|
||||
private static final int CAP_ROUND = 1;
|
||||
private static final int CAP_SQUARE = 2;
|
||||
|
||||
private static final int JOIN_BEVEL = 2;
|
||||
private static final int JOIN_MITER = 0;
|
||||
private static final int JOIN_ROUND = 1;
|
||||
private static final int JOIN_BEVEL = 2;
|
||||
private static final int JOIN_MITER = 0;
|
||||
private static final int JOIN_ROUND = 1;
|
||||
|
||||
private static final int PATH_TYPE_ARC = 4;
|
||||
private static final int PATH_TYPE_CLOSE = 1;
|
||||
private static final int PATH_TYPE_CURVETO = 3;
|
||||
private static final int PATH_TYPE_LINETO = 2;
|
||||
private static final int PATH_TYPE_MOVETO = 0;
|
||||
private static final int PATH_TYPE_ARC = 4;
|
||||
private static final int PATH_TYPE_CLOSE = 1;
|
||||
private static final int PATH_TYPE_CURVETO = 3;
|
||||
private static final int PATH_TYPE_LINETO = 2;
|
||||
private static final int PATH_TYPE_MOVETO = 0;
|
||||
|
||||
protected @Nullable Path mPath;
|
||||
private @Nullable float[] mStrokeColor;
|
||||
private @Nullable float[] mFillColor;
|
||||
private @Nullable float[] mStrokeDash;
|
||||
private float mStrokeWidth = 1;
|
||||
private int mStrokeCap = CAP_ROUND;
|
||||
private int mStrokeJoin = JOIN_ROUND;
|
||||
protected @Nullable Path mPath;
|
||||
private @Nullable float[] mStrokeColor;
|
||||
private @Nullable float[] mFillColor;
|
||||
private @Nullable float[] mStrokeDash;
|
||||
private float mStrokeWidth = 1;
|
||||
private int mStrokeLinecap = CAP_ROUND;
|
||||
private int mStrokeLinejoin = JOIN_ROUND;
|
||||
|
||||
@ReactProp(name = "d")
|
||||
public void setPath(@Nullable ReadableArray shapePath) {
|
||||
float[] pathData = PropHelper.toFloatArray(shapePath);
|
||||
mPath = createPath(pathData);
|
||||
markUpdated();
|
||||
}
|
||||
private Point mPaint;
|
||||
|
||||
@ReactProp(name = "stroke")
|
||||
public void setStroke(@Nullable ReadableArray strokeColors) {
|
||||
mStrokeColor = PropHelper.toFloatArray(strokeColors);
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@ReactProp(name = "strokeDash")
|
||||
public void setStrokeDash(@Nullable ReadableArray strokeDash) {
|
||||
mStrokeDash = PropHelper.toFloatArray(strokeDash);
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@ReactProp(name = "fill")
|
||||
public void setFill(@Nullable ReadableArray fillColors) {
|
||||
mFillColor = PropHelper.toFloatArray(fillColors);
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@ReactProp(name = "strokeWidth", defaultFloat = 1f)
|
||||
public void setStrokeWidth(float strokeWidth) {
|
||||
mStrokeWidth = strokeWidth;
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@ReactProp(name = "strokeCap", defaultInt = CAP_ROUND)
|
||||
public void setStrokeCap(int strokeCap) {
|
||||
mStrokeCap = strokeCap;
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@ReactProp(name = "strokeJoin", defaultInt = JOIN_ROUND)
|
||||
public void setStrokeJoin(int strokeJoin) {
|
||||
mStrokeJoin = strokeJoin;
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas, Paint paint, float opacity) {
|
||||
opacity *= mOpacity;
|
||||
if (opacity > MIN_OPACITY_FOR_DRAW) {
|
||||
saveAndSetupCanvas(canvas);
|
||||
if (mPath == null) {
|
||||
throw new JSApplicationIllegalArgumentException(
|
||||
"Paths should have a valid path (d) prop");
|
||||
}
|
||||
if (setupStrokePaint(paint, opacity)) {
|
||||
canvas.drawPath(mPath, paint);
|
||||
}
|
||||
if (setupFillPaint(paint, opacity)) {
|
||||
canvas.drawPath(mPath, paint);
|
||||
}
|
||||
restoreCanvas(canvas);
|
||||
@ReactProp(name = "d")
|
||||
public void setPath(@Nullable ReadableArray shapePath) {
|
||||
float[] pathData = PropHelper.toFloatArray(shapePath);
|
||||
mPath = createPath(pathData);
|
||||
markUpdated();
|
||||
}
|
||||
markUpdateSeen();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return false;
|
||||
@ReactProp(name = "stroke")
|
||||
public void setStroke(@Nullable ReadableArray strokeColors) {
|
||||
mStrokeColor = PropHelper.toFloatArray(strokeColors);
|
||||
markUpdated();
|
||||
}
|
||||
paint.reset();
|
||||
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
switch (mStrokeCap) {
|
||||
case CAP_BUTT:
|
||||
paint.setStrokeCap(Paint.Cap.BUTT);
|
||||
break;
|
||||
case CAP_SQUARE:
|
||||
paint.setStrokeCap(Paint.Cap.SQUARE);
|
||||
break;
|
||||
case CAP_ROUND:
|
||||
paint.setStrokeCap(Paint.Cap.ROUND);
|
||||
break;
|
||||
default:
|
||||
throw new JSApplicationIllegalArgumentException(
|
||||
"strokeCap " + mStrokeCap + " unrecognized");
|
||||
}
|
||||
switch (mStrokeJoin) {
|
||||
case JOIN_MITER:
|
||||
paint.setStrokeJoin(Paint.Join.MITER);
|
||||
break;
|
||||
case JOIN_BEVEL:
|
||||
paint.setStrokeJoin(Paint.Join.BEVEL);
|
||||
break;
|
||||
case JOIN_ROUND:
|
||||
paint.setStrokeJoin(Paint.Join.ROUND);
|
||||
break;
|
||||
default:
|
||||
throw new JSApplicationIllegalArgumentException(
|
||||
"strokeJoin " + mStrokeJoin + " unrecognized");
|
||||
}
|
||||
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));
|
||||
if (mStrokeDash != null && mStrokeDash.length > 0) {
|
||||
// TODO(6352067): Support dashes
|
||||
FLog.w(ReactConstants.TAG, "RNSVG: Dashes are not supported yet!");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
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;
|
||||
default:
|
||||
// TODO(6352048): Support gradients etc.
|
||||
FLog.w(ReactConstants.TAG, "RNSVG: Color type " + colorType + " not supported!");
|
||||
}
|
||||
return true;
|
||||
@ReactProp(name = "strokeDash")
|
||||
public void setStrokeDash(@Nullable ReadableArray strokeDash) {
|
||||
mStrokeDash = PropHelper.toFloatArray(strokeDash);
|
||||
markUpdated();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Path} from an array of instructions constructed by JS
|
||||
* (see RNSVGSerializablePath.js). Each instruction starts with a type (see PATH_TYPE_*) followed
|
||||
* by arguments for that instruction. For example, to create a line the instruction will be
|
||||
* 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
|
||||
*/
|
||||
private Path createPath(float[] data) {
|
||||
Path path = new Path();
|
||||
path.moveTo(0, 0);
|
||||
int i = 0;
|
||||
while (i < data.length) {
|
||||
int type = (int) data[i++];
|
||||
switch (type) {
|
||||
case PATH_TYPE_MOVETO:
|
||||
path.moveTo(data[i++] * mScale, data[i++] * mScale);
|
||||
break;
|
||||
case PATH_TYPE_CLOSE:
|
||||
path.close();
|
||||
break;
|
||||
case PATH_TYPE_LINETO:
|
||||
path.lineTo(data[i++] * mScale, data[i++] * mScale);
|
||||
break;
|
||||
case PATH_TYPE_CURVETO:
|
||||
path.cubicTo(
|
||||
data[i++] * mScale,
|
||||
data[i++] * mScale,
|
||||
data[i++] * mScale,
|
||||
data[i++] * mScale,
|
||||
data[i++] * mScale,
|
||||
data[i++] * mScale);
|
||||
break;
|
||||
case PATH_TYPE_ARC:
|
||||
{
|
||||
float x = data[i++] * mScale;
|
||||
float y = data[i++] * mScale;
|
||||
float r = data[i++] * mScale;
|
||||
float start = (float) Math.toDegrees(data[i++]);
|
||||
float end = (float) Math.toDegrees(data[i++]);
|
||||
boolean clockwise = data[i++] == 0f;
|
||||
if (!clockwise) {
|
||||
end = 360 - end;
|
||||
}
|
||||
float sweep = start - end;
|
||||
RectF oval = new RectF(x - r, y - r, x + r, y + r);
|
||||
path.addArc(oval, start, sweep);
|
||||
break;
|
||||
@ReactProp(name = "fill")
|
||||
public void setFill(@Nullable ReadableArray fillColors) {
|
||||
mFillColor = PropHelper.toFloatArray(fillColors);
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@ReactProp(name = "strokeWidth", defaultFloat = 1f)
|
||||
public void setStrokeWidth(float strokeWidth) {
|
||||
mStrokeWidth = strokeWidth;
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@ReactProp(name = "strokeLinecap", defaultInt = CAP_ROUND)
|
||||
public void setStrokeLinecap(int strokeLinecap) {
|
||||
mStrokeLinecap = strokeLinecap;
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@ReactProp(name = "strokeLinejoin", defaultInt = JOIN_ROUND)
|
||||
public void setStrokeLinejoin(int strokeLinejoin) {
|
||||
mStrokeLinejoin = strokeLinejoin;
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas, Paint paint, float opacity) {
|
||||
opacity *= mOpacity;
|
||||
if (opacity > MIN_OPACITY_FOR_DRAW) {
|
||||
saveAndSetupCanvas(canvas);
|
||||
if (mPath == null) {
|
||||
throw new JSApplicationIllegalArgumentException(
|
||||
"Paths should have a valid path (d) prop");
|
||||
}
|
||||
if (setupStrokePaint(paint, opacity)) {
|
||||
canvas.drawPath(mPath, paint);
|
||||
}
|
||||
if (setupFillPaint(paint, opacity)) {
|
||||
canvas.drawPath(mPath, paint);
|
||||
}
|
||||
restoreCanvas(canvas);
|
||||
}
|
||||
default:
|
||||
throw new JSApplicationIllegalArgumentException(
|
||||
"Unrecognized drawing instruction " + type);
|
||||
}
|
||||
markUpdateSeen();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return false;
|
||||
}
|
||||
paint.reset();
|
||||
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
switch (mStrokeLinecap) {
|
||||
case CAP_BUTT:
|
||||
paint.setStrokeCap(Paint.Cap.BUTT);
|
||||
break;
|
||||
case CAP_SQUARE:
|
||||
paint.setStrokeCap(Paint.Cap.SQUARE);
|
||||
break;
|
||||
case CAP_ROUND:
|
||||
paint.setStrokeCap(Paint.Cap.ROUND);
|
||||
break;
|
||||
default:
|
||||
throw new JSApplicationIllegalArgumentException(
|
||||
"strokeLinecap " + mStrokeLinecap + " unrecognized");
|
||||
}
|
||||
switch (mStrokeLinejoin) {
|
||||
case JOIN_MITER:
|
||||
paint.setStrokeJoin(Paint.Join.MITER);
|
||||
break;
|
||||
case JOIN_BEVEL:
|
||||
paint.setStrokeJoin(Paint.Join.BEVEL);
|
||||
break;
|
||||
case JOIN_ROUND:
|
||||
paint.setStrokeJoin(Paint.Join.ROUND);
|
||||
break;
|
||||
default:
|
||||
throw new JSApplicationIllegalArgumentException(
|
||||
"strokeLinejoin " + mStrokeLinejoin + " unrecognized");
|
||||
}
|
||||
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));
|
||||
if (mStrokeDash != null && mStrokeDash.length > 0) {
|
||||
// TODO(6352067): Support dashes
|
||||
FLog.w(ReactConstants.TAG, "RNSVG: Dashes are not supported yet!");
|
||||
}
|
||||
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;
|
||||
int offset = 0;
|
||||
for (int i = 0; i < stopsCount; i++) {
|
||||
int index;
|
||||
|
||||
if (i % 2 == 0) {
|
||||
index = i / 2;
|
||||
} else {
|
||||
index = stopsCount + offset - i;
|
||||
offset++;
|
||||
}
|
||||
|
||||
stops[i] = value[startStops + index];
|
||||
stopsColors[i] = Color.argb(
|
||||
(int) (value[startColorsPosition + 3 + index * 4] * 255),
|
||||
(int) (value[startColorsPosition + index * 4] * 255),
|
||||
(int) (value[startColorsPosition + 1 + index * 4] * 255),
|
||||
(int) (value[startColorsPosition + 2 + index * 4] * 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) {
|
||||
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);
|
||||
|
||||
float radius = mFillColor[3];
|
||||
float radiusRatio = mFillColor[4] / radius;
|
||||
Shader radialGradient = new RadialGradient(
|
||||
mFillColor[5] * mScale,
|
||||
mFillColor[6] * mScale / radiusRatio,
|
||||
radius * mScale,
|
||||
stopsColors,
|
||||
stops,
|
||||
Shader.TileMode.CLAMP
|
||||
);
|
||||
|
||||
Matrix radialMatrix = new Matrix();
|
||||
float [] rawMatrix = new float[9];
|
||||
rawMatrix[0] = 1;
|
||||
rawMatrix[1] = 0;
|
||||
rawMatrix[2] = 0;
|
||||
rawMatrix[3] = 0;
|
||||
rawMatrix[4] = radiusRatio;
|
||||
rawMatrix[5] = 0;
|
||||
rawMatrix[6] = 0;
|
||||
rawMatrix[7] = 0;
|
||||
rawMatrix[8] = 1;
|
||||
|
||||
radialMatrix.setValues(rawMatrix);
|
||||
radialGradient.setLocalMatrix(radialMatrix);
|
||||
paint.setShader(radialGradient);
|
||||
break;
|
||||
default:
|
||||
// TODO: Support pattern.
|
||||
FLog.w(ReactConstants.TAG, "RNSVG: Color type " + colorType + " not supported!");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Path} from an array of instructions constructed by JS
|
||||
* (see RNSVGSerializablePath.js). Each instruction starts with a type (see PATH_TYPE_*) followed
|
||||
* by arguments for that instruction. For example, to create a line the instruction will be
|
||||
* 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
|
||||
*/
|
||||
private Path createPath(float[] data) {
|
||||
Path path = new Path();
|
||||
path.moveTo(0, 0);
|
||||
int i = 0;
|
||||
while (i < data.length) {
|
||||
int type = (int) data[i++];
|
||||
switch (type) {
|
||||
case PATH_TYPE_MOVETO:
|
||||
path.moveTo(data[i++] * mScale, data[i++] * mScale);
|
||||
break;
|
||||
case PATH_TYPE_CLOSE:
|
||||
path.close();
|
||||
break;
|
||||
case PATH_TYPE_LINETO:
|
||||
path.lineTo(data[i++] * mScale, data[i++] * mScale);
|
||||
break;
|
||||
case PATH_TYPE_CURVETO:
|
||||
path.cubicTo(
|
||||
data[i++] * mScale,
|
||||
data[i++] * mScale,
|
||||
data[i++] * mScale,
|
||||
data[i++] * mScale,
|
||||
data[i++] * mScale,
|
||||
data[i++] * mScale);
|
||||
break;
|
||||
case PATH_TYPE_ARC:
|
||||
{
|
||||
float x = data[i++] * mScale;
|
||||
float y = data[i++] * mScale;
|
||||
float r = data[i++] * mScale;
|
||||
float start = (float) Math.toDegrees(data[i++]);
|
||||
float end = (float) Math.toDegrees(data[i++]);
|
||||
boolean clockwise = data[i++] == 0f;
|
||||
if (!clockwise) {
|
||||
end = 360 - end;
|
||||
}
|
||||
float sweep = start - end;
|
||||
RectF oval = new RectF(x - r, y - r, x + r, y + r);
|
||||
path.addArc(oval, start, sweep);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new JSApplicationIllegalArgumentException(
|
||||
"Unrecognized drawing instruction " + type);
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
package com.horcrux.svg;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
//import com.facebook.react.uimanager.ReactStylesDiffMap;
|
||||
@@ -23,66 +24,66 @@ import com.facebook.react.uimanager.ViewManager;
|
||||
*/
|
||||
public class RNSVGRenderableViewManager extends ViewManager<View, ReactShadowNode> {
|
||||
|
||||
/* 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_GROUP = "RNSVGGroup";
|
||||
/* package */ static final String CLASS_SVG = "RNSVGPath";
|
||||
/* package */ static final String CLASS_TEXT = "RNSVGText";
|
||||
|
||||
private final String mClassName;
|
||||
private final String mClassName;
|
||||
|
||||
public static RNSVGRenderableViewManager createRNSVGGroupViewManager() {
|
||||
return new RNSVGRenderableViewManager(CLASS_GROUP);
|
||||
}
|
||||
|
||||
public static RNSVGRenderableViewManager createRNSVGPathViewManager() {
|
||||
return new RNSVGRenderableViewManager(CLASS_SVG);
|
||||
}
|
||||
|
||||
public static RNSVGRenderableViewManager createRNSVGTextViewManager() {
|
||||
return new RNSVGRenderableViewManager(CLASS_TEXT);
|
||||
}
|
||||
|
||||
private RNSVGRenderableViewManager(String className) {
|
||||
mClassName = className;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return mClassName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactShadowNode createShadowNodeInstance() {
|
||||
if (mClassName == CLASS_GROUP) {
|
||||
return new RNSVGGroupShadowNode();
|
||||
} else if (mClassName == CLASS_SVG) {
|
||||
return new RNSVGPathShadowNode();
|
||||
} else if (mClassName == CLASS_TEXT) {
|
||||
return new RNSVGTextShadowNode();
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected type " + mClassName);
|
||||
public static RNSVGRenderableViewManager createRNSVGGroupViewManager() {
|
||||
return new RNSVGRenderableViewManager(CLASS_GROUP);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ReactShadowNode> getShadowNodeClass() {
|
||||
if (mClassName == CLASS_GROUP) {
|
||||
return RNSVGGroupShadowNode.class;
|
||||
} else if (mClassName == CLASS_SVG) {
|
||||
return RNSVGPathShadowNode.class;
|
||||
} else if (mClassName == CLASS_TEXT) {
|
||||
return RNSVGTextShadowNode.class;
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected type " + mClassName);
|
||||
public static RNSVGRenderableViewManager createRNSVGPathViewManager() {
|
||||
return new RNSVGRenderableViewManager(CLASS_SVG);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View createViewInstance(ThemedReactContext reactContext) {
|
||||
throw new IllegalStateException("RNSVGPath does not map into a native view");
|
||||
}
|
||||
public static RNSVGRenderableViewManager createRNSVGTextViewManager() {
|
||||
return new RNSVGRenderableViewManager(CLASS_TEXT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateExtraData(View root, Object extraData) {
|
||||
throw new IllegalStateException("RNSVGPath does not map into a native view");
|
||||
}
|
||||
private RNSVGRenderableViewManager(String className) {
|
||||
mClassName = className;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return mClassName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactShadowNode createShadowNodeInstance() {
|
||||
if (mClassName == CLASS_GROUP) {
|
||||
return new RNSVGGroupShadowNode();
|
||||
} else if (mClassName == CLASS_SVG) {
|
||||
return new RNSVGPathShadowNode();
|
||||
} else if (mClassName == CLASS_TEXT) {
|
||||
return new RNSVGTextShadowNode();
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected type " + mClassName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ReactShadowNode> getShadowNodeClass() {
|
||||
if (mClassName == CLASS_GROUP) {
|
||||
return RNSVGGroupShadowNode.class;
|
||||
} else if (mClassName == CLASS_SVG) {
|
||||
return RNSVGPathShadowNode.class;
|
||||
} else if (mClassName == CLASS_TEXT) {
|
||||
return RNSVGTextShadowNode.class;
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected type " + mClassName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View createViewInstance(ThemedReactContext reactContext) {
|
||||
throw new IllegalStateException("RNSVGPath does not map into a native view");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateExtraData(View root, Object extraData) {
|
||||
throw new IllegalStateException("RNSVGPath does not map into a native view");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,25 +21,25 @@ import android.view.View;
|
||||
*/
|
||||
public class RNSVGSvgView extends View {
|
||||
|
||||
private @Nullable Bitmap mBitmap;
|
||||
private @Nullable Bitmap mBitmap;
|
||||
|
||||
public RNSVGSvgView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public void setBitmap(Bitmap bitmap) {
|
||||
if (mBitmap != null) {
|
||||
mBitmap.recycle();
|
||||
public RNSVGSvgView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
mBitmap = bitmap;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
if (mBitmap != null) {
|
||||
canvas.drawBitmap(mBitmap, 0, 0, null);
|
||||
public void setBitmap(Bitmap bitmap) {
|
||||
if (mBitmap != null) {
|
||||
mBitmap.recycle();
|
||||
}
|
||||
mBitmap = bitmap;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
if (mBitmap != null) {
|
||||
canvas.drawBitmap(mBitmap, 0, 0, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,39 +23,39 @@ import com.facebook.react.uimanager.ThemedReactContext;
|
||||
public class RNSVGSvgViewManager extends
|
||||
BaseViewManager<RNSVGSvgView, RNSVGSvgViewShadowNode> {
|
||||
|
||||
private static final String REACT_CLASS = "RNSVGSvgView";
|
||||
private static final String REACT_CLASS = "RNSVGSvgView";
|
||||
|
||||
private static final CSSNode.MeasureFunction MEASURE_FUNCTION = new CSSNode.MeasureFunction() {
|
||||
@Override
|
||||
public void measure(CSSNode node, float width, float height, MeasureOutput measureOutput) {
|
||||
throw new IllegalStateException("SvgView should have explicit width and height set");
|
||||
}
|
||||
};
|
||||
|
||||
private static final CSSNode.MeasureFunction MEASURE_FUNCTION = new CSSNode.MeasureFunction() {
|
||||
@Override
|
||||
public void measure(CSSNode node, float width, float height, MeasureOutput measureOutput) {
|
||||
throw new IllegalStateException("SvgView should have explicit width and height set");
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
@Override
|
||||
public RNSVGSvgViewShadowNode createShadowNodeInstance() {
|
||||
RNSVGSvgViewShadowNode node = new RNSVGSvgViewShadowNode();
|
||||
node.setMeasureFunction(MEASURE_FUNCTION);
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RNSVGSvgViewShadowNode createShadowNodeInstance() {
|
||||
RNSVGSvgViewShadowNode node = new RNSVGSvgViewShadowNode();
|
||||
node.setMeasureFunction(MEASURE_FUNCTION);
|
||||
return node;
|
||||
}
|
||||
@Override
|
||||
public Class<RNSVGSvgViewShadowNode> getShadowNodeClass() {
|
||||
return RNSVGSvgViewShadowNode.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<RNSVGSvgViewShadowNode> getShadowNodeClass() {
|
||||
return RNSVGSvgViewShadowNode.class;
|
||||
}
|
||||
@Override
|
||||
protected RNSVGSvgView createViewInstance(ThemedReactContext reactContext) {
|
||||
return new RNSVGSvgView(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RNSVGSvgView createViewInstance(ThemedReactContext reactContext) {
|
||||
return new RNSVGSvgView(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateExtraData(RNSVGSvgView root, Object extraData) {
|
||||
root.setBitmap((Bitmap) extraData);
|
||||
}
|
||||
@Override
|
||||
public void updateExtraData(RNSVGSvgView root, Object extraData) {
|
||||
root.setBitmap((Bitmap) extraData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,36 +21,36 @@ import com.facebook.react.uimanager.UIViewOperationQueue;
|
||||
*/
|
||||
public class RNSVGSvgViewShadowNode extends LayoutShadowNode {
|
||||
|
||||
@Override
|
||||
public boolean isVirtual() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVirtualAnchor() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCollectExtraUpdates(UIViewOperationQueue uiUpdater) {
|
||||
super.onCollectExtraUpdates(uiUpdater);
|
||||
uiUpdater.enqueueUpdateExtraData(getReactTag(), drawOutput());
|
||||
}
|
||||
|
||||
private Object drawOutput() {
|
||||
// TODO(7255985): Use TextureView and pass Svg from the view to draw on it asynchronously
|
||||
// instead of passing the bitmap (which is inefficient especially in terms of memory usage)
|
||||
Bitmap bitmap = Bitmap.createBitmap(
|
||||
(int) getLayoutWidth(),
|
||||
(int) getLayoutHeight(),
|
||||
Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
Paint paint = new Paint();
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
RNSVGVirtualNode child = (RNSVGVirtualNode) getChildAt(i);
|
||||
child.draw(canvas, paint, 1f);
|
||||
child.markUpdateSeen();
|
||||
@Override
|
||||
public boolean isVirtual() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVirtualAnchor() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCollectExtraUpdates(UIViewOperationQueue uiUpdater) {
|
||||
super.onCollectExtraUpdates(uiUpdater);
|
||||
uiUpdater.enqueueUpdateExtraData(getReactTag(), drawOutput());
|
||||
}
|
||||
|
||||
private Object drawOutput() {
|
||||
// TODO(7255985): Use TextureView and pass Svg from the view to draw on it asynchronously
|
||||
// instead of passing the bitmap (which is inefficient especially in terms of memory usage)
|
||||
Bitmap bitmap = Bitmap.createBitmap(
|
||||
(int) getLayoutWidth(),
|
||||
(int) getLayoutHeight(),
|
||||
Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
Paint paint = new Paint();
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
RNSVGVirtualNode child = (RNSVGVirtualNode) getChildAt(i);
|
||||
child.draw(canvas, paint, 1f);
|
||||
child.markUpdateSeen();
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,117 +25,117 @@ import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
*/
|
||||
public class RNSVGTextShadowNode extends RNSVGPathShadowNode {
|
||||
|
||||
private static final String PROP_LINES = "lines";
|
||||
private static final String PROP_LINES = "lines";
|
||||
|
||||
private static final String PROP_FONT = "font";
|
||||
private static final String PROP_FONT_FAMILY = "fontFamily";
|
||||
private static final String PROP_FONT_SIZE = "fontSize";
|
||||
private static final String PROP_FONT_STYLE = "fontStyle";
|
||||
private static final String PROP_FONT_WEIGHT = "fontWeight";
|
||||
private static final String PROP_FONT = "font";
|
||||
private static final String PROP_FONT_FAMILY = "fontFamily";
|
||||
private static final String PROP_FONT_SIZE = "fontSize";
|
||||
private static final String PROP_FONT_STYLE = "fontStyle";
|
||||
private static final String PROP_FONT_WEIGHT = "fontWeight";
|
||||
|
||||
private static final int DEFAULT_FONT_SIZE = 12;
|
||||
private static final int DEFAULT_FONT_SIZE = 12;
|
||||
|
||||
private static final int TEXT_ALIGNMENT_CENTER = 2;
|
||||
private static final int TEXT_ALIGNMENT_LEFT = 0;
|
||||
private static final int TEXT_ALIGNMENT_RIGHT = 1;
|
||||
private static final int TEXT_ALIGNMENT_CENTER = 2;
|
||||
private static final int TEXT_ALIGNMENT_LEFT = 0;
|
||||
private static final int TEXT_ALIGNMENT_RIGHT = 1;
|
||||
|
||||
private @Nullable ReadableMap mFrame;
|
||||
private int mTextAlignment = TEXT_ALIGNMENT_LEFT;
|
||||
private @Nullable ReadableMap mFrame;
|
||||
private int mTextAlignment = TEXT_ALIGNMENT_LEFT;
|
||||
|
||||
@ReactProp(name = "frame")
|
||||
public void setFrame(@Nullable ReadableMap frame) {
|
||||
mFrame = frame;
|
||||
}
|
||||
|
||||
@ReactProp(name = "alignment", defaultInt = TEXT_ALIGNMENT_LEFT)
|
||||
public void setAlignment(int alignment) {
|
||||
mTextAlignment = alignment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas, Paint paint, float opacity) {
|
||||
if (mFrame == null) {
|
||||
return;
|
||||
}
|
||||
opacity *= mOpacity;
|
||||
if (opacity <= MIN_OPACITY_FOR_DRAW) {
|
||||
return;
|
||||
}
|
||||
if (!mFrame.hasKey(PROP_LINES)) {
|
||||
return;
|
||||
}
|
||||
ReadableArray linesProp = mFrame.getArray(PROP_LINES);
|
||||
if (linesProp == null || linesProp.size() == 0) {
|
||||
return;
|
||||
@ReactProp(name = "frame")
|
||||
public void setFrame(@Nullable ReadableMap frame) {
|
||||
mFrame = frame;
|
||||
}
|
||||
|
||||
// only set up the canvas if we have something to draw
|
||||
saveAndSetupCanvas(canvas);
|
||||
String[] lines = new String[linesProp.size()];
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
lines[i] = linesProp.getString(i);
|
||||
@ReactProp(name = "alignment", defaultInt = TEXT_ALIGNMENT_LEFT)
|
||||
public void setAlignment(int alignment) {
|
||||
mTextAlignment = alignment;
|
||||
}
|
||||
String text = TextUtils.join("\n", lines);
|
||||
if (setupStrokePaint(paint, opacity)) {
|
||||
applyTextPropertiesToPaint(paint);
|
||||
if (mPath == null) {
|
||||
canvas.drawText(text, 0, -paint.ascent(), paint);
|
||||
} else {
|
||||
canvas.drawTextOnPath(text, mPath, 0, 0, paint);
|
||||
}
|
||||
}
|
||||
if (setupFillPaint(paint, opacity)) {
|
||||
applyTextPropertiesToPaint(paint);
|
||||
if (mPath == null) {
|
||||
canvas.drawText(text, 0, -paint.ascent(), paint);
|
||||
} else {
|
||||
canvas.drawTextOnPath(text, mPath, 0, 0, paint);
|
||||
}
|
||||
}
|
||||
restoreCanvas(canvas);
|
||||
markUpdateSeen();
|
||||
}
|
||||
|
||||
private void applyTextPropertiesToPaint(Paint paint) {
|
||||
int alignment = mTextAlignment;
|
||||
switch (alignment) {
|
||||
case TEXT_ALIGNMENT_LEFT:
|
||||
paint.setTextAlign(Paint.Align.LEFT);
|
||||
break;
|
||||
case TEXT_ALIGNMENT_RIGHT:
|
||||
paint.setTextAlign(Paint.Align.RIGHT);
|
||||
break;
|
||||
case TEXT_ALIGNMENT_CENTER:
|
||||
paint.setTextAlign(Paint.Align.CENTER);
|
||||
break;
|
||||
}
|
||||
if (mFrame != null) {
|
||||
if (mFrame.hasKey(PROP_FONT)) {
|
||||
ReadableMap font = mFrame.getMap(PROP_FONT);
|
||||
if (font != null) {
|
||||
float fontSize = DEFAULT_FONT_SIZE;
|
||||
if (font.hasKey(PROP_FONT_SIZE)) {
|
||||
fontSize = (float) font.getDouble(PROP_FONT_SIZE);
|
||||
}
|
||||
paint.setTextSize(fontSize * mScale);
|
||||
boolean isBold =
|
||||
font.hasKey(PROP_FONT_WEIGHT) && "bold".equals(font.getString(PROP_FONT_WEIGHT));
|
||||
boolean isItalic =
|
||||
font.hasKey(PROP_FONT_STYLE) && "italic".equals(font.getString(PROP_FONT_STYLE));
|
||||
int fontStyle;
|
||||
if (isBold && isItalic) {
|
||||
fontStyle = Typeface.BOLD_ITALIC;
|
||||
} else if (isBold) {
|
||||
fontStyle = Typeface.BOLD;
|
||||
} else if (isItalic) {
|
||||
fontStyle = Typeface.ITALIC;
|
||||
} else {
|
||||
fontStyle = Typeface.NORMAL;
|
||||
}
|
||||
// NB: if the font family is null / unsupported, the default one will be used
|
||||
paint.setTypeface(Typeface.create(font.getString(PROP_FONT_FAMILY), fontStyle));
|
||||
@Override
|
||||
public void draw(Canvas canvas, Paint paint, float opacity) {
|
||||
if (mFrame == null) {
|
||||
return;
|
||||
}
|
||||
opacity *= mOpacity;
|
||||
if (opacity <= MIN_OPACITY_FOR_DRAW) {
|
||||
return;
|
||||
}
|
||||
if (!mFrame.hasKey(PROP_LINES)) {
|
||||
return;
|
||||
}
|
||||
ReadableArray linesProp = mFrame.getArray(PROP_LINES);
|
||||
if (linesProp == null || linesProp.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// only set up the canvas if we have something to draw
|
||||
saveAndSetupCanvas(canvas);
|
||||
String[] lines = new String[linesProp.size()];
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
lines[i] = linesProp.getString(i);
|
||||
}
|
||||
String text = TextUtils.join("\n", lines);
|
||||
if (setupStrokePaint(paint, opacity)) {
|
||||
applyTextPropertiesToPaint(paint);
|
||||
if (mPath == null) {
|
||||
canvas.drawText(text, 0, -paint.ascent(), paint);
|
||||
} else {
|
||||
canvas.drawTextOnPath(text, mPath, 0, 0, paint);
|
||||
}
|
||||
}
|
||||
if (setupFillPaint(paint, opacity)) {
|
||||
applyTextPropertiesToPaint(paint);
|
||||
if (mPath == null) {
|
||||
canvas.drawText(text, 0, -paint.ascent(), paint);
|
||||
} else {
|
||||
canvas.drawTextOnPath(text, mPath, 0, 0, paint);
|
||||
}
|
||||
}
|
||||
restoreCanvas(canvas);
|
||||
markUpdateSeen();
|
||||
}
|
||||
|
||||
private void applyTextPropertiesToPaint(Paint paint) {
|
||||
int alignment = mTextAlignment;
|
||||
switch (alignment) {
|
||||
case TEXT_ALIGNMENT_LEFT:
|
||||
paint.setTextAlign(Paint.Align.LEFT);
|
||||
break;
|
||||
case TEXT_ALIGNMENT_RIGHT:
|
||||
paint.setTextAlign(Paint.Align.RIGHT);
|
||||
break;
|
||||
case TEXT_ALIGNMENT_CENTER:
|
||||
paint.setTextAlign(Paint.Align.CENTER);
|
||||
break;
|
||||
}
|
||||
if (mFrame != null) {
|
||||
if (mFrame.hasKey(PROP_FONT)) {
|
||||
ReadableMap font = mFrame.getMap(PROP_FONT);
|
||||
if (font != null) {
|
||||
float fontSize = DEFAULT_FONT_SIZE;
|
||||
if (font.hasKey(PROP_FONT_SIZE)) {
|
||||
fontSize = (float) font.getDouble(PROP_FONT_SIZE);
|
||||
}
|
||||
paint.setTextSize(fontSize * mScale);
|
||||
boolean isBold =
|
||||
font.hasKey(PROP_FONT_WEIGHT) && "bold".equals(font.getString(PROP_FONT_WEIGHT));
|
||||
boolean isItalic =
|
||||
font.hasKey(PROP_FONT_STYLE) && "italic".equals(font.getString(PROP_FONT_STYLE));
|
||||
int fontStyle;
|
||||
if (isBold && isItalic) {
|
||||
fontStyle = Typeface.BOLD_ITALIC;
|
||||
} else if (isBold) {
|
||||
fontStyle = Typeface.BOLD;
|
||||
} else if (isItalic) {
|
||||
fontStyle = Typeface.ITALIC;
|
||||
} else {
|
||||
fontStyle = Typeface.NORMAL;
|
||||
}
|
||||
// NB: if the font family is null / unsupported, the default one will be used
|
||||
paint.setTypeface(Typeface.create(font.getString(PROP_FONT_FAMILY), fontStyle));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ package com.horcrux.svg;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
@@ -27,87 +28,87 @@ import com.facebook.react.uimanager.ReactShadowNode;
|
||||
*/
|
||||
public abstract class RNSVGVirtualNode extends ReactShadowNode {
|
||||
|
||||
protected static final float MIN_OPACITY_FOR_DRAW = 0.01f;
|
||||
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 static final float[] sMatrixData = new float[9];
|
||||
private static final float[] sRawMatrix = new float[9];
|
||||
|
||||
protected float mOpacity = 1f;
|
||||
private @Nullable Matrix mMatrix = new Matrix();
|
||||
protected float mOpacity = 1f;
|
||||
private @Nullable Matrix mMatrix = new Matrix();
|
||||
|
||||
protected final float mScale;
|
||||
protected final float mScale;
|
||||
|
||||
public RNSVGVirtualNode() {
|
||||
mScale = DisplayMetricsHolder.getWindowDisplayMetrics().density;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVirtual() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public abstract void draw(Canvas canvas, Paint paint, float opacity);
|
||||
|
||||
/**
|
||||
* Sets up the transform matrix on the canvas before an element is drawn.
|
||||
*
|
||||
* NB: for perf reasons this does not apply opacity, as that would mean creating a new canvas
|
||||
* layer (which allocates an offscreen bitmap) and having it composited afterwards. Instead, the
|
||||
* drawing code should apply opacity recursively.
|
||||
*
|
||||
* @param canvas the canvas to set up
|
||||
*/
|
||||
protected final void saveAndSetupCanvas(Canvas canvas) {
|
||||
canvas.save();
|
||||
if (mMatrix != null) {
|
||||
canvas.concat(mMatrix);
|
||||
public RNSVGVirtualNode() {
|
||||
mScale = DisplayMetricsHolder.getWindowDisplayMetrics().density;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the canvas after an element was drawn. This is always called in mirror with
|
||||
* {@link #saveAndSetupCanvas}.
|
||||
*
|
||||
* @param canvas the canvas to restore
|
||||
*/
|
||||
protected void restoreCanvas(Canvas canvas) {
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@ReactProp(name = "opacity", defaultFloat = 1f)
|
||||
public void setOpacity(float opacity) {
|
||||
mOpacity = opacity;
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@ReactProp(name = "transform")
|
||||
public void setTransform(@Nullable ReadableArray transformArray) {
|
||||
if (transformArray != null) {
|
||||
int matrixSize = PropHelper.toFloatArray(transformArray, sMatrixData);
|
||||
if (matrixSize == 6) {
|
||||
setupMatrix();
|
||||
} else if (matrixSize != -1) {
|
||||
throw new JSApplicationIllegalArgumentException("Transform matrices must be of size 6");
|
||||
}
|
||||
} else {
|
||||
mMatrix = null;
|
||||
@Override
|
||||
public boolean isVirtual() {
|
||||
return true;
|
||||
}
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
protected void setupMatrix() {
|
||||
sRawMatrix[0] = sMatrixData[0];
|
||||
sRawMatrix[1] = sMatrixData[2];
|
||||
sRawMatrix[2] = sMatrixData[4] * mScale;
|
||||
sRawMatrix[3] = sMatrixData[1];
|
||||
sRawMatrix[4] = sMatrixData[3];
|
||||
sRawMatrix[5] = sMatrixData[5] * mScale;
|
||||
sRawMatrix[6] = 0;
|
||||
sRawMatrix[7] = 0;
|
||||
sRawMatrix[8] = 1;
|
||||
if (mMatrix == null) {
|
||||
mMatrix = new Matrix();
|
||||
public abstract void draw(Canvas canvas, Paint paint, float opacity);
|
||||
|
||||
/**
|
||||
* Sets up the transform matrix on the canvas before an element is drawn.
|
||||
*
|
||||
* NB: for perf reasons this does not apply opacity, as that would mean creating a new canvas
|
||||
* layer (which allocates an offscreen bitmap) and having it composited afterwards. Instead, the
|
||||
* drawing code should apply opacity recursively.
|
||||
*
|
||||
* @param canvas the canvas to set up
|
||||
*/
|
||||
protected final void saveAndSetupCanvas(Canvas canvas) {
|
||||
canvas.save();
|
||||
if (mMatrix != null) {
|
||||
canvas.concat(mMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the canvas after an element was drawn. This is always called in mirror with
|
||||
* {@link #saveAndSetupCanvas}.
|
||||
*
|
||||
* @param canvas the canvas to restore
|
||||
*/
|
||||
protected void restoreCanvas(Canvas canvas) {
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@ReactProp(name = "opacity", defaultFloat = 1f)
|
||||
public void setOpacity(float opacity) {
|
||||
mOpacity = opacity;
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@ReactProp(name = "transform")
|
||||
public void setTransform(@Nullable ReadableArray transformArray) {
|
||||
if (transformArray != null) {
|
||||
int matrixSize = PropHelper.toFloatArray(transformArray, sMatrixData);
|
||||
if (matrixSize == 6) {
|
||||
setupMatrix();
|
||||
} else if (matrixSize != -1) {
|
||||
throw new JSApplicationIllegalArgumentException("Transform matrices must be of size 6");
|
||||
}
|
||||
} else {
|
||||
mMatrix = null;
|
||||
}
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
protected void setupMatrix() {
|
||||
sRawMatrix[0] = sMatrixData[0];
|
||||
sRawMatrix[1] = sMatrixData[2];
|
||||
sRawMatrix[2] = sMatrixData[4] * mScale;
|
||||
sRawMatrix[3] = sMatrixData[1];
|
||||
sRawMatrix[4] = sMatrixData[3];
|
||||
sRawMatrix[5] = sMatrixData[5] * mScale;
|
||||
sRawMatrix[6] = 0;
|
||||
sRawMatrix[7] = 0;
|
||||
sRawMatrix[8] = 1;
|
||||
if (mMatrix == null) {
|
||||
mMatrix = new Matrix();
|
||||
}
|
||||
mMatrix.setValues(sRawMatrix);
|
||||
}
|
||||
mMatrix.setValues(sRawMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user