mirror of
https://github.com/zoriya/react-native-svg.git
synced 2026-06-03 15:14:18 +00:00
Implement FontData class instead of WritableMap / ReadableMap
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
package com.horcrux.svg;
|
||||
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
import static com.facebook.react.uimanager.ViewProps.FONT_FAMILY;
|
||||
import static com.facebook.react.uimanager.ViewProps.FONT_SIZE;
|
||||
import static com.facebook.react.uimanager.ViewProps.FONT_WEIGHT;
|
||||
|
||||
class FontData {
|
||||
static final double DEFAULT_FONT_SIZE = 12d;
|
||||
|
||||
private static final double DEFAULT_KERNING = 0d;
|
||||
private static final double DEFAULT_WORD_SPACING = 0d;
|
||||
private static final double DEFAULT_LETTER_SPACING = 0d;
|
||||
|
||||
private static final String KERNING = "kerning";
|
||||
private static final String TEXT_ANCHOR = "textAnchor";
|
||||
private static final String WORD_SPACING = "wordSpacing";
|
||||
private static final String LETTER_SPACING = "letterSpacing";
|
||||
private static final String TEXT_DECORATION = "textDecoration";
|
||||
|
||||
final double fontSize;
|
||||
|
||||
final String fontStyle;
|
||||
final String fontFamily;
|
||||
final String fontWeight;
|
||||
|
||||
final String textAnchor;
|
||||
final String textDecoration;
|
||||
|
||||
final double kerning;
|
||||
final double wordSpacing;
|
||||
final double letterSpacing;
|
||||
|
||||
final boolean manualKerning;
|
||||
|
||||
static final FontData Defaults = new FontData();
|
||||
|
||||
private FontData() {
|
||||
fontStyle = "";
|
||||
fontFamily = "";
|
||||
fontWeight = "";
|
||||
|
||||
textAnchor = "start";
|
||||
textDecoration = "none";
|
||||
|
||||
manualKerning = false;
|
||||
kerning = DEFAULT_KERNING;
|
||||
fontSize = DEFAULT_FONT_SIZE;
|
||||
wordSpacing = DEFAULT_WORD_SPACING;
|
||||
letterSpacing = DEFAULT_LETTER_SPACING;
|
||||
}
|
||||
|
||||
private double toAbsolute(String string, double scale, double fontSize) {
|
||||
return PropHelper.fromRelative(
|
||||
string,
|
||||
0,
|
||||
0,
|
||||
scale,
|
||||
fontSize
|
||||
);
|
||||
}
|
||||
|
||||
FontData(ReadableMap font, FontData parent, double scale) {
|
||||
double parentFontSize = parent.fontSize;
|
||||
|
||||
if (font.hasKey(FONT_SIZE)) {
|
||||
String string = font.getString(FONT_SIZE);
|
||||
fontSize = PropHelper.fromRelative(
|
||||
string,
|
||||
parentFontSize,
|
||||
0,
|
||||
1,
|
||||
parentFontSize
|
||||
);
|
||||
} else {
|
||||
fontSize = parentFontSize;
|
||||
}
|
||||
|
||||
fontStyle = font.hasKey(FONT_SIZE) ? font.getString(FONT_SIZE) : parent.fontStyle;
|
||||
fontFamily = font.hasKey(FONT_FAMILY) ? font.getString(FONT_FAMILY) : parent.fontFamily;
|
||||
fontWeight = font.hasKey(FONT_WEIGHT) ? font.getString(FONT_WEIGHT) : parent.fontWeight;
|
||||
|
||||
textAnchor = font.hasKey(TEXT_ANCHOR) ? font.getString(TEXT_ANCHOR) : parent.textAnchor;
|
||||
textDecoration = font.hasKey(TEXT_DECORATION) ? font.getString(TEXT_DECORATION) : parent.textDecoration;
|
||||
|
||||
final boolean hasKerning = font.hasKey(KERNING);
|
||||
manualKerning = hasKerning || parent.manualKerning;
|
||||
|
||||
// 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.
|
||||
kerning = hasKerning ? toAbsolute(font.getString(KERNING), scale, fontSize) : parent.kerning;
|
||||
wordSpacing = font.hasKey(WORD_SPACING) ? toAbsolute(font.getString(WORD_SPACING), scale, fontSize) : parent.wordSpacing;
|
||||
letterSpacing = font.hasKey(LETTER_SPACING) ? toAbsolute(font.getString(LETTER_SPACING), scale, fontSize) : parent.letterSpacing;
|
||||
}
|
||||
}
|
||||
@@ -9,38 +9,20 @@
|
||||
|
||||
package com.horcrux.svg;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
|
||||
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;
|
||||
import static com.horcrux.svg.FontData.DEFAULT_FONT_SIZE;
|
||||
|
||||
// https://www.w3.org/TR/SVG/text.html#TSpanElement
|
||||
class GlyphContext {
|
||||
static final double DEFAULT_FONT_SIZE = 12d;
|
||||
|
||||
// Empty font context map
|
||||
private static final WritableMap DEFAULT_MAP = Arguments.createMap();
|
||||
|
||||
static {
|
||||
DEFAULT_MAP.putDouble(FONT_SIZE, DEFAULT_FONT_SIZE);
|
||||
}
|
||||
|
||||
// Current stack (one per node push/pop)
|
||||
private final ArrayList<ReadableMap> mFontContext = new ArrayList<>();
|
||||
private final ArrayList<FontData> mFontContext = new ArrayList<>();
|
||||
|
||||
// Unique input attribute lists (only added if node sets a value)
|
||||
private final ArrayList<String[]> mXsContext = new ArrayList<>();
|
||||
@@ -65,7 +47,7 @@ class GlyphContext {
|
||||
|
||||
// Calculated on push context, percentage and em length depends on parent font size
|
||||
private double mFontSize = DEFAULT_FONT_SIZE;
|
||||
private ReadableMap topFont = DEFAULT_MAP;
|
||||
private FontData topFont = FontData.Defaults;
|
||||
|
||||
// Current accumulated values
|
||||
// https://www.w3.org/TR/SVG/types.html#DataTypeCoordinate
|
||||
@@ -137,8 +119,6 @@ class GlyphContext {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
|
||||
mFontContext.add(DEFAULT_MAP);
|
||||
|
||||
mXsContext.add(mXs);
|
||||
mYsContext.add(mYs);
|
||||
mDXsContext.add(mDXs);
|
||||
@@ -151,6 +131,8 @@ class GlyphContext {
|
||||
mDYIndices.add(mDYIndex);
|
||||
mRIndices.add(mRIndex);
|
||||
|
||||
mFontContext.add(topFont);
|
||||
|
||||
pushIndices();
|
||||
}
|
||||
|
||||
@@ -160,54 +142,30 @@ class GlyphContext {
|
||||
mX = mY = mDX = mDY = 0;
|
||||
}
|
||||
|
||||
ReadableMap getFont() {
|
||||
FontData getFont() {
|
||||
return topFont;
|
||||
}
|
||||
|
||||
private ReadableMap getTopOrParentFont(GroupShadowNode child) {
|
||||
private FontData getTopOrParentFont(GroupShadowNode child) {
|
||||
if (mTop > 0) {
|
||||
return topFont;
|
||||
} else {
|
||||
GroupShadowNode parentRoot = child.getParentTextRoot();
|
||||
|
||||
while (parentRoot != null) {
|
||||
ReadableMap map = parentRoot.getGlyphContext().getFont();
|
||||
if (map != DEFAULT_MAP) {
|
||||
FontData map = parentRoot.getGlyphContext().getFont();
|
||||
if (map != FontData.Defaults) {
|
||||
return map;
|
||||
}
|
||||
parentRoot = parentRoot.getParentTextRoot();
|
||||
}
|
||||
|
||||
return DEFAULT_MAP;
|
||||
}
|
||||
}
|
||||
|
||||
private static void put(String key, WritableMap map, ReadableMap font, ReadableMap parent) {
|
||||
if (font.hasKey(key)) {
|
||||
map.putString(key, font.getString(key));
|
||||
} else if (parent.hasKey(key)) {
|
||||
map.putString(key, parent.getString(key));
|
||||
}
|
||||
}
|
||||
|
||||
private void putD(String key, WritableMap map, ReadableMap font, ReadableMap parent) {
|
||||
if (font.hasKey(key)) {
|
||||
String string = font.getString(key);
|
||||
double value = PropHelper.fromRelative(
|
||||
string,
|
||||
0,
|
||||
0,
|
||||
mScale,
|
||||
mFontSize
|
||||
);
|
||||
map.putDouble(key, value);
|
||||
} else if (parent.hasKey(key)) {
|
||||
map.putDouble(key, parent.getDouble(key));
|
||||
return FontData.Defaults;
|
||||
}
|
||||
}
|
||||
|
||||
private void pushNodeAndFont(GroupShadowNode node, @Nullable ReadableMap font) {
|
||||
ReadableMap parent = getTopOrParentFont(node);
|
||||
FontData parent = getTopOrParentFont(node);
|
||||
mTop++;
|
||||
|
||||
if (font == null) {
|
||||
@@ -215,42 +173,10 @@ class GlyphContext {
|
||||
return;
|
||||
}
|
||||
|
||||
WritableMap map = Arguments.createMap();
|
||||
mFontContext.add(map);
|
||||
topFont = map;
|
||||
FontData data = new FontData(font, parent, mScale);
|
||||
mFontContext.add(data);
|
||||
topFont = data;
|
||||
|
||||
double parentFontSize = parent.getDouble(FONT_SIZE);
|
||||
|
||||
if (font.hasKey(FONT_SIZE)) {
|
||||
String string = font.getString(FONT_SIZE);
|
||||
double value = PropHelper.fromRelative(
|
||||
string,
|
||||
parentFontSize,
|
||||
0,
|
||||
1,
|
||||
parentFontSize
|
||||
);
|
||||
map.putDouble(FONT_SIZE, value);
|
||||
mFontSize = value;
|
||||
} else {
|
||||
mFontSize = parentFontSize;
|
||||
}
|
||||
|
||||
map.putDouble(FONT_SIZE, mFontSize);
|
||||
|
||||
put(FONT_STYLE, map, font, parent);
|
||||
put(FONT_FAMILY, map, font, parent);
|
||||
put(FONT_WEIGHT, 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.
|
||||
putD(KERNING, map, font, parent);
|
||||
putD(WORD_SPACING, map, font, parent);
|
||||
putD(LETTER_SPACING, map, font, parent);
|
||||
}
|
||||
|
||||
void pushContext(GroupShadowNode node, @Nullable ReadableMap font) {
|
||||
|
||||
@@ -19,7 +19,6 @@ import android.graphics.PathMeasure;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Typeface;
|
||||
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.uimanager.ReactShadowNode;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
|
||||
@@ -28,11 +27,6 @@ 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
|
||||
*/
|
||||
@@ -45,10 +39,6 @@ class TSpanShadowNode extends TextShadowNode {
|
||||
private static final String OTF = ".otf";
|
||||
private static final String TTF = ".ttf";
|
||||
|
||||
private static final double DEFAULT_KERNING = 0d;
|
||||
private static final double DEFAULT_WORD_SPACING = 0d;
|
||||
private static final double DEFAULT_LETTER_SPACING = 0d;
|
||||
|
||||
private Path mCache;
|
||||
private @Nullable String mContent;
|
||||
private TextPathShadowNode textPath;
|
||||
@@ -119,14 +109,13 @@ class TSpanShadowNode extends TextShadowNode {
|
||||
}
|
||||
|
||||
GlyphContext gc = getTextRootGlyphContext();
|
||||
ReadableMap font = gc.getFont();
|
||||
FontData font = gc.getFont();
|
||||
applyTextPropertiesToPaint(paint, font);
|
||||
|
||||
double distance = 0;
|
||||
double renderMethodScaling = 1;
|
||||
final double textMeasure = paint.measureText(line);
|
||||
double offset = font.hasKey(TEXT_ANCHOR) ?
|
||||
getTextAnchorShift(textMeasure, font.getString(TEXT_ANCHOR)) : 0;
|
||||
double offset = getTextAnchorShift(textMeasure, font.textAnchor);
|
||||
|
||||
PathMeasure pm = null;
|
||||
if (textPath != null) {
|
||||
@@ -176,17 +165,12 @@ class TSpanShadowNode extends TextShadowNode {
|
||||
*
|
||||
* */
|
||||
|
||||
final boolean hasKerning = font.hasKey(KERNING);
|
||||
double kerning = hasKerning ? font.getDouble(KERNING) : DEFAULT_KERNING;
|
||||
final boolean autoKerning = !hasKerning;
|
||||
final boolean autoKerning = !font.manualKerning;
|
||||
double kerning = font.kerning;
|
||||
|
||||
final double wordSpacing = font.hasKey(WORD_SPACING) ?
|
||||
font.getDouble(WORD_SPACING)
|
||||
: DEFAULT_WORD_SPACING;
|
||||
final double wordSpacing = font.wordSpacing;
|
||||
|
||||
final double letterSpacing = font.hasKey(LETTER_SPACING) ?
|
||||
font.getDouble(LETTER_SPACING)
|
||||
: DEFAULT_LETTER_SPACING;
|
||||
final double letterSpacing = font.letterSpacing;
|
||||
|
||||
for (int index = 0; index < length; index++) {
|
||||
glyph = new Path();
|
||||
@@ -249,31 +233,27 @@ class TSpanShadowNode extends TextShadowNode {
|
||||
return path;
|
||||
}
|
||||
|
||||
private void applyTextPropertiesToPaint(Paint paint, ReadableMap font) {
|
||||
private void applyTextPropertiesToPaint(Paint paint, FontData font) {
|
||||
AssetManager assetManager = getThemedContext().getResources().getAssets();
|
||||
|
||||
double fontSize = font.getDouble(FONT_SIZE) * mScale;
|
||||
double fontSize = font.fontSize * mScale;
|
||||
|
||||
boolean isBold = font.hasKey(FONT_WEIGHT) &&
|
||||
BOLD.equals(font.getString(FONT_WEIGHT));
|
||||
boolean isBold = BOLD.equals(font.fontWeight);
|
||||
|
||||
boolean isItalic = font.hasKey(FONT_STYLE) &&
|
||||
ITALIC.equals(font.getString(FONT_STYLE));
|
||||
boolean isItalic = ITALIC.equals(font.fontStyle);
|
||||
|
||||
boolean underlineText = false;
|
||||
boolean strikeThruText = false;
|
||||
|
||||
if (font.hasKey(TEXT_DECORATION)) {
|
||||
String decoration = font.getString(TEXT_DECORATION);
|
||||
switch (decoration) {
|
||||
case TEXT_DECORATION_UNDERLINE:
|
||||
underlineText = true;
|
||||
break;
|
||||
String decoration = font.textDecoration;
|
||||
switch (decoration) {
|
||||
case TEXT_DECORATION_UNDERLINE:
|
||||
underlineText = true;
|
||||
break;
|
||||
|
||||
case TEXT_DECORATION_LINE_THROUGH:
|
||||
strikeThruText = true;
|
||||
break;
|
||||
}
|
||||
case TEXT_DECORATION_LINE_THROUGH:
|
||||
strikeThruText = true;
|
||||
break;
|
||||
}
|
||||
|
||||
int fontStyle;
|
||||
@@ -288,16 +268,17 @@ class TSpanShadowNode extends TextShadowNode {
|
||||
}
|
||||
|
||||
Typeface typeface = null;
|
||||
final String fontFamily = font.fontFamily;
|
||||
try {
|
||||
String path = FONTS + font.getString(FONT_FAMILY) + OTF;
|
||||
String path = FONTS + fontFamily + OTF;
|
||||
typeface = Typeface.createFromAsset(assetManager, path);
|
||||
} catch (Exception ignored) {
|
||||
try {
|
||||
String path = FONTS + font.getString(FONT_FAMILY) + TTF;
|
||||
String path = FONTS + fontFamily + TTF;
|
||||
typeface = Typeface.createFromAsset(assetManager, path);
|
||||
} catch (Exception ignored2) {
|
||||
try {
|
||||
typeface = Typeface.create(font.getString(FONT_FAMILY), fontStyle);
|
||||
typeface = Typeface.create(fontFamily, fontStyle);
|
||||
} catch (Exception ignored3) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,12 +37,6 @@ class TextShadowNode extends GroupShadowNode {
|
||||
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 @Nullable ReadableArray mPositionX;
|
||||
private @Nullable ReadableArray mPositionY;
|
||||
private @Nullable ReadableArray mRotate;
|
||||
|
||||
@@ -26,7 +26,7 @@ import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.horcrux.svg.GlyphContext.DEFAULT_FONT_SIZE;
|
||||
import static com.horcrux.svg.FontData.DEFAULT_FONT_SIZE;
|
||||
|
||||
abstract class VirtualNode extends LayoutShadowNode {
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user