Merge branch 'master' into patch-1

This commit is contained in:
SaeedZhiany
2019-05-01 17:22:42 +04:30
committed by GitHub
54 changed files with 1089 additions and 273 deletions
+7 -15
View File
@@ -4,31 +4,27 @@ def safeExtGet(prop, fallback) {
buildscript {
repositories {
jcenter()
google()
maven {
url 'https://maven.google.com/'
name 'Google'
}
jcenter()
}
dependencies {
//noinspection GradleDependency
classpath 'com.android.tools.build:gradle:3.3.1'
classpath safeExtGet('gradleBuildTools', 'com.android.tools.build:gradle:3.4.0')
}
}
apply plugin: 'com.android.library'
android {
compileSdkVersion safeExtGet('compileSdkVersion', 27)
compileSdkVersion safeExtGet('compileSdkVersion', 28)
//noinspection GradleDependency
buildToolsVersion safeExtGet('buildToolsVersion', '27.0.3')
buildToolsVersion safeExtGet('buildToolsVersion', '28.0.3')
defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', 16)
//noinspection OldTargetApi
targetSdkVersion safeExtGet('targetSdkVersion', 27)
targetSdkVersion safeExtGet('targetSdkVersion', 28)
}
lintOptions {
abortOnError false
@@ -37,15 +33,11 @@ android {
repositories {
mavenLocal()
jcenter()
google()
jcenter()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$projectDir/../../../node_modules/react-native/android"
}
maven {
url 'https://maven.google.com/'
name 'Google'
url "$rootDir/../node_modules/react-native/android"
}
}
@@ -19,7 +19,7 @@ import javax.annotation.Nullable;
class GlyphContext {
// Current stack (one per node push/pop)
private final ArrayList<FontData> mFontContext = new ArrayList<>();
final ArrayList<FontData> mFontContext = new ArrayList<>();
// Unique input attribute lists (only added if node sets a value)
private final ArrayList<SVGLength[]> mXsContext = new ArrayList<>();
@@ -56,6 +56,14 @@ abstract public class RenderableView extends VirtualView {
private static final int FILL_RULE_EVENODD = 0;
static final int FILL_RULE_NONZERO = 1;
// vectorEffect
static final int VECTOR_EFFECT_DEFAULT = 0;
static final int VECTOR_EFFECT_NON_SCALING_STROKE = 1;
static final int VECTOR_EFFECT_INHERIT = 2;
static final int VECTOR_EFFECT_URI = 3;
public int vectorEffect = VECTOR_EFFECT_DEFAULT;
/*
Used in mergeProperties, keep public
*/
@@ -86,6 +94,12 @@ abstract public class RenderableView extends VirtualView {
private static final Pattern regex = Pattern.compile("[0-9.-]+");
@ReactProp(name = "vectorEffect", defaultInt = VECTOR_EFFECT_DEFAULT)
public void setVectorEffect(int vectorEffect) {
this.vectorEffect = vectorEffect;
invalidate();
}
@ReactProp(name = "fill")
public void setFill(@Nullable Dynamic fill) {
if (fill == null || fill.isNull()) {
@@ -322,7 +336,14 @@ abstract public class RenderableView extends VirtualView {
mPath = getPath(canvas, paint);
mPath.setFillType(fillRule);
}
boolean nonScalingStroke = vectorEffect == VECTOR_EFFECT_NON_SCALING_STROKE;
Path path = mPath;
if (nonScalingStroke) {
Path scaled = new Path();
mPath.transform(canvas.getMatrix(), scaled);
canvas.setMatrix(null);
path = scaled;
}
RectF clientRect = new RectF();
path.computeBounds(clientRect, true);
@@ -13,10 +13,11 @@ import android.graphics.Matrix;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.JavaOnlyMap;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.uimanager.DisplayMetricsHolder;
import com.facebook.react.uimanager.LayoutShadowNode;
import com.facebook.react.uimanager.MatrixMathHelper;
@@ -43,6 +44,7 @@ import static com.facebook.react.uimanager.ViewProps.*;
import static com.horcrux.svg.RenderableView.CAP_ROUND;
import static com.horcrux.svg.RenderableView.FILL_RULE_NONZERO;
import static com.horcrux.svg.RenderableView.JOIN_ROUND;
import static com.horcrux.svg.RenderableView.VECTOR_EFFECT_DEFAULT;
/**
* ViewManager for all RNSVG views
@@ -176,7 +178,9 @@ class RenderableViewManager extends ViewGroupManager<VirtualView> {
}
private static void decomposeMatrix() {
Assertions.assertCondition(sTransformDecompositionArray.length == 16);
if (sTransformDecompositionArray.length != 16) {
throw new AssertionError();
}
// output values
final double[] perspective = sMatrixDecompositionContext.perspective;
@@ -314,8 +318,12 @@ class RenderableViewManager extends ViewGroupManager<VirtualView> {
float scale = DisplayMetricsHolder.getScreenDisplayMetrics().density;
// The following converts the matrix's perspective to a camera distance
// such that the camera perspective looks the same on Android and iOS
float normalizedCameraDistance = scale * cameraDistance * CAMERA_DISTANCE_NORMALIZATION_MULTIPLIER;
// such that the camera perspective looks the same on Android and iOS.
// The native Android implementation removed the screen density from the
// calculation, so squaring and a normalization value of
// sqrt(5) produces an exact replica with iOS.
// For more information, see https://github.com/facebook/react-native/pull/18302
float normalizedCameraDistance = scale * scale * cameraDistance * CAMERA_DISTANCE_NORMALIZATION_MULTIPLIER;
view.setCameraDistance(normalizedCameraDistance);
}
@@ -345,6 +353,22 @@ class RenderableViewManager extends ViewGroupManager<VirtualView> {
public void setFont(GroupView node, @Nullable ReadableMap font) {
node.setFont(font);
}
@ReactProp(name = "fontSize")
public void setFontSize(GroupView node, Dynamic fontSize) {
JavaOnlyMap map = new JavaOnlyMap();
switch (fontSize.getType()) {
case Number:
map.putDouble("fontSize", fontSize.asDouble());
break;
case String:
map.putString("fontSize", fontSize.asString());
break;
default:
return;
}
node.setFont(map);
}
}
@@ -991,17 +1015,26 @@ class RenderableViewManager extends ViewGroupManager<VirtualView> {
node.setStrokeLinejoin(strokeLinejoin);
}
@ReactProp(name = "vectorEffect", defaultInt = VECTOR_EFFECT_DEFAULT)
public void setVectorEffect(RenderableView node, int vectorEffect) {
node.setVectorEffect(vectorEffect);
}
@ReactProp(name = "matrix")
public void setMatrix(VirtualView node, Dynamic matrixArray) {
node.setMatrix(matrixArray);
}
@ReactProp(name = "transform")
public void setTransform(VirtualView node, ReadableArray matrix) {
if (matrix == null) {
public void setTransform(VirtualView node, Dynamic matrix) {
if (matrix.getType() != ReadableType.Array) {
return;
}
ReadableArray ma = matrix.asArray();
if (ma == null) {
resetTransformProperty(node);
} else {
setTransformProperty(node, matrix);
setTransformProperty(node, ma);
}
Matrix m = node.getMatrix();
node.mTransform = m;
@@ -1028,6 +1061,9 @@ class RenderableViewManager extends ViewGroupManager<VirtualView> {
if (view!= null) {
view.invalidate();
}
if (node instanceof TextView) {
((TextView)node).getTextContainer().clearChildCache();
}
}
@Override
@@ -101,8 +101,19 @@ public class SvgView extends ReactViewGroup implements ReactCompoundView, ReactC
if (mBitmap == null) {
mBitmap = drawOutput();
}
if (mBitmap != null)
if (mBitmap != null) {
canvas.drawBitmap(mBitmap, 0, 0, null);
if (toDataUrlTask != null) {
toDataUrlTask.run();
toDataUrlTask = null;
}
}
}
private Runnable toDataUrlTask = null;
void setToDataUrlTask(Runnable task) {
toDataUrlTask = task;
}
@Override
@@ -138,6 +149,10 @@ public class SvgView extends ReactViewGroup implements ReactCompoundView, ReactC
private boolean mRendered = false;
int mTintColor = 0;
boolean isRendered() {
return mRendered;
}
private void clearChildCache() {
if (!mRendered) {
return;
@@ -238,7 +253,7 @@ public class SvgView extends ReactViewGroup implements ReactCompoundView, ReactC
return mCanvas.getClipBounds();
}
void drawChildren(final Canvas canvas) {
synchronized void drawChildren(final Canvas canvas) {
mRendered = true;
mCanvas = canvas;
if (mAlign != null) {
@@ -298,7 +313,27 @@ public class SvgView extends ReactViewGroup implements ReactCompoundView, ReactC
getHeight(),
Bitmap.Config.ARGB_8888);
clearChildCache();
drawChildren(new Canvas(bitmap));
clearChildCache();
this.invalidate();
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
bitmap.recycle();
byte[] bitmapBytes = stream.toByteArray();
return Base64.encodeToString(bitmapBytes, Base64.DEFAULT);
}
String toDataURL(int width, int height) {
Bitmap bitmap = Bitmap.createBitmap(
width,
height,
Bitmap.Config.ARGB_8888);
clearChildCache();
drawChildren(new Canvas(bitmap));
clearChildCache();
this.invalidate();
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
bitmap.recycle();
@@ -28,9 +28,19 @@ class SvgViewManager extends ReactViewManager {
private static final String REACT_CLASS = "RNSVGSvgView";
private static final SparseArray<SvgView> mTagToSvgView = new SparseArray<>();
private static final SparseArray<Runnable> mTagToRunnable = new SparseArray<>();
static void setSvgView(int tag, SvgView svg) {
mTagToSvgView.put(tag, svg);
Runnable task = mTagToRunnable.get(tag);
if (task != null) {
task.run();
mTagToRunnable.delete(tag);
}
}
static void runWhenViewIsAvailable(int tag, Runnable task) {
mTagToRunnable.put(tag, task);
}
static @Nullable SvgView getSvgViewByTag(int tag) {
@@ -13,6 +13,8 @@ import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.UiThreadUtil;
class SvgViewModule extends ReactContextBaseJavaModule {
SvgViewModule(ReactApplicationContext reactContext) {
@@ -24,13 +26,55 @@ class SvgViewModule extends ReactContextBaseJavaModule {
return "RNSVGSvgViewManager";
}
static public void toDataURL(final int tag, final ReadableMap options, final Callback successCallback, final int attempt) {
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
SvgView svg = SvgViewManager.getSvgViewByTag(tag);
if (svg == null) {
SvgViewManager.runWhenViewIsAvailable(tag, new Runnable() {
@Override
public void run() {
SvgView svg = SvgViewManager.getSvgViewByTag(tag);
if (svg == null) { // Should never happen
return;
}
svg.setToDataUrlTask(new Runnable() {
@Override
public void run() {
toDataURL(tag, options, successCallback, attempt + 1);
}
});
}
});
} else if (!svg.isRendered()) {
svg.setToDataUrlTask(new Runnable() {
@Override
public void run() {
toDataURL(tag, options, successCallback, attempt + 1);
}
});
} else {
if (options != null) {
successCallback.invoke(
svg.toDataURL(
options.getInt("width"),
options.getInt("height")
)
);
} else {
successCallback.invoke(svg.toDataURL());
}
}
}
}
);
}
@ReactMethod
public void toDataURL(int tag, Callback successCallback) {
SvgView svg = SvgViewManager.getSvgViewByTag(tag);
if (svg != null) {
successCallback.invoke(svg.toDataURL());
}
public void toDataURL(int tag, ReadableMap options, Callback successCallback) {
toDataURL(tag, options, successCallback, 0);
}
}
@@ -21,6 +21,7 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.os.Build;
import android.view.View;
import android.view.ViewParent;
import com.facebook.react.bridge.ReactContext;
@@ -47,6 +48,7 @@ class TSpanView extends TextView {
private static final String OTF = ".otf";
private static final String TTF = ".ttf";
private Path mCachedPath;
@Nullable String mContent;
private TextPathView textPath;
ArrayList<String> emoji = new ArrayList<>();
@@ -62,6 +64,17 @@ class TSpanView extends TextView {
invalidate();
}
@Override
public void invalidate() {
mCachedPath = null;
super.invalidate();
}
void clearCache() {
mCachedPath = null;
super.clearCache();
}
@Override
void draw(Canvas canvas, Paint paint, float opacity) {
if (mContent != null) {
@@ -88,22 +101,73 @@ class TSpanView extends TextView {
@Override
Path getPath(Canvas canvas, Paint paint) {
if (mPath != null) {
return mPath;
if (mCachedPath != null) {
return mCachedPath;
}
if (mContent == null) {
mPath = getGroupPath(canvas, paint);
return mPath;
mCachedPath = getGroupPath(canvas, paint);
return mCachedPath;
}
setupTextPath();
pushGlyphContext();
mPath = getLinePath(mContent, paint, canvas);
mCachedPath = getLinePath(mContent, paint, canvas);
popGlyphContext();
return mPath;
return mCachedPath;
}
double getSubtreeTextChunksTotalAdvance(Paint paint) {
if (!Double.isNaN(cachedAdvance)) {
return cachedAdvance;
}
double advance = 0;
if (mContent == null) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child instanceof TextView) {
TextView text = (TextView)child;
advance += text.getSubtreeTextChunksTotalAdvance(paint);
}
}
cachedAdvance = advance;
return advance;
}
String line = mContent;
final int length = line.length();
if (length == 0) {
cachedAdvance = 0;
return advance;
}
GlyphContext gc = getTextRootGlyphContext();
FontData font = gc.getFont();
applyTextPropertiesToPaint(paint, font);
double letterSpacing = font.letterSpacing;
final boolean allowOptionalLigatures = letterSpacing == 0 &&
font.fontVariantLigatures == FontVariantLigatures.normal;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
String required = "'rlig', 'liga', 'clig', 'calt', 'locl', 'ccmp', 'mark', 'mkmk',";
String defaultFeatures = required + "'kern', ";
if (allowOptionalLigatures) {
String additionalLigatures = "'hlig', 'cala', ";
paint.setFontFeatureSettings(defaultFeatures + additionalLigatures + font.fontFeatureSettings);
} else {
String disableDiscretionaryLigatures = "'liga' 0, 'clig' 0, 'dlig' 0, 'hlig' 0, 'cala' 0, ";
paint.setFontFeatureSettings(defaultFeatures + disableDiscretionaryLigatures + font.fontFeatureSettings);
}
paint.setLetterSpacing((float)(letterSpacing / (font.fontSize * mScale)));
}
cachedAdvance = paint.measureText(line);
return cachedAdvance;
}
@SuppressWarnings("ConstantConditions")
@@ -311,8 +375,10 @@ class TSpanView extends TextView {
attributes, such as a dx attribute value on a tspan element.
*/
final TextAnchor textAnchor = font.textAnchor;
final double textMeasure = paint.measureText(line);
TextView anchorRoot = getTextAnchorRoot();
final double textMeasure = anchorRoot.getSubtreeTextChunksTotalAdvance(paint);
double offset = getTextAnchorOffset(textAnchor, textMeasure);
applyTextPropertiesToPaint(paint, font);
int side = 1;
double startOfRendering = 0;
@@ -563,7 +629,7 @@ class TSpanView extends TextView {
// this will just retrieve the bounding rect for 'x'
paint.getTextBounds("x", 0, 1, bounds);
int xHeight = bounds.height();
baselineShift = xHeight / 2;
baselineShift = xHeight / 2.0;
break;
case central:
@@ -691,7 +757,6 @@ class TSpanView extends TextView {
final Matrix end = new Matrix();
final float[] startPointMatrixData = new float[9];
final float[] midPointMatrixData = new float[9];
final float[] endPointMatrixData = new float[9];
emoji.clear();
@@ -961,6 +1026,9 @@ class TSpanView extends TextView {
paint.setTypeface(typeface);
paint.setTextSize((float) fontSize);
paint.setTextAlign(Paint.Align.LEFT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
paint.setLetterSpacing(0);
}
// Do these have any effect for anyone? Not for me (@msand) at least.
// paint.setUnderlineText(underlineText);
@@ -1000,6 +1068,9 @@ class TSpanView extends TextView {
if (mRegion == null && mFillPath != null) {
mRegion = getRegion(mFillPath);
}
if (mRegion == null && mPath != null) {
mRegion = getRegion(mPath);
}
if (mStrokeRegion == null && mStrokePath != null) {
mStrokeRegion = getRegion(mStrokePath);
}
@@ -14,6 +14,7 @@ import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Region;
import android.view.View;
import android.view.ViewParent;
import com.facebook.react.bridge.Dynamic;
@@ -38,6 +39,7 @@ class TextView extends GroupView {
@Nullable ArrayList<SVGLength> mRotate;
@Nullable ArrayList<SVGLength> mDeltaX;
@Nullable ArrayList<SVGLength> mDeltaY;
double cachedAdvance = Double.NaN;
public TextView(ReactContext reactContext) {
super(reactContext);
@@ -49,7 +51,12 @@ class TextView extends GroupView {
return;
}
super.invalidate();
clearChildCache();
getTextContainer().clearChildCache();
}
void clearCache() {
cachedAdvance = Double.NaN;
super.clearCache();
}
@ReactProp(name = "textLength")
@@ -207,4 +214,45 @@ class TextView extends GroupView {
boolean isTextNode = !(this instanceof TextPathView) && !(this instanceof TSpanView);
getTextRootGlyphContext().pushContext(isTextNode, this, mFont, mPositionX, mPositionY, mDeltaX, mDeltaY, mRotate);
}
TextView getTextAnchorRoot() {
GlyphContext gc = getTextRootGlyphContext();
ArrayList<FontData> font = gc.mFontContext;
TextView node = this;
ViewParent parent = this.getParent();
for (int i = font.size() - 1; i >= 0; i--) {
if (!(parent instanceof TextView) || font.get(i).textAnchor == TextProperties.TextAnchor.start || node.mPositionX != null) {
return node;
}
node = (TextView) parent;
parent = node.getParent();
}
return node;
}
double getSubtreeTextChunksTotalAdvance(Paint paint) {
if (!Double.isNaN(cachedAdvance)) {
return cachedAdvance;
}
double advance = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child instanceof TextView) {
TextView text = (TextView) child;
advance += text.getSubtreeTextChunksTotalAdvance(paint);
}
}
cachedAdvance = advance;
return advance;
}
TextView getTextContainer() {
TextView node = this;
ViewParent parent = this.getParent();
while (parent instanceof TextView) {
node = (TextView) parent;
parent = node.getParent();
}
return node;
}
}
@@ -11,6 +11,7 @@ package com.horcrux.svg;
import android.annotation.SuppressLint;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
@@ -66,31 +67,33 @@ class UseView extends RenderableView {
void draw(Canvas canvas, Paint paint, float opacity) {
VirtualView template = getSvgView().getDefinedTemplate(mHref);
if (template != null) {
canvas.translate((float) relativeOnWidth(mX), (float) relativeOnHeight(mY));
if (template instanceof RenderableView) {
((RenderableView)template).mergeProperties(this);
}
int count = template.saveAndSetupCanvas(canvas);
clip(canvas, paint);
if (template instanceof SymbolView) {
SymbolView symbol = (SymbolView)template;
symbol.drawSymbol(canvas, paint, opacity, (float) relativeOnWidth(mW), (float) relativeOnHeight(mH));
} else {
template.draw(canvas, paint, opacity * mOpacity);
}
this.setClientRect(template.getClientRect());
template.restoreCanvas(canvas, count);
if (template instanceof RenderableView) {
((RenderableView)template).resetProperties();
}
} else {
if (template == null) {
FLog.w(ReactConstants.TAG, "`Use` element expected a pre-defined svg template as `href` prop, " +
"template named: " + mHref + " is not defined.");
"template named: " + mHref + " is not defined.");
return;
}
template.clearCache();
canvas.translate((float) relativeOnWidth(mX), (float) relativeOnHeight(mY));
if (template instanceof RenderableView) {
((RenderableView)template).mergeProperties(this);
}
int count = template.saveAndSetupCanvas(canvas);
clip(canvas, paint);
if (template instanceof SymbolView) {
SymbolView symbol = (SymbolView)template;
symbol.drawSymbol(canvas, paint, opacity, (float) relativeOnWidth(mW), (float) relativeOnHeight(mH));
} else {
template.draw(canvas, paint, opacity * mOpacity);
}
this.setClientRect(template.getClientRect());
template.restoreCanvas(canvas, count);
if (template instanceof RenderableView) {
((RenderableView)template).resetProperties();
}
}
@@ -105,6 +108,12 @@ class UseView extends RenderableView {
mInvTransform.mapPoints(dst);
VirtualView template = getSvgView().getDefinedTemplate(mHref);
if (template == null) {
FLog.w(ReactConstants.TAG, "`Use` element expected a pre-defined svg template as `href` prop, " +
"template named: " + mHref + " is not defined.");
return -1;
}
int hitChild = template.hitTest(dst);
if (hitChild != -1) {
return (template.isResponsible() || hitChild != template.getId()) ? hitChild : getId();
@@ -115,7 +124,17 @@ class UseView extends RenderableView {
@Override
Path getPath(Canvas canvas, Paint paint) {
// todo:
return new Path();
VirtualView template = getSvgView().getDefinedTemplate(mHref);
if (template == null) {
FLog.w(ReactConstants.TAG, "`Use` element expected a pre-defined svg template as `href` prop, " +
"template named: " + mHref + " is not defined.");
return null;
}
Path path = template.getPath(canvas, paint);
Path use = new Path();
Matrix m = new Matrix();
m.setTranslate((float) relativeOnWidth(mX), (float) relativeOnHeight(mY));
path.transform(m, use);
return use;
}
}
@@ -101,7 +101,7 @@ abstract public class VirtualView extends ReactViewGroup {
super.invalidate();
}
private void clearCache() {
void clearCache() {
canvasDiagonal = -1;
canvasHeight = -1;
canvasWidth = -1;