mirror of
https://github.com/zoriya/react-native-svg.git
synced 2026-06-06 16:32:24 +00:00
finish basic Text features on Android
This commit is contained in:
@@ -35,6 +35,7 @@ public class RNSVGRenderableViewManager extends ViewGroupManager<ViewGroup> {
|
||||
/* package */ static final String CLASS_VIEW_BOX = "RNSVGViewBox";
|
||||
/* package */ static final String CLASS_LINEAR_GRADIENT = "RNSVGLinearGradient";
|
||||
/* package */ static final String CLASS_RADIAL_GRADIENT = "RNSVGRadialGradient";
|
||||
/* package */ static final String CLASS_SPAN = "RNSVGSpan";
|
||||
|
||||
private final String mClassName;
|
||||
|
||||
@@ -96,6 +97,10 @@ public class RNSVGRenderableViewManager extends ViewGroupManager<ViewGroup> {
|
||||
return new RNSVGRenderableViewManager(CLASS_RADIAL_GRADIENT);
|
||||
}
|
||||
|
||||
public static RNSVGRenderableViewManager createRNSVGSpanManager() {
|
||||
return new RNSVGRenderableViewManager(CLASS_SPAN);
|
||||
}
|
||||
|
||||
private RNSVGRenderableViewManager(String className) {
|
||||
mClassName = className;
|
||||
}
|
||||
@@ -150,6 +155,9 @@ public class RNSVGRenderableViewManager extends ViewGroupManager<ViewGroup> {
|
||||
case CLASS_RADIAL_GRADIENT:
|
||||
mVirtualNode = new RNSVGRadialGradientShadowNode();
|
||||
break;
|
||||
case CLASS_SPAN:
|
||||
mVirtualNode = new RNSVGSpanShadowNode();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unexpected type " + mClassName);
|
||||
}
|
||||
@@ -189,6 +197,8 @@ public class RNSVGRenderableViewManager extends ViewGroupManager<ViewGroup> {
|
||||
return RNSVGLinearGradientShadowNode.class;
|
||||
case CLASS_RADIAL_GRADIENT:
|
||||
return RNSVGRadialGradientShadowNode.class;
|
||||
case CLASS_SPAN:
|
||||
return RNSVGSpanShadowNode.class;
|
||||
default:
|
||||
throw new IllegalStateException("Unexpected type " + mClassName);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Horcrux.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the MIT-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
|
||||
package com.horcrux.svg;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Typeface;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Shadow node for virtual RNSVGPath view
|
||||
*/
|
||||
public class RNSVGSpanShadowNode extends RNSVGPathShadowNode {
|
||||
|
||||
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 float mDx;
|
||||
private float mDy;
|
||||
private @Nullable String mPx;
|
||||
private @Nullable String mPy;
|
||||
private ReadableMap mFont;
|
||||
private String mContent;
|
||||
|
||||
@ReactProp(name = "dx")
|
||||
public void setDx(float dx) {
|
||||
mDx = dx * mScale;
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@ReactProp(name = "dy")
|
||||
public void setDy(float dy) {
|
||||
mDy = dy * mScale;
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@ReactProp(name = "px")
|
||||
public void setPx(String px) {
|
||||
mPx = px;
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@ReactProp(name = "py")
|
||||
public void setPy(String py) {
|
||||
mPy = py;
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@ReactProp(name = "font")
|
||||
public void setFont(ReadableMap font) {
|
||||
mFont = font;
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@ReactProp(name = "content")
|
||||
public void setContent(String content) {
|
||||
mContent = content;
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas, Paint paint, float opacity) {
|
||||
mPath = getPath(canvas, paint);
|
||||
super.draw(canvas, paint, opacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Path getPath(Canvas canvas, Paint paint) {
|
||||
Path path = new Path();
|
||||
RNSVGTextShadowNode text = (RNSVGTextShadowNode)getParent();
|
||||
|
||||
if (text == null) {
|
||||
return path;
|
||||
}
|
||||
|
||||
applyTextPropertiesToPaint(paint);
|
||||
paint.getTextPath(mContent, 0, mContent.length(), 0, 0, path);
|
||||
|
||||
if (!mContent.isEmpty()) {
|
||||
if (mPx != null) {
|
||||
text.setOffsetX(PropHelper.fromPercentageToFloat(mPx, mCanvasWidth, 0, mScale), false);
|
||||
}
|
||||
|
||||
if (mPy != null) {
|
||||
text.setOffsetY(PropHelper.fromPercentageToFloat(mPy, mCanvasHeight, 0, mScale) - paint.ascent(), false);
|
||||
}
|
||||
|
||||
text.setOffsetX(mDx, true);
|
||||
text.setOffsetY(mDy, true);
|
||||
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.setTranslate(text.getOffsetX(), text.getOffsetY());
|
||||
|
||||
text.setOffsetX(getBox(paint).width(), true);
|
||||
|
||||
path.transform(matrix);
|
||||
} else {
|
||||
text.setOffsetX(mDx, true);
|
||||
text.setOffsetY(mDy, true);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private void applyTextPropertiesToPaint(Paint paint) {
|
||||
paint.setTextAlign(Paint.Align.LEFT);
|
||||
|
||||
float fontSize = DEFAULT_FONT_SIZE;
|
||||
if (mFont.hasKey(PROP_FONT_SIZE)) {
|
||||
fontSize = (float) mFont.getDouble(PROP_FONT_SIZE);
|
||||
}
|
||||
paint.setTextSize(fontSize * mScale);
|
||||
boolean isBold = mFont.hasKey(PROP_FONT_WEIGHT) && "bold".equals(mFont.getString(PROP_FONT_WEIGHT));
|
||||
boolean isItalic = mFont.hasKey(PROP_FONT_STYLE) && "italic".equals(mFont.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(mFont.getString(PROP_FONT_FAMILY), fontStyle));
|
||||
}
|
||||
|
||||
public RectF getBox(Paint paint) {
|
||||
applyTextPropertiesToPaint(paint);
|
||||
Rect bound = new Rect();
|
||||
paint.getTextBounds(mContent, 0, mContent.length(), bound);
|
||||
return new RectF(bound);
|
||||
}
|
||||
}
|
||||
@@ -109,13 +109,25 @@ public class RNSVGSvgView extends ViewGroup {
|
||||
}
|
||||
|
||||
private int getAbsoluteLeft(View view) {
|
||||
int left = view.getLeft() - view.getScrollX();
|
||||
|
||||
if (view.getParent() == view.getRootView() || view.getParent() instanceof ReactRootView) {
|
||||
return left;
|
||||
}
|
||||
|
||||
View parent = (View) view.getParent();
|
||||
return view.getLeft() - view.getScrollX() + (parent instanceof ReactRootView ? 0 : getAbsoluteLeft(parent));
|
||||
return left + getAbsoluteLeft(parent);
|
||||
}
|
||||
|
||||
private int getAbsoluteTop(View view) {
|
||||
int top = view.getTop() - view.getScrollY();
|
||||
|
||||
if (view.getParent() == view.getRootView() || view.getParent() instanceof ReactRootView) {
|
||||
return top;
|
||||
}
|
||||
|
||||
View parent = (View) view.getParent();
|
||||
return view.getTop() - view.getScrollY() + (parent instanceof ReactRootView ? 0 : getAbsoluteTop(parent));
|
||||
return top + getAbsoluteTop(parent);
|
||||
}
|
||||
|
||||
private void dispatch(MotionEvent ev, TouchEventType type) {
|
||||
|
||||
@@ -19,8 +19,10 @@ import android.graphics.Path;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Region;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
@@ -30,31 +32,17 @@ import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
/**
|
||||
* Shadow node for virtual RNSVGText view
|
||||
*/
|
||||
public class RNSVGTextShadowNode extends RNSVGPathShadowNode {
|
||||
public class RNSVGTextShadowNode extends RNSVGGroupShadowNode {
|
||||
|
||||
private static final String PROP_LINES = "lines";
|
||||
private float mOffsetX = 0;
|
||||
private float mOffsetY = 0;
|
||||
|
||||
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 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 Path mTextPath;
|
||||
|
||||
@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;
|
||||
@@ -70,173 +58,100 @@ public class RNSVGTextShadowNode extends RNSVGPathShadowNode {
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas, Paint paint, float opacity) {
|
||||
opacity *= mOpacity;
|
||||
if (opacity > MIN_OPACITY_FOR_DRAW) {
|
||||
String text = formatText();
|
||||
if (text == null) {
|
||||
return;
|
||||
}
|
||||
float shift = getShift(paint);
|
||||
|
||||
// only set up the canvas if we have something to draw
|
||||
int count = saveAndSetupCanvas(canvas);
|
||||
clip(canvas, paint);
|
||||
RectF box = getBox(paint, text);
|
||||
final int count = canvas.save();
|
||||
|
||||
if (setupStrokePaint(paint, opacity, box)) {
|
||||
drawText(canvas, paint, text);
|
||||
}
|
||||
if (setupFillPaint(paint, opacity, box)) {
|
||||
drawText(canvas, paint, text);
|
||||
}
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.postTranslate(-shift, 0);
|
||||
canvas.concat(matrix);
|
||||
super.draw(canvas, paint, opacity);
|
||||
|
||||
restoreCanvas(canvas, count);
|
||||
markUpdateSeen();
|
||||
}
|
||||
}
|
||||
|
||||
private void drawText(Canvas canvas, Paint paint, String text) {
|
||||
applyTextPropertiesToPaint(paint);
|
||||
|
||||
if (mTextPath == null) {
|
||||
canvas.drawText(text, 0, -paint.ascent(), paint);
|
||||
} else {
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.setTranslate(0, -paint.getTextSize() * 1.1f);
|
||||
mTextPath.transform(matrix);
|
||||
canvas.drawTextOnPath(text, mTextPath, 0, -paint.ascent(), paint);
|
||||
}
|
||||
}
|
||||
|
||||
private String formatText() {
|
||||
if (mFrame == null || !mFrame.hasKey(PROP_LINES)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ReadableArray linesProp = mFrame.getArray(PROP_LINES);
|
||||
if (linesProp == null || linesProp.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] lines = new String[linesProp.size()];
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
lines[i] = linesProp.getString(i);
|
||||
}
|
||||
return TextUtils.join("\n", lines);
|
||||
}
|
||||
|
||||
private RectF getBox(Paint paint, String text) {
|
||||
Rect bound = new Rect();
|
||||
paint.getTextBounds(text, 0, text.length(), bound);
|
||||
return new RectF(bound);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
restoreCanvas(canvas, count);
|
||||
markUpdateSeen();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Path getPath(Canvas canvas, Paint paint) {
|
||||
Path path = new Path();
|
||||
Path path = getPathFromSuper(canvas, paint);
|
||||
|
||||
String text = formatText();
|
||||
if (text == null) {
|
||||
return path;
|
||||
}
|
||||
|
||||
// TODO: get path while TextPath is set.
|
||||
if (setupFillPaint(paint, 1.0f, getBox(paint, text))) {
|
||||
applyTextPropertiesToPaint(paint);
|
||||
paint.getTextPath(text, 0, text.length(), 0, -paint.ascent(), path);
|
||||
path.transform(mMatrix);
|
||||
}
|
||||
float shift = getShift(paint);
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.setTranslate(shift, 0);
|
||||
path.transform(matrix);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hitTest(Point point, View view, @Nullable Matrix matrix) {
|
||||
Bitmap bitmap = Bitmap.createBitmap(
|
||||
mCanvasWidth,
|
||||
mCanvasHeight,
|
||||
Bitmap.Config.ARGB_8888);
|
||||
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
|
||||
if (matrix != null) {
|
||||
canvas.concat(matrix);
|
||||
}
|
||||
|
||||
canvas.concat(mMatrix);
|
||||
|
||||
String text = formatText();
|
||||
if (text == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
Paint paint = new Paint();
|
||||
clip(canvas, paint);
|
||||
setHitTestFill(paint);
|
||||
drawText(canvas, paint, text);
|
||||
|
||||
if (setHitTestStroke(paint)) {
|
||||
drawText(canvas, paint, text);
|
||||
}
|
||||
|
||||
canvas.setBitmap(bitmap);
|
||||
try {
|
||||
if (bitmap.getPixel(point.x, point.y) != 0) {
|
||||
return view.getId();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return -1;
|
||||
} finally {
|
||||
bitmap.recycle();
|
||||
}
|
||||
return -1;
|
||||
private Path getPathFromSuper(Canvas canvas, Paint paint) {
|
||||
Path path = super.getPath(canvas, paint);
|
||||
// reset offsetX and offsetY
|
||||
mOffsetX = mOffsetY = 0;
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setOffsetX(float x, boolean increase) {
|
||||
if (increase) {
|
||||
mOffsetX += x;
|
||||
} else {
|
||||
mOffsetX = x;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hitTest(Point point, View view) {
|
||||
return this.hitTest(point, view, null);
|
||||
public void setOffsetY(float y, boolean increase) {
|
||||
if (increase) {
|
||||
mOffsetY += y;
|
||||
} else {
|
||||
mOffsetY = y;
|
||||
}
|
||||
}
|
||||
|
||||
public float getOffsetX() {
|
||||
return mOffsetX;
|
||||
}
|
||||
|
||||
public float getOffsetY() {
|
||||
return mOffsetY;
|
||||
}
|
||||
|
||||
private float getShift(Paint paint) {
|
||||
Rect rect = new Rect();
|
||||
|
||||
for (int i = getChildCount() - 1; i >= 0; i--) {
|
||||
if (!(getChildAt(i) instanceof RNSVGVirtualNode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
RectF box = ((RNSVGSpanShadowNode) getChildAt(i)).getBox(paint);
|
||||
|
||||
if (rect.top > box.top) {
|
||||
rect.top = (int)box.top;
|
||||
}
|
||||
if (rect.right < box.right) {
|
||||
rect.right = (int)box.right;
|
||||
}
|
||||
if (rect.bottom < box.bottom) {
|
||||
rect.bottom = (int)box.bottom;
|
||||
}
|
||||
if (rect.left > box.left) {
|
||||
rect.left = (int)box.left;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
float width = rect.width();
|
||||
float shift;
|
||||
|
||||
switch (mTextAlignment) {
|
||||
case TEXT_ALIGNMENT_RIGHT:
|
||||
shift = width;
|
||||
break;
|
||||
case TEXT_ALIGNMENT_LEFT:
|
||||
shift = 0;
|
||||
break;
|
||||
default:
|
||||
shift = width / 2;
|
||||
}
|
||||
return shift;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ public class RNSvgPackage implements ReactPackage {
|
||||
RNSVGRenderableViewManager.createRNSVGViewBoxViewManager(),
|
||||
RNSVGRenderableViewManager.createRNSVGLinearGradientManager(),
|
||||
RNSVGRenderableViewManager.createRNSVGRadialGradientManager(),
|
||||
RNSVGRenderableViewManager.createRNSVGSpanManager(),
|
||||
new RNSVGSvgViewManager());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user