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
This commit is contained in:
Maurus Cuelenaere
2019-10-29 00:29:20 -07:00
committed by Facebook Github Bot
parent 6ebd3b046e
commit c2c4b43dfe
8 changed files with 116 additions and 6 deletions
@@ -83,6 +83,7 @@ public class ViewProps {
public static final String FONT_SIZE = "fontSize";
public static final String FONT_WEIGHT = "fontWeight";
public static final String FONT_STYLE = "fontStyle";
public static final String FONT_VARIANT = "fontVariant";
public static final String FONT_FAMILY = "fontFamily";
public static final String LINE_HEIGHT = "lineHeight";
public static final String LETTER_SPACING = "letterSpacing";
@@ -10,6 +10,7 @@ package com.facebook.react.views.text;
import android.content.res.AssetManager;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.os.Build;
import android.text.TextPaint;
import android.text.style.MetricAffectingSpan;
import androidx.annotation.NonNull;
@@ -32,27 +33,30 @@ public class CustomStyleSpan extends MetricAffectingSpan implements ReactSpan {
private final int mStyle;
private final int mWeight;
private final @Nullable String mFeatureSettings;
private final @Nullable String mFontFamily;
public CustomStyleSpan(
int fontStyle,
int fontWeight,
@Nullable String fontFeatureSettings,
@Nullable String fontFamily,
@NonNull AssetManager assetManager) {
mStyle = fontStyle;
mWeight = fontWeight;
mFeatureSettings = fontFeatureSettings;
mFontFamily = fontFamily;
mAssetManager = assetManager;
}
@Override
public void updateDrawState(TextPaint ds) {
apply(ds, mStyle, mWeight, mFontFamily, mAssetManager);
apply(ds, mStyle, mWeight, mFeatureSettings, mFontFamily, mAssetManager);
}
@Override
public void updateMeasureState(@NonNull TextPaint paint) {
apply(paint, mStyle, mWeight, mFontFamily, mAssetManager);
apply(paint, mStyle, mWeight, mFeatureSettings, mFontFamily, mAssetManager);
}
/** Returns {@link Typeface#NORMAL} or {@link Typeface#ITALIC}. */
@@ -71,9 +75,17 @@ public class CustomStyleSpan extends MetricAffectingSpan implements ReactSpan {
}
private static void apply(
Paint paint, int style, int weight, @Nullable String family, AssetManager assetManager) {
Paint paint,
int style,
int weight,
@Nullable String fontFeatureSettings,
@Nullable String family,
AssetManager assetManager) {
Typeface typeface = ReactTypefaceUtils.applyStyles(
paint.getTypeface(), style, weight, family, assetManager);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
paint.setFontFeatureSettings(fontFeatureSettings);
}
paint.setTypeface(typeface);
paint.setSubpixelText(true);
}
@@ -18,6 +18,7 @@ import android.view.Gravity;
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.ArrayList;
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 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
new CustomStyleSpan(
textShadowNode.mFontStyle,
textShadowNode.mFontWeight,
textShadowNode.mFontFeatureSettings,
textShadowNode.mFontFamily,
textShadowNode.getThemedContext().getAssets())));
}
@@ -357,6 +360,11 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
*/
protected @Nullable String mFontFamily = null;
/**
* @see android.graphics.Paint#setFontFeatureSettings
*/
protected @Nullable String mFontFeatureSettings = null;
protected boolean mContainsImages = false;
protected Map<Integer, ReactShadowNode> mInlineViews;
@@ -483,6 +491,16 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
}
}
@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);
@@ -13,6 +13,11 @@ import android.graphics.Typeface;
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 class ReactTypefaceUtils {
return fontStyle;
}
public static @Nullable String parseFontVariant(@Nullable ReadableArray fontVariantArray) {
if (fontVariantArray == null || fontVariantArray.size() == 0) {
return null;
}
List<String> 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;
@@ -13,6 +13,7 @@ import android.text.Layout;
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 class TextAttributeProps {
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 @@ public class TextAttributeProps {
}
}
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 class TextAttributeProps {
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
@@ -92,6 +92,7 @@ public class TextLayoutManager {
new CustomStyleSpan(
textAttributes.mFontStyle,
textAttributes.mFontWeight,
textAttributes.mFontFeatureSettings,
textAttributes.mFontFamily,
context.getAssets())));
}