diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTVirtualText.java b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTVirtualText.java index fd980f73b..b30cfb9f7 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTVirtualText.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTVirtualText.java @@ -16,6 +16,7 @@ import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.TextUtils; +import com.facebook.react.bridge.ReadableMap; import com.facebook.react.uimanager.PixelUtil; import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.ViewProps; @@ -29,7 +30,13 @@ import com.facebook.react.uimanager.ViewProps; private static final String ITALIC = "italic"; private static final String NORMAL = "normal"; + private static final String PROP_SHADOW_OFFSET = "textShadowOffset"; + private static final String PROP_SHADOW_RADIUS = "textShadowRadius"; + private static final String PROP_SHADOW_COLOR = "textShadowColor"; + private static final int DEFAULT_TEXT_SHADOW_COLOR = 0x55000000; + private FontStylingSpan mFontStylingSpan = FontStylingSpan.INSTANCE; + private ShadowStyleSpan mShadowStyleSpan = ShadowStyleSpan.INSTANCE; @Override protected void performCollectText(SpannableStringBuilder builder) { @@ -49,6 +56,16 @@ import com.facebook.react.uimanager.ViewProps; end, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + if (mShadowStyleSpan.getColor() != 0 && mShadowStyleSpan.getRadius() != 0) { + mShadowStyleSpan.freeze(); + + builder.setSpan( + mShadowStyleSpan, + begin, + end, + Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + } + for (int i = 0, childCount = getChildCount(); i < childCount; ++i) { FlatTextShadowNode child = (FlatTextShadowNode) getChildAt(i); child.applySpans(builder); @@ -156,6 +173,42 @@ import com.facebook.react.uimanager.ViewProps; } } + @ReactProp(name = PROP_SHADOW_OFFSET) + public void setTextShadowOffset(@Nullable ReadableMap offsetMap) { + float dx = 0; + float dy = 0; + if (offsetMap != null) { + if (offsetMap.hasKey("width")) { + dx = PixelUtil.toPixelFromDIP(offsetMap.getDouble("width")); + } + if (offsetMap.hasKey("height")) { + dy = PixelUtil.toPixelFromDIP(offsetMap.getDouble("height")); + } + } + + if (!mShadowStyleSpan.offsetMatches(dx, dy)) { + getShadowSpan().setOffset(dx, dy); + notifyChanged(false); + } + } + + @ReactProp(name = PROP_SHADOW_RADIUS) + public void setTextShadowRadius(float textShadowRadius) { + textShadowRadius = PixelUtil.toPixelFromDIP(textShadowRadius); + if (mShadowStyleSpan.getRadius() != textShadowRadius) { + getShadowSpan().setRadius(textShadowRadius); + notifyChanged(false); + } + } + + @ReactProp(name = PROP_SHADOW_COLOR, defaultInt = DEFAULT_TEXT_SHADOW_COLOR, customType = "Color") + public void setTextShadowColor(int textShadowColor) { + if (mShadowStyleSpan.getColor() != textShadowColor) { + getShadowSpan().setColor(textShadowColor); + notifyChanged(false); + } + } + /** * Returns font size for this node. * When called on RCTText, this value is never -1 (unset). @@ -179,6 +232,13 @@ import com.facebook.react.uimanager.ViewProps; return mFontStylingSpan; } + private final ShadowStyleSpan getShadowSpan() { + if (mShadowStyleSpan.isFrozen()) { + mShadowStyleSpan = mShadowStyleSpan.mutableCopy(); + } + return mShadowStyleSpan; + } + /** * Return -1 if the input string is not a valid numeric fontWeight (100, 200, ..., 900), otherwise * return the weight. diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/ShadowStyleSpan.java b/ReactAndroid/src/main/java/com/facebook/react/flat/ShadowStyleSpan.java new file mode 100644 index 000000000..6f79adcb4 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/ShadowStyleSpan.java @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.flat; + +import android.text.TextPaint; +import android.text.style.CharacterStyle; + +/* package */ final class ShadowStyleSpan extends CharacterStyle { + + /* package */ static final ShadowStyleSpan INSTANCE = new ShadowStyleSpan(0, 0, 0, 0, true); + + private float mDx; + private float mDy; + private float mRadius; + private int mColor; + private boolean mFrozen; + + private ShadowStyleSpan(float dx, float dy, float radius, int color, boolean frozen) { + mDx = dx; + mDy = dy; + mRadius = radius; + mColor = color; + mFrozen = frozen; + } + + public boolean offsetMatches(float dx, float dy) { + return mDx == dx && mDy == dy; + } + + public void setOffset(float dx, float dy) { + mDx = dx; + mDy = dy; + } + + public float getRadius() { + return mRadius; + } + + public void setRadius(float radius) { + mRadius = radius; + } + + public int getColor() { + return mColor; + } + + public void setColor(int color) { + mColor = color; + } + + /* package */ ShadowStyleSpan mutableCopy() { + return new ShadowStyleSpan(mDx, mDy, mRadius, mColor, false); + } + + /* package */ boolean isFrozen() { + return mFrozen; + } + + /* package */ void freeze() { + mFrozen = true; + } + + @Override + public void updateDrawState(TextPaint textPaint) { + textPaint.setShadowLayer(mRadius, mDx, mDy, mColor); + } +}