mirror of
https://github.com/zoriya/react-native-svg.git
synced 2025-12-21 06:15:15 +00:00
Simplify textAnchor and textDecoration handling
This commit is contained in:
@@ -18,18 +18,20 @@ import java.util.ArrayList;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.horcrux.svg.TextShadowNode.KERNING;
|
||||
import static com.horcrux.svg.TextShadowNode.TEXT_ANCHOR;
|
||||
import static com.horcrux.svg.TextShadowNode.WORD_SPACING;
|
||||
import static com.horcrux.svg.TextShadowNode.LETTER_SPACING;
|
||||
import static com.horcrux.svg.TextShadowNode.TEXT_DECORATION;
|
||||
import static com.facebook.react.uimanager.ViewProps.FONT_SIZE;
|
||||
import static com.facebook.react.uimanager.ViewProps.FONT_STYLE;
|
||||
import static com.facebook.react.uimanager.ViewProps.FONT_FAMILY;
|
||||
import static com.facebook.react.uimanager.ViewProps.FONT_WEIGHT;
|
||||
|
||||
// https://www.w3.org/TR/SVG/text.html#TSpanElement
|
||||
class GlyphContext {
|
||||
static final double DEFAULT_FONT_SIZE = 12d;
|
||||
|
||||
private static final String KERNING = "kerning";
|
||||
private static final String FONT_SIZE = "fontSize";
|
||||
private static final String FONT_STYLE = "fontStyle";
|
||||
private static final String FONT_WEIGHT = "fontWeight";
|
||||
private static final String FONT_FAMILY = "fontFamily";
|
||||
private static final String WORD_SPACING = "wordSpacing";
|
||||
private static final String LETTER_SPACING = "letterSpacing";
|
||||
|
||||
// Empty font context map
|
||||
private static final WritableMap DEFAULT_MAP = Arguments.createMap();
|
||||
|
||||
@@ -236,20 +238,18 @@ class GlyphContext {
|
||||
|
||||
map.putDouble(FONT_SIZE, mFontSize);
|
||||
|
||||
put(FONT_STYLE, map, font, parent);
|
||||
put(FONT_FAMILY, map, font, parent);
|
||||
|
||||
put(FONT_WEIGHT, map, font, parent);
|
||||
|
||||
put(FONT_STYLE, map, font, parent);
|
||||
put(TEXT_ANCHOR, map, font, parent);
|
||||
put(TEXT_DECORATION, map, font, parent);
|
||||
|
||||
// https://www.w3.org/TR/SVG11/text.html#SpacingProperties
|
||||
// https://drafts.csswg.org/css-text-3/#spacing
|
||||
// calculated values for units in: kerning, word-spacing, and, letter-spacing
|
||||
|
||||
// calculated values for units in: kerning, word-spacing, and, letter-spacing.
|
||||
putD(KERNING, map, font, parent);
|
||||
|
||||
putD(WORD_SPACING, map, font, parent);
|
||||
|
||||
putD(LETTER_SPACING, map, font, parent);
|
||||
}
|
||||
|
||||
@@ -428,7 +428,7 @@ class GlyphContext {
|
||||
return mFontSize;
|
||||
}
|
||||
|
||||
double nextX(double glyphWidth) {
|
||||
double nextX(double advance) {
|
||||
incrementIndices(mXIndices, mXsIndex);
|
||||
|
||||
int nextIndex = mXIndex + 1;
|
||||
@@ -439,7 +439,7 @@ class GlyphContext {
|
||||
mX = PropHelper.fromRelative(string, mWidth, 0, mScale, mFontSize);
|
||||
}
|
||||
|
||||
mX += glyphWidth;
|
||||
mX += advance;
|
||||
|
||||
return mX;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,11 @@ import javax.annotation.Nullable;
|
||||
import static android.graphics.PathMeasure.POSITION_MATRIX_FLAG;
|
||||
import static android.graphics.PathMeasure.TANGENT_MATRIX_FLAG;
|
||||
|
||||
import static com.facebook.react.uimanager.ViewProps.FONT_SIZE;
|
||||
import static com.facebook.react.uimanager.ViewProps.FONT_STYLE;
|
||||
import static com.facebook.react.uimanager.ViewProps.FONT_WEIGHT;
|
||||
import static com.facebook.react.uimanager.ViewProps.FONT_FAMILY;
|
||||
|
||||
/**
|
||||
* Shadow node for virtual TSpan view
|
||||
*/
|
||||
@@ -44,14 +49,6 @@ class TSpanShadowNode extends TextShadowNode {
|
||||
private static final double DEFAULT_WORD_SPACING = 0d;
|
||||
private static final double DEFAULT_LETTER_SPACING = 0d;
|
||||
|
||||
private static final String PROP_KERNING = "kerning";
|
||||
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_FAMILY = "fontFamily";
|
||||
private static final String PROP_WORD_SPACING = "wordSpacing";
|
||||
private static final String PROP_LETTER_SPACING = "letterSpacing";
|
||||
|
||||
private Path mCache;
|
||||
private @Nullable String mContent;
|
||||
private TextPathShadowNode textPath;
|
||||
@@ -98,10 +95,10 @@ class TSpanShadowNode extends TextShadowNode {
|
||||
return mCache;
|
||||
}
|
||||
|
||||
private double getTextAnchorShift(double width) {
|
||||
private double getTextAnchorShift(double width, String textAnchor) {
|
||||
double x = 0;
|
||||
|
||||
switch (getComputedTextAnchor()) {
|
||||
switch (textAnchor) {
|
||||
case TEXT_ANCHOR_MIDDLE:
|
||||
x = -width / 2;
|
||||
break;
|
||||
@@ -144,7 +141,9 @@ class TSpanShadowNode extends TextShadowNode {
|
||||
}
|
||||
}
|
||||
|
||||
offset += getTextAnchorShift(textMeasure);
|
||||
if (font.hasKey(TEXT_ANCHOR)) {
|
||||
offset += getTextAnchorShift(textMeasure, font.getString(TEXT_ANCHOR));
|
||||
}
|
||||
|
||||
double x;
|
||||
double y;
|
||||
@@ -180,16 +179,16 @@ class TSpanShadowNode extends TextShadowNode {
|
||||
*
|
||||
* */
|
||||
|
||||
final boolean hasKerning = font.hasKey(PROP_KERNING);
|
||||
double kerning = hasKerning ? font.getDouble(PROP_KERNING) : DEFAULT_KERNING;
|
||||
final boolean hasKerning = font.hasKey(KERNING);
|
||||
double kerning = hasKerning ? font.getDouble(KERNING) : DEFAULT_KERNING;
|
||||
final boolean autoKerning = !hasKerning;
|
||||
|
||||
final double wordSpacing = font.hasKey(PROP_WORD_SPACING) ?
|
||||
font.getDouble(PROP_WORD_SPACING)
|
||||
final double wordSpacing = font.hasKey(WORD_SPACING) ?
|
||||
font.getDouble(WORD_SPACING)
|
||||
: DEFAULT_WORD_SPACING;
|
||||
|
||||
final double letterSpacing = font.hasKey(PROP_LETTER_SPACING) ?
|
||||
font.getDouble(PROP_LETTER_SPACING)
|
||||
final double letterSpacing = font.hasKey(LETTER_SPACING) ?
|
||||
font.getDouble(LETTER_SPACING)
|
||||
: DEFAULT_LETTER_SPACING;
|
||||
|
||||
for (int index = 0; index < length; index++) {
|
||||
@@ -251,19 +250,29 @@ class TSpanShadowNode extends TextShadowNode {
|
||||
private void applyTextPropertiesToPaint(Paint paint, ReadableMap font) {
|
||||
AssetManager assetManager = getThemedContext().getResources().getAssets();
|
||||
|
||||
double fontSize = font.getDouble(PROP_FONT_SIZE) * mScale;
|
||||
double fontSize = font.getDouble(FONT_SIZE) * mScale;
|
||||
|
||||
boolean isBold = font.hasKey(PROP_FONT_WEIGHT) &&
|
||||
BOLD.equals(font.getString(PROP_FONT_WEIGHT));
|
||||
boolean isBold = font.hasKey(FONT_WEIGHT) &&
|
||||
BOLD.equals(font.getString(FONT_WEIGHT));
|
||||
|
||||
boolean isItalic = font.hasKey(PROP_FONT_STYLE) &&
|
||||
ITALIC.equals(font.getString(PROP_FONT_STYLE));
|
||||
boolean isItalic = font.hasKey(FONT_STYLE) &&
|
||||
ITALIC.equals(font.getString(FONT_STYLE));
|
||||
|
||||
int decoration = getTextDecoration();
|
||||
boolean underlineText = false;
|
||||
boolean strikeThruText = false;
|
||||
|
||||
boolean underlineText = decoration == TEXT_DECORATION_UNDERLINE;
|
||||
if (font.hasKey(TEXT_DECORATION)) {
|
||||
String decoration = font.getString(TEXT_DECORATION);
|
||||
switch (decoration) {
|
||||
case TEXT_DECORATION_UNDERLINE:
|
||||
underlineText = true;
|
||||
break;
|
||||
|
||||
boolean strikeThruText = decoration == TEXT_DECORATION_LINE_THROUGH;
|
||||
case TEXT_DECORATION_LINE_THROUGH:
|
||||
strikeThruText = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int fontStyle;
|
||||
if (isBold && isItalic) {
|
||||
@@ -278,15 +287,15 @@ class TSpanShadowNode extends TextShadowNode {
|
||||
|
||||
Typeface typeface = null;
|
||||
try {
|
||||
String path = FONTS + font.getString(PROP_FONT_FAMILY) + OTF;
|
||||
String path = FONTS + font.getString(FONT_FAMILY) + OTF;
|
||||
typeface = Typeface.createFromAsset(assetManager, path);
|
||||
} catch (Exception ignored) {
|
||||
try {
|
||||
String path = FONTS + font.getString(PROP_FONT_FAMILY) + TTF;
|
||||
String path = FONTS + font.getString(FONT_FAMILY) + TTF;
|
||||
typeface = Typeface.createFromAsset(assetManager, path);
|
||||
} catch (Exception ignored2) {
|
||||
try {
|
||||
typeface = Typeface.create(font.getString(PROP_FONT_FAMILY), fontStyle);
|
||||
typeface = Typeface.create(font.getString(FONT_FAMILY), fontStyle);
|
||||
} catch (Exception ignored3) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import android.graphics.Path;
|
||||
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.uimanager.ReactShadowNode;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@@ -25,38 +24,31 @@ import javax.annotation.Nullable;
|
||||
*/
|
||||
|
||||
class TextShadowNode extends GroupShadowNode {
|
||||
// static final String INHERIT = "inherit";
|
||||
|
||||
private static final int TEXT_ANCHOR_AUTO = 0;
|
||||
// static final int TEXT_ANCHOR_START = 1;
|
||||
static final int TEXT_ANCHOR_MIDDLE = 2;
|
||||
static final int TEXT_ANCHOR_END = 3;
|
||||
// static final String TEXT_ANCHOR_AUTO = "auto";
|
||||
// static final String TEXT_ANCHOR_START = "start";
|
||||
static final String TEXT_ANCHOR_MIDDLE = "middle";
|
||||
static final String TEXT_ANCHOR_END = "end";
|
||||
|
||||
private static final int TEXT_DECORATION_NONE = 0;
|
||||
static final int TEXT_DECORATION_UNDERLINE = 1;
|
||||
// static final int TEXT_DECORATION_OVERLINE = 2;
|
||||
static final int TEXT_DECORATION_LINE_THROUGH = 3;
|
||||
// static final int TEXT_DECORATION_BLINK = 4;
|
||||
// static final String TEXT_DECORATION_NONE = "none";
|
||||
static final String TEXT_DECORATION_UNDERLINE = "underline";
|
||||
// static final String TEXT_DECORATION_OVERLINE = "overline";
|
||||
static final String TEXT_DECORATION_LINE_THROUGH = "line-through";
|
||||
// static final String TEXT_DECORATION_BLINK = "blink";
|
||||
|
||||
static final String KERNING = "kerning";
|
||||
static final String TEXT_ANCHOR = "textAnchor";
|
||||
static final String WORD_SPACING = "wordSpacing";
|
||||
static final String LETTER_SPACING = "letterSpacing";
|
||||
static final String TEXT_DECORATION = "textDecoration";
|
||||
|
||||
private int mTextAnchor = TEXT_ANCHOR_AUTO;
|
||||
private int mTextDecoration = TEXT_DECORATION_NONE;
|
||||
private @Nullable ReadableArray mPositionX;
|
||||
private @Nullable ReadableArray mPositionY;
|
||||
private @Nullable ReadableArray mRotate;
|
||||
private @Nullable ReadableArray mDeltaX;
|
||||
private @Nullable ReadableArray mDeltaY;
|
||||
|
||||
@ReactProp(name = "textAnchor")
|
||||
public void setTextAnchor(int textAnchor) {
|
||||
mTextAnchor = textAnchor;
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@ReactProp(name = "textDecoration")
|
||||
public void setTextDecoration(int textDecoration) {
|
||||
mTextDecoration = textDecoration;
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@ReactProp(name = "rotate")
|
||||
public void setRotate(@Nullable ReadableArray rotate) {
|
||||
mRotate = rotate;
|
||||
@@ -112,52 +104,6 @@ class TextShadowNode extends GroupShadowNode {
|
||||
return groupPath;
|
||||
}
|
||||
|
||||
private int getTextAnchor() {
|
||||
return mTextAnchor;
|
||||
}
|
||||
|
||||
int getComputedTextAnchor() {
|
||||
int anchor = mTextAnchor;
|
||||
if (anchor != TEXT_ANCHOR_AUTO) {
|
||||
return anchor;
|
||||
}
|
||||
ReactShadowNode shadowNode = this.getParent();
|
||||
|
||||
while (shadowNode instanceof GroupShadowNode) {
|
||||
if (shadowNode instanceof TextShadowNode) {
|
||||
anchor = ((TextShadowNode) shadowNode).getTextAnchor();
|
||||
if (anchor != TEXT_ANCHOR_AUTO) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
shadowNode = shadowNode.getParent();
|
||||
}
|
||||
|
||||
return anchor;
|
||||
}
|
||||
|
||||
int getTextDecoration() {
|
||||
int decoration = mTextDecoration;
|
||||
if (decoration != TEXT_DECORATION_NONE) {
|
||||
return decoration;
|
||||
}
|
||||
ReactShadowNode shadowNode = this.getParent();
|
||||
|
||||
while (shadowNode instanceof GroupShadowNode) {
|
||||
if (shadowNode instanceof TextShadowNode) {
|
||||
decoration = ((TextShadowNode) shadowNode).getTextDecoration();
|
||||
if (decoration != TEXT_DECORATION_NONE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
shadowNode = shadowNode.getParent();
|
||||
}
|
||||
|
||||
return decoration;
|
||||
}
|
||||
|
||||
void releaseCachedPath() {
|
||||
traverseChildren(new NodeRunnable() {
|
||||
public void run(VirtualNode node) {
|
||||
|
||||
@@ -16,7 +16,6 @@ export default class extends Shape {
|
||||
...fontProps,
|
||||
dx: numberProp,
|
||||
dy: numberProp,
|
||||
textAnchor: PropTypes.oneOf(['start', 'middle', 'end'])
|
||||
};
|
||||
|
||||
static childContextTypes = {
|
||||
|
||||
@@ -15,7 +15,6 @@ export default class extends Shape {
|
||||
...fontProps,
|
||||
dx: numberProp,
|
||||
dy: numberProp,
|
||||
textAnchor: PropTypes.oneOf(['start', 'middle', 'end'])
|
||||
};
|
||||
|
||||
static childContextTypes = {
|
||||
|
||||
@@ -8,21 +8,7 @@ const fontFamilySuffix = /[\s"']*$/;
|
||||
const spaceReg = /\s+/;
|
||||
const commaReg = /,/g;
|
||||
|
||||
const anchors = {
|
||||
auto: 0,
|
||||
start: 1,
|
||||
middle: 2,
|
||||
end: 3
|
||||
};
|
||||
|
||||
const decorations = {
|
||||
none: 0,
|
||||
underline: 1,
|
||||
overline: 2,
|
||||
'line-through': 3,
|
||||
blink: 4
|
||||
};
|
||||
let cachedFontObjectsFromString = {};
|
||||
const cachedFontObjectsFromString = {};
|
||||
|
||||
function extractSingleFontFamily(fontFamilyString) {
|
||||
// SVG on the web allows for multiple font-families to be specified.
|
||||
@@ -37,40 +23,59 @@ function parseFontString(font) {
|
||||
if (cachedFontObjectsFromString.hasOwnProperty(font)) {
|
||||
return cachedFontObjectsFromString[font];
|
||||
}
|
||||
let match = fontRegExp.exec(font);
|
||||
const match = fontRegExp.exec(font);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
let fontFamily = extractSingleFontFamily(match[3]);
|
||||
let fontSize = match[2] || "12";
|
||||
let isBold = /bold/.exec(match[1]);
|
||||
let isItalic = /italic/.exec(match[1]);
|
||||
const fontFamily = extractSingleFontFamily(match[3]);
|
||||
const fontSize = match[2] || "12";
|
||||
const isBold = /bold/.exec(match[1]);
|
||||
const isItalic = /italic/.exec(match[1]);
|
||||
const fontWeight = isBold ? 'bold' : 'normal';
|
||||
const fontStyle = isItalic ? 'italic' : 'normal';
|
||||
cachedFontObjectsFromString[font] = {
|
||||
fontSize,
|
||||
fontFamily,
|
||||
fontWeight: isBold ? 'bold' : 'normal',
|
||||
fontStyle: isItalic ? 'italic' : 'normal'
|
||||
fontWeight,
|
||||
fontStyle,
|
||||
};
|
||||
return cachedFontObjectsFromString[font];
|
||||
}
|
||||
|
||||
export function extractFont(props) {
|
||||
let font = props.font;
|
||||
const {
|
||||
letterSpacing,
|
||||
wordSpacing,
|
||||
fontWeight,
|
||||
fontStyle,
|
||||
kerning,
|
||||
textAnchor,
|
||||
textDecoration,
|
||||
} = props;
|
||||
let {
|
||||
font,
|
||||
fontSize,
|
||||
fontFamily,
|
||||
} = props;
|
||||
|
||||
let ownedFont = {
|
||||
fontFamily: extractSingleFontFamily(props.fontFamily),
|
||||
letterSpacing: props.letterSpacing,
|
||||
wordSpacing: props.wordSpacing,
|
||||
fontWeight: props.fontWeight,
|
||||
fontStyle: props.fontStyle,
|
||||
fontSize: props.fontSize ? '' + props.fontSize : null,
|
||||
kerning: props.kerning,
|
||||
};
|
||||
fontFamily = extractSingleFontFamily(fontFamily);
|
||||
fontSize = fontSize ? '' + fontSize : null;
|
||||
|
||||
if (typeof props.font === 'string') {
|
||||
font = parseFontString(props.font);
|
||||
const ownedFont = _.pickBy({
|
||||
fontFamily,
|
||||
letterSpacing,
|
||||
wordSpacing,
|
||||
fontWeight,
|
||||
fontStyle,
|
||||
fontSize,
|
||||
kerning,
|
||||
textAnchor,
|
||||
textDecoration,
|
||||
}, prop => !_.isNil(prop));
|
||||
|
||||
if (typeof font === 'string') {
|
||||
font = parseFontString(font);
|
||||
}
|
||||
ownedFont = _.pickBy(ownedFont, prop => !_.isNil(prop));
|
||||
|
||||
return _.defaults(ownedFont, font);
|
||||
}
|
||||
@@ -88,28 +93,27 @@ function parseSVGLengthList(delta) {
|
||||
}
|
||||
|
||||
export default function(props, container) {
|
||||
let {
|
||||
const {
|
||||
x,
|
||||
y,
|
||||
dx,
|
||||
dy,
|
||||
rotate,
|
||||
method,
|
||||
spacing,
|
||||
textAnchor,
|
||||
startOffset,
|
||||
textDecoration
|
||||
} = props;
|
||||
let {
|
||||
rotate,
|
||||
children,
|
||||
startOffset
|
||||
} = props;
|
||||
|
||||
let positionX = parseSVGLengthList(x);
|
||||
let positionY = parseSVGLengthList(y);
|
||||
const positionX = parseSVGLengthList(x);
|
||||
const positionY = parseSVGLengthList(y);
|
||||
const deltaX = parseSVGLengthList(dx);
|
||||
const deltaY = parseSVGLengthList(dy);
|
||||
rotate = parseSVGLengthList(rotate);
|
||||
|
||||
let { children } = props;
|
||||
let content = null;
|
||||
|
||||
if (typeof children === 'string' || typeof children === 'number') {
|
||||
const childrenString = children.toString();
|
||||
if (container) {
|
||||
@@ -128,10 +132,11 @@ export default function(props, container) {
|
||||
});
|
||||
}
|
||||
|
||||
const font = extractFont(props);
|
||||
startOffset = (startOffset || 0).toString();
|
||||
|
||||
return {
|
||||
textDecoration: decorations[textDecoration] || 0,
|
||||
textAnchor: anchors[textAnchor] || 0,
|
||||
font: extractFont(props),
|
||||
font,
|
||||
children,
|
||||
content,
|
||||
positionX,
|
||||
@@ -141,6 +146,6 @@ export default function(props, container) {
|
||||
deltaY,
|
||||
method,
|
||||
spacing,
|
||||
startOffset: (startOffset || 0).toString(),
|
||||
startOffset,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@ const fontProps = {
|
||||
fontSize: numberProp,
|
||||
fontWeight: numberProp,
|
||||
fontStyle: PropTypes.string,
|
||||
textAnchor: PropTypes.string,
|
||||
textDecoration: PropTypes.string,
|
||||
letterSpacing: PropTypes.string,
|
||||
wordSpacing: PropTypes.string,
|
||||
kerning: PropTypes.string,
|
||||
|
||||
Reference in New Issue
Block a user