Skip to content
Permalink
Browse files
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: #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](getdelta@10cafca) 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
  • Loading branch information
mcuelenaere authored and facebook-github-bot committed Oct 29, 2019
1 parent 6ebd3b0 commit c2c4b43dfe098342a6958a20f6a1d841f7526e48
Showing 8 changed files with 116 additions and 6 deletions.
@@ -54,9 +54,6 @@ const DeprecatedTextStylePropTypes = {
| '800'
| '900',
>),
/**
* @platform ios
*/
fontVariant: (ReactPropTypes.arrayOf(
ReactPropTypes.oneOf([
'small-caps',
@@ -574,6 +574,33 @@ class TextExample extends React.Component<{}> {
This very long text should be clipped and this will not be visible.
</Text>
</RNTesterBlock>
<RNTesterBlock title="Font variants">
<Text style={{fontVariant: ['small-caps']}}>Small Caps{'\n'}</Text>
<Text
style={{
fontFamily: 'Roboto',
fontVariant: ['oldstyle-nums'],
}}>
Old Style nums 0123456789{'\n'}
</Text>
<Text
style={{
fontFamily: 'Roboto',
fontVariant: ['lining-nums'],
}}>
Lining nums 0123456789{'\n'}
</Text>
<Text style={{fontVariant: ['tabular-nums']}}>
Tabular nums{'\n'}
1111{'\n'}
2222{'\n'}
</Text>
<Text style={{fontVariant: ['proportional-nums']}}>
Proportional nums{'\n'}
1111{'\n'}
2222{'\n'}
</Text>
</RNTesterBlock>
<RNTesterBlock title="Include Font Padding">
<View
style={{
@@ -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 @@
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 int getWeight() {
}

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 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<Integer, ReactShadowNode> 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);
@@ -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<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.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
@@ -92,6 +92,7 @@ private static void buildSpannableFromFragment(
new CustomStyleSpan(
textAttributes.mFontStyle,
textAttributes.mFontWeight,
textAttributes.mFontFeatureSettings,
textAttributes.mFontFamily,
context.getAssets())));
}

0 comments on commit c2c4b43

Please sign in to comment.