diff --git a/android/src/main/java/com/horcrux/svg/FontData.java b/android/src/main/java/com/horcrux/svg/FontData.java new file mode 100644 index 00000000..2f190b96 --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/FontData.java @@ -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; + } +} diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 5d4bdb81..92b5d55a 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -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 mFontContext = new ArrayList<>(); + private final ArrayList mFontContext = new ArrayList<>(); // Unique input attribute lists (only added if node sets a value) private final ArrayList 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) { diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index cc4250d2..ae9c48e7 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -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) { } } diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index 4ee00ba8..443bb2ee 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -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; diff --git a/android/src/main/java/com/horcrux/svg/VirtualNode.java b/android/src/main/java/com/horcrux/svg/VirtualNode.java index 594e3f68..754989f7 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/VirtualNode.java @@ -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 { /*