mirror of
https://github.com/zoriya/react-native-svg.git
synced 2026-06-06 00:12:21 +00:00
Merge branch 'master' into patch-1
This commit is contained in:
+7
-15
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user