From c2c4b43dfe098342a6958a20f6a1d841f7526e48 Mon Sep 17 00:00:00 2001 From: Maurus Cuelenaere Date: Tue, 29 Oct 2019 00:26:23 -0700 Subject: [PATCH] Add Android support for fontVariant prop (#27006) Summary: Android was missing support for the `fontVariant` prop in TextViews, this PR adds that. ## Changelog [Android] [Added] - Add Android support for fontVariant prop Pull Request resolved: https://github.com/facebook/react-native/pull/27006 Test Plan: Since I can't get RNTester to work locally (it crashes when loading `libyoga.so` on `No implementation found for long com.facebook.yoga.YogaNative.jni_YGConfigNew()`), I'll post some screenshots below of our app showing the difference. We are using a slightly different [version](https://github.com/getdelta/react-native/commit/10cafcaa0798e5dbe8b56d461885fa84c6953739) of this commit, since we're still on 0.60, but the gist remains the same when rebased on master. Before: ![Screenshot_20191025-130325__01](https://user-images.githubusercontent.com/1682432/67566586-7b3f2880-f728-11e9-85c0-57667d645153.jpg) After: ![Screenshot_20191025-130444__01](https://user-images.githubusercontent.com/1682432/67566599-842ffa00-f728-11e9-988a-1b12ee393b83.jpg) Differential Revision: D18179642 Pulled By: mdvacca fbshipit-source-id: 03a050aa76e7bafa0343354dfa778cf74af5abd2 --- .../DeprecatedTextStylePropTypes.js | 3 -- .../js/examples/Text/TextExample.android.js | 27 ++++++++++++++ .../facebook/react/uimanager/ViewProps.java | 1 + .../react/views/text/CustomStyleSpan.java | 18 ++++++++-- .../views/text/ReactBaseTextShadowNode.java | 18 ++++++++++ .../react/views/text/ReactTypefaceUtils.java | 35 +++++++++++++++++++ .../react/views/text/TextAttributeProps.java | 19 ++++++++++ .../react/views/text/TextLayoutManager.java | 1 + 8 files changed, 116 insertions(+), 6 deletions(-) diff --git a/Libraries/DeprecatedPropTypes/DeprecatedTextStylePropTypes.js b/Libraries/DeprecatedPropTypes/DeprecatedTextStylePropTypes.js index 63ab3213ffd6e7..428b2d38aab2e3 100644 --- a/Libraries/DeprecatedPropTypes/DeprecatedTextStylePropTypes.js +++ b/Libraries/DeprecatedPropTypes/DeprecatedTextStylePropTypes.js @@ -54,9 +54,6 @@ const DeprecatedTextStylePropTypes = { | '800' | '900', >), - /** - * @platform ios - */ fontVariant: (ReactPropTypes.arrayOf( ReactPropTypes.oneOf([ 'small-caps', diff --git a/RNTester/js/examples/Text/TextExample.android.js b/RNTester/js/examples/Text/TextExample.android.js index ae3aa5c81179da..fc4a241ee23cdc 100644 --- a/RNTester/js/examples/Text/TextExample.android.js +++ b/RNTester/js/examples/Text/TextExample.android.js @@ -574,6 +574,33 @@ class TextExample extends React.Component<{}> { This very long text should be clipped and this will not be visible. + + Small Caps{'\n'} + + Old Style nums 0123456789{'\n'} + + + Lining nums 0123456789{'\n'} + + + Tabular nums{'\n'} + 1111{'\n'} + 2222{'\n'} + + + Proportional nums{'\n'} + 1111{'\n'} + 2222{'\n'} + + = Build.VERSION_CODES.LOLLIPOP) { + paint.setFontFeatureSettings(fontFeatureSettings); + } paint.setTypeface(typeface); paint.setSubpixelText(true); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java index 717aec41e9628b..76c658212edd15 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java @@ -18,6 +18,7 @@ import androidx.annotation.Nullable; import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; +import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.uimanager.IllegalViewOperationException; import com.facebook.react.uimanager.LayoutShadowNode; @@ -33,6 +34,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; /** * {@link ReactShadowNode} abstract class for spannable text nodes. @@ -195,6 +197,7 @@ private static void buildSpannedFromShadowNode( new CustomStyleSpan( textShadowNode.mFontStyle, textShadowNode.mFontWeight, + textShadowNode.mFontFeatureSettings, textShadowNode.mFontFamily, textShadowNode.getThemedContext().getAssets()))); } @@ -357,6 +360,11 @@ protected Spannable spannedFromShadowNode( */ protected @Nullable String mFontFamily = null; + /** + * @see android.graphics.Paint#setFontFeatureSettings + */ + protected @Nullable String mFontFeatureSettings = null; + protected boolean mContainsImages = false; protected Map mInlineViews; @@ -483,6 +491,16 @@ public void setFontWeight(@Nullable String fontWeightString) { } } + @ReactProp(name = ViewProps.FONT_VARIANT) + public void setFontVariant(@Nullable ReadableArray fontVariantArray) { + String fontFeatureSettings = ReactTypefaceUtils.parseFontVariant(fontVariantArray); + + if (!Objects.equals(fontFeatureSettings, mFontFeatureSettings)) { + mFontFeatureSettings = fontFeatureSettings; + markUpdated(); + } + } + @ReactProp(name = ViewProps.FONT_STYLE) public void setFontStyle(@Nullable String fontStyleString) { int fontStyle = ReactTypefaceUtils.parseFontStyle(fontStyleString); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTypefaceUtils.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTypefaceUtils.java index 7910deadf6e162..c811e2f757cd8c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTypefaceUtils.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTypefaceUtils.java @@ -13,6 +13,11 @@ import androidx.annotation.Nullable; +import com.facebook.react.bridge.ReadableArray; + +import java.util.ArrayList; +import java.util.List; + public class ReactTypefaceUtils { public static final int UNSET = -1; @@ -38,6 +43,36 @@ public static int parseFontStyle(@Nullable String fontStyleString) { return fontStyle; } + public static @Nullable String parseFontVariant(@Nullable ReadableArray fontVariantArray) { + if (fontVariantArray == null || fontVariantArray.size() == 0) { + return null; + } + + List features = new ArrayList<>(); + for (int i = 0; i < fontVariantArray.size(); i++) { + // see https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist + switch (fontVariantArray.getString(i)) { + case "small-caps": + features.add("'smcp'"); + break; + case "oldstyle-nums": + features.add("'onum'"); + break; + case "lining-nums": + features.add("'lnum'"); + break; + case "tabular-nums": + features.add("'tnum'"); + break; + case "proportional-nums": + features.add("'pnum'"); + break; + } + } + + return String.join(", ", features); + } + public static Typeface applyStyles(@Nullable Typeface typeface, int style, int weight, @Nullable String family, AssetManager assetManager) { int oldStyle; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java index a0bf03f1413c8e..ca26c4f7a6d723 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java @@ -13,6 +13,7 @@ import android.view.Gravity; import androidx.annotation.Nullable; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; +import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.uimanager.PixelUtil; import com.facebook.react.uimanager.ReactStylesDiffMap; @@ -92,6 +93,11 @@ public class TextAttributeProps { */ protected @Nullable String mFontFamily = null; + /** + * @see android.graphics.Paint#setFontFeatureSettings + */ + protected @Nullable String mFontFeatureSettings = null; + protected boolean mContainsImages = false; protected float mHeightOfTallestInlineImage = Float.NaN; @@ -114,6 +120,7 @@ public TextAttributeProps(ReactStylesDiffMap props) { setFontFamily(getStringProp(ViewProps.FONT_FAMILY)); setFontWeight(getStringProp(ViewProps.FONT_WEIGHT)); setFontStyle(getStringProp(ViewProps.FONT_STYLE)); + setFontVariant(getArrayProp(ViewProps.FONT_VARIANT)); setIncludeFontPadding(getBooleanProp(ViewProps.INCLUDE_FONT_PADDING, true)); setTextDecorationLine(getStringProp(ViewProps.TEXT_DECORATION_LINE)); setTextBreakStrategy(getStringProp(ViewProps.TEXT_BREAK_STRATEGY)); @@ -155,6 +162,14 @@ private float getFloatProp(String name, float defaultvalue) { } } + private @Nullable ReadableArray getArrayProp(String name) { + if (mProps.hasKey(name)) { + return mProps.getArray(name); + } else { + return null; + } + } + // Returns a line height which takes into account the requested line height // and the height of the inline images. public float getEffectiveLineHeight() { @@ -280,6 +295,10 @@ public void setFontFamily(@Nullable String fontFamily) { mFontFamily = fontFamily; } + public void setFontVariant(@Nullable ReadableArray fontVariant) { + mFontFeatureSettings = ReactTypefaceUtils.parseFontVariant(fontVariant); + } + /** * /* This code is duplicated in ReactTextInputManager /* TODO: Factor into a common place they * can both use diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java index 995856c7102ba0..25697da7b594c6 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java @@ -92,6 +92,7 @@ private static void buildSpannableFromFragment( new CustomStyleSpan( textAttributes.mFontStyle, textAttributes.mFontWeight, + textAttributes.mFontFeatureSettings, textAttributes.mFontFamily, context.getAssets()))); }