mirror of
https://github.com/zoriya/react-native-svg.git
synced 2025-12-21 06:15:15 +00:00
Implement correct path measurement, matrix calculation, getTextAnchorShift, startOffset.
Add method and spacing attributes to textPath. Correct startOffset calculation. Implement method="stretch".
This commit is contained in:
@@ -1,186 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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.Matrix;
|
|
||||||
import android.graphics.PointF;
|
|
||||||
|
|
||||||
import com.facebook.react.bridge.ReadableArray;
|
|
||||||
import com.facebook.react.bridge.ReadableMap;
|
|
||||||
|
|
||||||
public class BezierTransformer {
|
|
||||||
private ReadableArray mBezierCurves;
|
|
||||||
private PathShadowNode mPath;
|
|
||||||
private int mCurrentBezierIndex = 0;
|
|
||||||
private float mStartOffset = 0f;
|
|
||||||
private float mLastOffset = 0f;
|
|
||||||
private float mLastRecord = 0f;
|
|
||||||
private float mLastDistance = 0f;
|
|
||||||
private PointF mLastPoint = new PointF();
|
|
||||||
private PointF mP0 = new PointF();
|
|
||||||
private PointF mP1 = new PointF();
|
|
||||||
private PointF mP2 = new PointF();
|
|
||||||
private PointF mP3 = new PointF();
|
|
||||||
private boolean mReachedStart;
|
|
||||||
private boolean mReachedEnd;
|
|
||||||
|
|
||||||
BezierTransformer(PathShadowNode path, float startOffset) {
|
|
||||||
mBezierCurves = path.getBezierCurves();
|
|
||||||
mStartOffset = startOffset;
|
|
||||||
mPath = path;
|
|
||||||
}
|
|
||||||
|
|
||||||
private float calculateBezier(float t, float P0, float P1, float P2, float P3) {
|
|
||||||
return (1-t)*(1-t)*(1-t)*P0+3*(1-t)*(1-t)*t*P1+3*(1-t)*t*t*P2+t*t*t*P3;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PointF pointAtOffset(float t) {
|
|
||||||
float x = calculateBezier(t, mP0.x, mP1.x, mP2.x, mP3.x);
|
|
||||||
float y = calculateBezier(t, mP0.y, mP1.y, mP2.y, mP3.y);
|
|
||||||
return new PointF(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
private float calculateBezierPrime(float t, float P0, float P1, float P2, float P3) {
|
|
||||||
return -3*(1-t)*(1-t)*P0+(3*(1-t)*(1-t)*P1)-(6*t*(1-t)*P1)-(3*t*t*P2)+(6*t*(1-t)*P2)+3*t*t*P3;
|
|
||||||
}
|
|
||||||
|
|
||||||
private float angleAtOffset(float t) {
|
|
||||||
float dx = calculateBezierPrime(t, mP0.x, mP1.x, mP2.x, mP3.x);
|
|
||||||
float dy = calculateBezierPrime(t, mP0.y, mP1.y, mP2.y, mP3.y);
|
|
||||||
return (float)Math.atan2(dy, dx);
|
|
||||||
}
|
|
||||||
|
|
||||||
private float calculateDistance(PointF a, PointF b) {
|
|
||||||
return (float)Math.hypot(a.x - b.x, a.y - b.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
private PointF getPointFromMap(ReadableMap map) {
|
|
||||||
return new PointF((float)map.getDouble("x"), (float)map.getDouble("y"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simplistic routine to find the offset along Bezier that is
|
|
||||||
// `distance` away from `point`. `offset` is the offset used to
|
|
||||||
// generate `point`, and saves us the trouble of recalculating it
|
|
||||||
// This routine just walks forward until it finds a point at least
|
|
||||||
// `distance` away. Good optimizations here would reduce the number
|
|
||||||
// of guesses, but this is tricky since if we go too far out, the
|
|
||||||
// curve might loop back on leading to incorrect results. Tuning
|
|
||||||
// kStep is good start.
|
|
||||||
private float offsetAtDistance(float distance, PointF point, float offset) {
|
|
||||||
float kStep = 0.001f; // 0.0001 - 0.001 work well
|
|
||||||
float newDistance = 0;
|
|
||||||
float newOffset = offset + kStep;
|
|
||||||
while (newDistance <= distance && newOffset < 1.0) {
|
|
||||||
newOffset += kStep;
|
|
||||||
newDistance = calculateDistance(point, pointAtOffset(newOffset));
|
|
||||||
}
|
|
||||||
|
|
||||||
mLastDistance = newDistance;
|
|
||||||
return newOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setControlPoints() {
|
|
||||||
ReadableArray bezier = mBezierCurves.getArray(mCurrentBezierIndex++);
|
|
||||||
|
|
||||||
if (bezier != null) {
|
|
||||||
// set start point
|
|
||||||
if (bezier.size() == 1) {
|
|
||||||
mLastPoint = mP0 = getPointFromMap(bezier.getMap(0));
|
|
||||||
setControlPoints();
|
|
||||||
} else if (bezier.size() == 3) {
|
|
||||||
mP1 = getPointFromMap(bezier.getMap(0));
|
|
||||||
mP2 = getPointFromMap(bezier.getMap(1));
|
|
||||||
mP3 = getPointFromMap(bezier.getMap(2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public float getStartOffset() {
|
|
||||||
return mStartOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PathShadowNode getPath() {
|
|
||||||
return mPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float getTotalDistance() {
|
|
||||||
float distance = 0;
|
|
||||||
|
|
||||||
while (!mReachedEnd) {
|
|
||||||
distance += 0.1f;
|
|
||||||
float offset = offsetAtDistance(distance - mLastRecord, mLastPoint, mLastOffset);
|
|
||||||
|
|
||||||
if (offset < 1) {
|
|
||||||
PointF glyphPoint = pointAtOffset(offset);
|
|
||||||
mLastOffset = offset;
|
|
||||||
mLastPoint = glyphPoint;
|
|
||||||
mLastRecord = distance;
|
|
||||||
} else if (mBezierCurves.size() == mCurrentBezierIndex) {
|
|
||||||
mReachedEnd = true;
|
|
||||||
} else {
|
|
||||||
mLastOffset = 0;
|
|
||||||
mLastPoint = mP0 = mP3;
|
|
||||||
mLastRecord += mLastDistance;
|
|
||||||
setControlPoints();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mCurrentBezierIndex = 0;
|
|
||||||
mLastOffset = 0f;
|
|
||||||
mLastRecord = 0f;
|
|
||||||
mLastDistance = 0f;
|
|
||||||
mLastPoint = new PointF();
|
|
||||||
mP0 = new PointF();
|
|
||||||
mP1 = new PointF();
|
|
||||||
mP2 = new PointF();
|
|
||||||
mP3 = new PointF();
|
|
||||||
mReachedEnd = false;
|
|
||||||
|
|
||||||
return distance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Matrix getTransformAtDistance(float distance) {
|
|
||||||
mReachedStart = distance >= 0;
|
|
||||||
|
|
||||||
if (mReachedEnd || !mReachedStart) {
|
|
||||||
return new Matrix();
|
|
||||||
}
|
|
||||||
|
|
||||||
float offset = offsetAtDistance(distance - mLastRecord, mLastPoint, mLastOffset);
|
|
||||||
|
|
||||||
if (offset < 1) {
|
|
||||||
PointF glyphPoint = pointAtOffset(offset);
|
|
||||||
mLastOffset = offset;
|
|
||||||
mLastPoint = glyphPoint;
|
|
||||||
mLastRecord = distance;
|
|
||||||
Matrix matrix = new Matrix();
|
|
||||||
matrix.setRotate((float)Math.toDegrees(angleAtOffset(offset)));
|
|
||||||
matrix.postTranslate(glyphPoint.x, glyphPoint.y);
|
|
||||||
return matrix;
|
|
||||||
} else if (mBezierCurves.size() == mCurrentBezierIndex) {
|
|
||||||
mReachedEnd = true;
|
|
||||||
return new Matrix();
|
|
||||||
} else {
|
|
||||||
mLastOffset = 0;
|
|
||||||
mLastPoint = mP0 = mP3;
|
|
||||||
mLastRecord += mLastDistance;
|
|
||||||
setControlPoints();
|
|
||||||
return getTransformAtDistance(distance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasReachedEnd() {
|
|
||||||
return mReachedEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasReachedStart() {
|
|
||||||
return mReachedStart;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,6 +14,7 @@ import android.graphics.Canvas;
|
|||||||
import android.graphics.Matrix;
|
import android.graphics.Matrix;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Path;
|
import android.graphics.Path;
|
||||||
|
import android.graphics.PathMeasure;
|
||||||
import android.graphics.PointF;
|
import android.graphics.PointF;
|
||||||
import android.graphics.RectF;
|
import android.graphics.RectF;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
@@ -24,14 +25,17 @@ import com.facebook.react.uimanager.annotations.ReactProp;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static android.graphics.PathMeasure.POSITION_MATRIX_FLAG;
|
||||||
|
import static android.graphics.PathMeasure.TANGENT_MATRIX_FLAG;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shadow node for virtual TSpan view
|
* Shadow node for virtual TSpan view
|
||||||
*/
|
*/
|
||||||
public class TSpanShadowNode extends TextShadowNode {
|
public class TSpanShadowNode extends TextShadowNode {
|
||||||
|
|
||||||
private BezierTransformer mBezierTransformer;
|
|
||||||
private Path mCache;
|
private Path mCache;
|
||||||
private @Nullable String mContent;
|
private @Nullable String mContent;
|
||||||
|
private TextPathShadowNode textPath;
|
||||||
|
|
||||||
private static final String PROP_FONT_FAMILY = "fontFamily";
|
private static final String PROP_FONT_FAMILY = "fontFamily";
|
||||||
private static final String PROP_FONT_SIZE = "fontSize";
|
private static final String PROP_FONT_SIZE = "fontSize";
|
||||||
@@ -83,6 +87,21 @@ public class TSpanShadowNode extends TextShadowNode {
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private float getTextAnchorShift(float width) {
|
||||||
|
float x = 0;
|
||||||
|
|
||||||
|
switch (getComputedTextAnchor()) {
|
||||||
|
case TEXT_ANCHOR_MIDDLE:
|
||||||
|
x = -width / 2;
|
||||||
|
break;
|
||||||
|
case TEXT_ANCHOR_END:
|
||||||
|
x = -width;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
private Path getLinePath(Canvas canvas, String line, Paint paint) {
|
private Path getLinePath(Canvas canvas, String line, Paint paint) {
|
||||||
ReadableMap font = applyTextPropertiesToPaint(paint);
|
ReadableMap font = applyTextPropertiesToPaint(paint);
|
||||||
int length = line.length();
|
int length = line.length();
|
||||||
@@ -92,29 +111,22 @@ public class TSpanShadowNode extends TextShadowNode {
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
PointF glyphPoint = getGlyphPointFromContext(0, 0);
|
|
||||||
PointF glyphDelta = getGlyphDeltaFromContext();
|
|
||||||
float textMeasure = 0;
|
|
||||||
float distance = 0;
|
|
||||||
float offset = 0;
|
float offset = 0;
|
||||||
PathShadowNode p;
|
float distance = 0;
|
||||||
Path bezierPath;
|
float renderMethodScaling = 1;
|
||||||
|
float textMeasure = paint.measureText(line);
|
||||||
|
float textAnchorShift = getTextAnchorShift(textMeasure);
|
||||||
|
|
||||||
if (mBezierTransformer != null) {
|
PathMeasure pm = null;
|
||||||
offset = mBezierTransformer.getStartOffset();
|
|
||||||
boolean debug = true;
|
if (textPath != null) {
|
||||||
if (debug) {
|
pm = new PathMeasure(textPath.getPath(), false);
|
||||||
distance = mBezierTransformer.getTotalDistance();
|
distance = pm.getLength();
|
||||||
textMeasure = paint.measureText(line);
|
offset = PropHelper.fromPercentageToFloat(textPath.getStartOffset(), distance, 0, mScale);
|
||||||
p = mBezierTransformer.getPath();
|
String spacing = textPath.getSpacing(); // spacing = "auto | exact"
|
||||||
bezierPath = p.getPath();
|
String method = textPath.getMethod(); // method = "align | stretch"
|
||||||
canvas.drawTextOnPath(
|
if ("stretch".equals(method)) {
|
||||||
line,
|
renderMethodScaling = distance / textMeasure;
|
||||||
bezierPath,
|
|
||||||
offset + glyphPoint.x + glyphDelta.x,
|
|
||||||
glyphDelta.y,
|
|
||||||
paint
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +134,8 @@ public class TSpanShadowNode extends TextShadowNode {
|
|||||||
float width;
|
float width;
|
||||||
Matrix matrix;
|
Matrix matrix;
|
||||||
String current;
|
String current;
|
||||||
|
PointF glyphPoint;
|
||||||
|
PointF glyphDelta;
|
||||||
String previous = "";
|
String previous = "";
|
||||||
float glyphPosition = 0;
|
float glyphPosition = 0;
|
||||||
char[] chars = line.toCharArray();
|
char[] chars = line.toCharArray();
|
||||||
@@ -133,8 +147,8 @@ public class TSpanShadowNode extends TextShadowNode {
|
|||||||
paint.getTextWidths(line, widths);
|
paint.getTextWidths(line, widths);
|
||||||
|
|
||||||
for (int index = 0; index < length; index++) {
|
for (int index = 0; index < length; index++) {
|
||||||
|
width = widths[index] * renderMethodScaling;
|
||||||
current = String.valueOf(chars[index]);
|
current = String.valueOf(chars[index]);
|
||||||
width = widths[index];
|
|
||||||
glyph = new Path();
|
glyph = new Path();
|
||||||
|
|
||||||
if (isKerningValueSet) {
|
if (isKerningValueSet) {
|
||||||
@@ -149,32 +163,42 @@ public class TSpanShadowNode extends TextShadowNode {
|
|||||||
previous = current;
|
previous = current;
|
||||||
}
|
}
|
||||||
|
|
||||||
glyphPoint = getGlyphPointFromContext(glyphPosition, width);
|
glyphPoint = getGlyphPointFromContext(textAnchorShift + glyphPosition, width);
|
||||||
|
glyphDelta = getGlyphDeltaFromContext();
|
||||||
|
glyphPosition += width;
|
||||||
|
matrix = new Matrix();
|
||||||
|
|
||||||
if (mBezierTransformer != null) {
|
if (textPath != null) {
|
||||||
float halfway = width / 2;
|
float halfway = width / 2;
|
||||||
|
float start = offset + glyphPoint.x + glyphDelta.x;
|
||||||
|
float midpoint = start + halfway;
|
||||||
|
|
||||||
matrix = mBezierTransformer.getTransformAtDistance(
|
if (midpoint > distance ) {
|
||||||
offset + glyphPoint.x + glyphDelta.x + halfway
|
if (start <= distance) {
|
||||||
);
|
// Seems to cut off too early, see e.g. toap3, this shows the last "p"
|
||||||
|
midpoint = start;
|
||||||
if (textPathHasReachedEnd()) {
|
halfway = 0;
|
||||||
|
} else {
|
||||||
break;
|
break;
|
||||||
} else if (!textPathHasReachedStart()) {
|
}
|
||||||
|
} else if (midpoint < 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pm.getMatrix(midpoint, matrix, POSITION_MATRIX_FLAG | TANGENT_MATRIX_FLAG);
|
||||||
|
|
||||||
matrix.preTranslate(-halfway, glyphDelta.y);
|
matrix.preTranslate(-halfway, glyphDelta.y);
|
||||||
|
matrix.preScale(renderMethodScaling, 1);
|
||||||
matrix.postTranslate(0, glyphPoint.y);
|
matrix.postTranslate(0, glyphPoint.y);
|
||||||
} else {
|
} else {
|
||||||
matrix = new Matrix();
|
matrix.setTranslate(
|
||||||
matrix.setTranslate(glyphPoint.x + glyphDelta.x, glyphPoint.y + glyphDelta.y);
|
glyphPoint.x + glyphDelta.x + textAnchorShift,
|
||||||
|
glyphPoint.y + glyphDelta.y
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
paint.getTextPath(current, 0, 1, 0, 0, glyph);
|
paint.getTextPath(current, 0, 1, 0, 0, glyph);
|
||||||
glyphDelta = getGlyphDeltaFromContext();
|
|
||||||
glyph.transform(matrix);
|
glyph.transform(matrix);
|
||||||
glyphPosition += width;
|
|
||||||
path.addPath(glyph);
|
path.addPath(glyph);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,8 +242,7 @@ public class TSpanShadowNode extends TextShadowNode {
|
|||||||
|
|
||||||
while (parent != null) {
|
while (parent != null) {
|
||||||
if (parent.getClass() == TextPathShadowNode.class) {
|
if (parent.getClass() == TextPathShadowNode.class) {
|
||||||
TextPathShadowNode textPath = (TextPathShadowNode)parent;
|
textPath = (TextPathShadowNode)parent;
|
||||||
mBezierTransformer = textPath.getBezierTransformer();
|
|
||||||
break;
|
break;
|
||||||
} else if (!(parent instanceof TextShadowNode)) {
|
} else if (!(parent instanceof TextShadowNode)) {
|
||||||
break;
|
break;
|
||||||
@@ -228,12 +251,4 @@ public class TSpanShadowNode extends TextShadowNode {
|
|||||||
parent = parent.getParent();
|
parent = parent.getParent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean textPathHasReachedEnd() {
|
|
||||||
return mBezierTransformer.hasReachedEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean textPathHasReachedStart() {
|
|
||||||
return mBezierTransformer.hasReachedStart();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import javax.annotation.Nullable;
|
|||||||
public class TextPathShadowNode extends TextShadowNode {
|
public class TextPathShadowNode extends TextShadowNode {
|
||||||
|
|
||||||
private String mHref;
|
private String mHref;
|
||||||
|
private String mMethod;
|
||||||
|
private String mSpacing;
|
||||||
private @Nullable String mStartOffset;
|
private @Nullable String mStartOffset;
|
||||||
|
|
||||||
@ReactProp(name = "href")
|
@ReactProp(name = "href")
|
||||||
@@ -37,12 +39,36 @@ public class TextPathShadowNode extends TextShadowNode {
|
|||||||
markUpdated();
|
markUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "method")
|
||||||
|
public void setMethod(@Nullable String method) {
|
||||||
|
mMethod = method;
|
||||||
|
markUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "spacing")
|
||||||
|
public void setSpacing(@Nullable String spacing) {
|
||||||
|
mSpacing = spacing;
|
||||||
|
markUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMethod() {
|
||||||
|
return mMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSpacing() {
|
||||||
|
return mSpacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStartOffset() {
|
||||||
|
return mStartOffset;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(Canvas canvas, Paint paint, float opacity) {
|
public void draw(Canvas canvas, Paint paint, float opacity) {
|
||||||
drawGroup(canvas, paint, opacity);
|
drawGroup(canvas, paint, opacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BezierTransformer getBezierTransformer() {
|
public Path getPath() {
|
||||||
SvgViewShadowNode svg = getSvgShadowNode();
|
SvgViewShadowNode svg = getSvgShadowNode();
|
||||||
VirtualNode template = svg.getDefinedTemplate(mHref);
|
VirtualNode template = svg.getDefinedTemplate(mHref);
|
||||||
|
|
||||||
@@ -52,7 +78,7 @@ public class TextPathShadowNode extends TextShadowNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PathShadowNode path = (PathShadowNode)template;
|
PathShadowNode path = (PathShadowNode)template;
|
||||||
return new BezierTransformer(path, relativeOnWidth(mStartOffset));
|
return path.getPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -33,10 +33,10 @@ import com.facebook.react.uimanager.annotations.ReactProp;
|
|||||||
|
|
||||||
public class TextShadowNode extends GroupShadowNode {
|
public class TextShadowNode extends GroupShadowNode {
|
||||||
|
|
||||||
private static final int TEXT_ANCHOR_AUTO = 0;
|
static final int TEXT_ANCHOR_AUTO = 0;
|
||||||
private static final int TEXT_ANCHOR_START = 1;
|
static final int TEXT_ANCHOR_START = 1;
|
||||||
private static final int TEXT_ANCHOR_MIDDLE = 2;
|
static final int TEXT_ANCHOR_MIDDLE = 2;
|
||||||
private static final int TEXT_ANCHOR_END = 3;
|
static final int TEXT_ANCHOR_END = 3;
|
||||||
|
|
||||||
private int mTextAnchor = TEXT_ANCHOR_AUTO;
|
private int mTextAnchor = TEXT_ANCHOR_AUTO;
|
||||||
private @Nullable ReadableArray mDeltaX;
|
private @Nullable ReadableArray mDeltaX;
|
||||||
@@ -86,8 +86,6 @@ public class TextShadowNode extends GroupShadowNode {
|
|||||||
setupGlyphContext();
|
setupGlyphContext();
|
||||||
clip(canvas, paint);
|
clip(canvas, paint);
|
||||||
Path path = getGroupPath(canvas, paint);
|
Path path = getGroupPath(canvas, paint);
|
||||||
Matrix matrix = getAlignMatrix(path);
|
|
||||||
canvas.concat(matrix);
|
|
||||||
drawGroup(canvas, paint, opacity);
|
drawGroup(canvas, paint, opacity);
|
||||||
releaseCachedPath();
|
releaseCachedPath();
|
||||||
}
|
}
|
||||||
@@ -97,9 +95,6 @@ public class TextShadowNode extends GroupShadowNode {
|
|||||||
protected Path getPath(Canvas canvas, Paint paint) {
|
protected Path getPath(Canvas canvas, Paint paint) {
|
||||||
setupGlyphContext();
|
setupGlyphContext();
|
||||||
Path groupPath = getGroupPath(canvas, paint);
|
Path groupPath = getGroupPath(canvas, paint);
|
||||||
Matrix matrix = getAlignMatrix(groupPath);
|
|
||||||
groupPath.transform(matrix);
|
|
||||||
|
|
||||||
releaseCachedPath();
|
releaseCachedPath();
|
||||||
return groupPath;
|
return groupPath;
|
||||||
}
|
}
|
||||||
@@ -108,20 +103,21 @@ public class TextShadowNode extends GroupShadowNode {
|
|||||||
return mTextAnchor;
|
return mTextAnchor;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getComputedTextAnchor() {
|
int getComputedTextAnchor() {
|
||||||
int anchor = mTextAnchor;
|
int anchor = mTextAnchor;
|
||||||
ReactShadowNode shadowNode = this;
|
ReactShadowNode shadowNode = this;
|
||||||
|
|
||||||
while (shadowNode.getChildCount() > 0 &&
|
while (shadowNode instanceof GroupShadowNode) {
|
||||||
anchor == TEXT_ANCHOR_AUTO) {
|
|
||||||
shadowNode = shadowNode.getChildAt(0);
|
|
||||||
|
|
||||||
if (shadowNode instanceof TextShadowNode) {
|
if (shadowNode instanceof TextShadowNode) {
|
||||||
anchor = ((TextShadowNode) shadowNode).getTextAnchor();
|
anchor = ((TextShadowNode) shadowNode).getTextAnchor();
|
||||||
} else {
|
if (anchor != TEXT_ANCHOR_AUTO) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shadowNode = shadowNode.getParent();
|
||||||
|
}
|
||||||
|
|
||||||
return anchor;
|
return anchor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,25 +143,4 @@ public class TextShadowNode extends GroupShadowNode {
|
|||||||
protected void pushGlyphContext() {
|
protected void pushGlyphContext() {
|
||||||
getTextRoot().getGlyphContext().pushContext(mFont, mDeltaX, mDeltaY, mPositionX, mPositionY);
|
getTextRoot().getGlyphContext().pushContext(mFont, mDeltaX, mDeltaY, mPositionX, mPositionY);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Matrix getAlignMatrix(Path path) {
|
|
||||||
RectF box = new RectF();
|
|
||||||
path.computeBounds(box, true);
|
|
||||||
|
|
||||||
float width = box.width();
|
|
||||||
float x = 0;
|
|
||||||
|
|
||||||
switch (getComputedTextAnchor()) {
|
|
||||||
case TEXT_ANCHOR_MIDDLE:
|
|
||||||
x = -width / 2;
|
|
||||||
break;
|
|
||||||
case TEXT_ANCHOR_END:
|
|
||||||
x = -width;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Matrix matrix = new Matrix();
|
|
||||||
matrix.setTranslate(x, 0);
|
|
||||||
return matrix;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ export default class extends Shape {
|
|||||||
...pathProps,
|
...pathProps,
|
||||||
...fontProps,
|
...fontProps,
|
||||||
href: PropTypes.string.isRequired,
|
href: PropTypes.string.isRequired,
|
||||||
|
method: PropTypes.oneOf(['align', 'stretch']),
|
||||||
|
spacing: PropTypes.oneOf(['auto', 'exact']),
|
||||||
startOffset: numberProp
|
startOffset: numberProp
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -114,6 +114,8 @@ const TextAttributes = {
|
|||||||
|
|
||||||
const TextPathAttributes = {
|
const TextPathAttributes = {
|
||||||
href: true,
|
href: true,
|
||||||
|
method: true,
|
||||||
|
spacing: true,
|
||||||
startOffset: true,
|
startOffset: true,
|
||||||
...RenderableAttributes
|
...RenderableAttributes
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ export default function(props, container) {
|
|||||||
y,
|
y,
|
||||||
dx,
|
dx,
|
||||||
dy,
|
dy,
|
||||||
|
method,
|
||||||
|
spacing,
|
||||||
textAnchor,
|
textAnchor,
|
||||||
startOffset
|
startOffset
|
||||||
} = props;
|
} = props;
|
||||||
@@ -127,6 +129,8 @@ export default function(props, container) {
|
|||||||
content,
|
content,
|
||||||
deltaX,
|
deltaX,
|
||||||
deltaY,
|
deltaY,
|
||||||
|
method,
|
||||||
|
spacing,
|
||||||
startOffset: (startOffset || 0).toString(),
|
startOffset: (startOffset || 0).toString(),
|
||||||
positionX: _.isNil(x) ? null : x.toString(),
|
positionX: _.isNil(x) ? null : x.toString(),
|
||||||
positionY: _.isNil(y) ? null : y.toString()
|
positionY: _.isNil(y) ? null : y.toString()
|
||||||
|
|||||||
Reference in New Issue
Block a user