diff --git a/android/src/main/java/com/horcrux/svg/GlyphContext.java b/android/src/main/java/com/horcrux/svg/GlyphContext.java index 54800dbd..5d4bdb81 100644 --- a/android/src/main/java/com/horcrux/svg/GlyphContext.java +++ b/android/src/main/java/com/horcrux/svg/GlyphContext.java @@ -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; } diff --git a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java index 2f5863fd..c2121b09 100644 --- a/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TSpanShadowNode.java @@ -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) { } } diff --git a/android/src/main/java/com/horcrux/svg/TextShadowNode.java b/android/src/main/java/com/horcrux/svg/TextShadowNode.java index 420bf7c1..4ee00ba8 100644 --- a/android/src/main/java/com/horcrux/svg/TextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/TextShadowNode.java @@ -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) { diff --git a/elements/TSpan.js b/elements/TSpan.js index 4f97de41..c272ea73 100644 --- a/elements/TSpan.js +++ b/elements/TSpan.js @@ -16,7 +16,6 @@ export default class extends Shape { ...fontProps, dx: numberProp, dy: numberProp, - textAnchor: PropTypes.oneOf(['start', 'middle', 'end']) }; static childContextTypes = { diff --git a/elements/Text.js b/elements/Text.js index 60f86dd1..ee1e0c0d 100644 --- a/elements/Text.js +++ b/elements/Text.js @@ -15,7 +15,6 @@ export default class extends Shape { ...fontProps, dx: numberProp, dy: numberProp, - textAnchor: PropTypes.oneOf(['start', 'middle', 'end']) }; static childContextTypes = { diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index bf623521..b9dfe5ae 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -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, }; } diff --git a/lib/props.js b/lib/props.js index 1140650a..6398e2a0 100644 --- a/lib/props.js +++ b/lib/props.js @@ -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,