Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(android): font variation settings #44667

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = {
*/
color: colorAttributes,
fontFamily: true,
fontVariationSettings: true,
fontSize: true,
fontStyle: true,
fontVariant: {process: processFontVariant},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ export interface TextStyleAndroid extends ViewStyle {
export interface TextStyle extends TextStyleIOS, TextStyleAndroid, ViewStyle {
color?: ColorValue | undefined;
fontFamily?: string | undefined;
fontVariationSettings?: string | undefined;
fontSize?: number | undefined;
fontStyle?: 'normal' | 'italic' | undefined;
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,7 @@ export type ____TextStyle_InternalCore = $ReadOnly<{
...$Exact<____ViewStyle_Internal>,
color?: ____ColorValue_Internal,
fontFamily?: string,
fontVariationSettings?: string,
fontSize?: number,
fontStyle?: 'normal' | 'italic',
fontWeight?: ____FontWeight_Internal,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7819,6 +7819,7 @@ export type ____TextStyle_InternalCore = $ReadOnly<{
...$Exact<____ViewStyle_Internal>,
color?: ____ColorValue_Internal,
fontFamily?: string,
fontVariationSettings?: string,
fontSize?: number,
fontStyle?: \\"normal\\" | \\"italic\\",
fontWeight?: ____FontWeight_Internal,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public object ViewProps {
public const val FONT_STYLE: String = "fontStyle"
public const val FONT_VARIANT: String = "fontVariant"
public const val FONT_FAMILY: String = "fontFamily"
public const val FONT_VARIATION_SETTINGS: String = "fontVariationSettings";
public const val LINE_HEIGHT: String = "lineHeight"
public const val LETTER_SPACING: String = "letterSpacing"
public const val NEEDS_OFFSCREEN_ALPHA_COMPOSITING: String = "needsOffscreenAlphaCompositing"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ private static void buildSpannedFromShadowNode(
}
if (textShadowNode.mFontStyle != ReactConstants.UNSET
|| textShadowNode.mFontWeight != ReactConstants.UNSET
|| textShadowNode.mFontVariationSettings != null
|| textShadowNode.mFontFamily != null) {
ops.add(
new SetSpanOperation(
Expand All @@ -203,6 +204,7 @@ private static void buildSpannedFromShadowNode(
textShadowNode.mFontWeight,
textShadowNode.mFontFeatureSettings,
textShadowNode.mFontFamily,
textShadowNode.mFontVariationSettings,
textShadowNode.getThemedContext().getAssets())));
}
if (textShadowNode.mIsUnderlineTextDecorationSet) {
Expand Down Expand Up @@ -348,6 +350,11 @@ protected Spannable spannedFromShadowNode(

protected int mFontWeight = ReactConstants.UNSET;

/**
* mFontVariationSettings can be used for variable font features e.g: 'wght' 850
*/
protected String mFontVariationSettings;

/**
* NB: If a font family is used that does not have a style in a certain Android version (ie.
* monospace bold pre Android 5.0), that style (ie. bold) will not be inherited by nested Text
Expand Down Expand Up @@ -525,6 +532,12 @@ public void setFontWeight(@Nullable String fontWeightString) {
}
}

@ReactProp(name = ViewProps.FONT_VARIATION_SETTINGS)
public void setFontVariationSettings(@Nullable String fontVariationSettings) {
mFontVariationSettings = fontVariationSettings;
markUpdated();
}

@ReactProp(name = ViewProps.FONT_VARIANT)
public void setFontVariant(@Nullable ReadableArray fontVariantArray) {
String fontFeatureSettings = ReactTypefaceUtils.parseFontVariant(fontVariantArray);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ public class TextAttributeProps {
public static final short TA_KEY_LINE_BREAK_STRATEGY = 25;
public static final short TA_KEY_ROLE = 26;
public static final short TA_KEY_TEXT_TRANSFORM = 27;
public static final short TA_KEY_ALIGNMENT_VERTICAL = 28;
public static final short TA_KEY_FONT_VARIATION_SETTINGS = 29;

public static final int UNSET = -1;

Expand Down Expand Up @@ -135,6 +137,12 @@ public class TextAttributeProps {
*/
protected @Nullable String mFontFamily = null;

/**
* mFontVariationSettings can be used for variable font features e.g: 'wght' 850
* This works for Android 8.1 and above.
*/
protected @Nullable String mFontVariationSettings = null;

/**
* @see android.graphics.Paint#setFontFeatureSettings
*/
Expand Down Expand Up @@ -225,6 +233,9 @@ public static TextAttributeProps fromMapBuffer(MapBuffer props) {
case TA_KEY_TEXT_TRANSFORM:
result.setTextTransform(entry.getStringValue());
break;
case TA_KEY_FONT_VARIATION_SETTINGS:
result.setFontVariationSettings(entry.getStringValue());
break;
}
}

Expand Down Expand Up @@ -252,6 +263,7 @@ public static TextAttributeProps fromReadableMap(ReactStylesDiffMap props) {
? props.getInt(ViewProps.BACKGROUND_COLOR, 0)
: null);
result.setFontFamily(getStringProp(props, ViewProps.FONT_FAMILY));
result.setFontVariationSettings(getStringProp(props, ViewProps.FONT_VARIATION_SETTINGS));
result.setFontWeight(getStringProp(props, ViewProps.FONT_WEIGHT));
result.setFontStyle(getStringProp(props, ViewProps.FONT_STYLE));
result.setFontVariant(getArrayProp(props, ViewProps.FONT_VARIANT));
Expand Down Expand Up @@ -465,10 +477,18 @@ public String getFontFamily() {
return mFontFamily;
}

public String getFontVariationSettings() {
return mFontVariationSettings;
}

private void setFontFamily(@Nullable String fontFamily) {
mFontFamily = fontFamily;
}

private void setFontVariationSettings(String fontVariationSettings) {
mFontVariationSettings = fontVariationSettings;
}

private void setFontVariant(@Nullable ReadableArray fontVariant) {
mFontFeatureSettings = ReactTypefaceUtils.parseFontVariant(fontVariant);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ private static void buildSpannableFromFragments(
new SetSpanOperation(start, end, new ReactAbsoluteSizeSpan(textAttributes.mFontSize)));
if (textAttributes.mFontStyle != ReactConstants.UNSET
|| textAttributes.mFontWeight != ReactConstants.UNSET
|| textAttributes.mFontVariationSettings != null
|| textAttributes.mFontFamily != null) {
ops.add(
new SetSpanOperation(
Expand All @@ -259,6 +260,7 @@ private static void buildSpannableFromFragments(
textAttributes.mFontWeight,
textAttributes.mFontFeatureSettings,
textAttributes.mFontFamily,
textAttributes.mFontVariationSettings,
context.getAssets())));
}
if (textAttributes.mIsUnderlineTextDecorationSet) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ package com.facebook.react.views.text.internal.span
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 com.facebook.react.common.ReactConstants
Expand All @@ -32,14 +33,15 @@ public class CustomStyleSpan(
private val privateWeight: Int,
public val fontFeatureSettings: String?,
public val fontFamily: String?,
public val fontVariationSettings: String?,
private val assetManager: AssetManager
) : MetricAffectingSpan(), ReactSpan {
public override fun updateDrawState(ds: TextPaint) {
apply(ds, privateStyle, privateWeight, fontFeatureSettings, fontFamily, assetManager)
apply(ds, privateStyle, privateWeight, fontFeatureSettings, fontFamily, fontVariationSettings, assetManager)
}

public override fun updateMeasureState(paint: TextPaint) {
apply(paint, privateStyle, privateWeight, fontFeatureSettings, fontFamily, assetManager)
apply(paint, privateStyle, privateWeight, fontFeatureSettings, fontFamily, fontVariationSettings, assetManager)
}

public val style: Int
Expand All @@ -59,19 +61,28 @@ public class CustomStyleSpan(
}

public companion object {
private val TAG = CustomStyleSpan::class.simpleName
private fun apply(
paint: Paint,
style: Int,
weight: Int,
fontFeatureSettingsParam: String?,
family: String?,
fontVariationSettingsParam: String?,
assetManager: AssetManager
) {
val typeface =
ReactTypefaceUtils.applyStyles(paint.typeface, style, weight, family, assetManager)
paint.apply {
fontFeatureSettings = fontFeatureSettingsParam
setTypeface(typeface)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
fontVariationSettings = fontVariationSettingsParam
} catch (e: IllegalArgumentException) {
// Do nothing
}
}
isSubpixelText = true
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;

import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatEditText;
import androidx.core.util.Predicate;
Expand Down Expand Up @@ -527,7 +529,7 @@ public void setInputType(int type) {
/**
* If set forces multiline on input, because of a restriction on Android source that enables
* multiline only for inputs of type Text and Multiline on method {@link
* android.widget.TextView#isMultilineInputType(int)}} Source: {@Link <a
* TextView#isMultilineInputType(int)}} Source: {@Link <a
* href='https://android.googlesource.com/platform/frameworks/base/+/jb-release/core/java/android/widget/TextView.java'>TextView.java</a>}
*/
if (isMultiline()) {
Expand Down Expand Up @@ -598,6 +600,7 @@ public void maybeUpdateTypeface() {
if (mFontStyle != ReactConstants.UNSET
|| mFontWeight != ReactConstants.UNSET
|| mFontFamily != null
|| getFontVariationSettingsInternal() != null
|| getFontFeatureSettings() != null) {
setPaintFlags(getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG);
} else {
Expand Down Expand Up @@ -771,10 +774,20 @@ private void stripStyleEquivalentSpans(SpannableStringBuilder sb) {
return span.getStyle() == mFontStyle
&& Objects.equals(span.getFontFamily(), mFontFamily)
&& span.getWeight() == mFontWeight
&& Objects.equals(span.getFontVariationSettings(), getFontVariationSettingsInternal())
&& Objects.equals(span.getFontFeatureSettings(), getFontFeatureSettings());
});
}

// Font variation settings is only available on API 26+, we return null if not available
private @Nullable String getFontVariationSettingsInternal() {
String fontVariationSettings = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
fontVariationSettings = super.getFontVariationSettings();
}
return fontVariationSettings;
}

private <T> void stripSpansOfKind(
SpannableStringBuilder sb, Class<T> clazz, Predicate<T> shouldStrip) {
T[] spans = sb.getSpans(0, sb.length(), clazz);
Expand Down Expand Up @@ -829,13 +842,15 @@ private void addSpansFromStyleAttributes(SpannableStringBuilder workingText) {
if (mFontStyle != ReactConstants.UNSET
|| mFontWeight != ReactConstants.UNSET
|| mFontFamily != null
|| getFontVariationSettingsInternal() != null
|| getFontFeatureSettings() != null) {
workingText.setSpan(
new CustomStyleSpan(
mFontStyle,
mFontWeight,
getFontFeatureSettings(),
mFontFamily,
getFontVariationSettingsInternal(),
getContext().getAssets()),
0,
workingText.length(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,11 @@ public void setFontFamily(ReactEditText view, String fontFamily) {
view.setFontFamily(fontFamily);
}

@ReactProp(name = ViewProps.FONT_VARIATION_SETTINGS)
public void setFontVariationSettings(ReactEditText view, String fontVariationSettings) {
view.setFontVariationSettings(fontVariationSettings);
}

@ReactProp(name = ViewProps.MAX_FONT_SIZE_MULTIPLIER, defaultFloat = Float.NaN)
public void setMaxFontSizeMultiplier(ReactEditText view, float maxFontSizeMultiplier) {
view.setMaxFontSizeMultiplier(maxFontSizeMultiplier);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ void TextAttributes::apply(TextAttributes textAttributes) {
// Font
fontFamily = !textAttributes.fontFamily.empty() ? textAttributes.fontFamily
: fontFamily;
fontVariationSettings = !textAttributes.fontVariationSettings.empty() ? textAttributes.fontVariationSettings
: fontVariationSettings;
fontSize =
!std::isnan(textAttributes.fontSize) ? textAttributes.fontSize : fontSize;
fontSizeMultiplier = !std::isnan(textAttributes.fontSizeMultiplier)
Expand Down Expand Up @@ -121,6 +123,7 @@ bool TextAttributes::operator==(const TextAttributes& rhs) const {
foregroundColor,
backgroundColor,
fontFamily,
fontVariationSettings,
fontWeight,
fontStyle,
fontVariant,
Expand All @@ -145,6 +148,7 @@ bool TextAttributes::operator==(const TextAttributes& rhs) const {
rhs.foregroundColor,
rhs.backgroundColor,
rhs.fontFamily,
rhs.fontVariationSettings,
rhs.fontWeight,
rhs.fontStyle,
rhs.fontVariant,
Expand Down Expand Up @@ -202,6 +206,7 @@ SharedDebugStringConvertibleList TextAttributes::getDebugProps() const {

// Font
debugStringConvertibleItem("fontFamily", fontFamily),
debugStringConvertibleItem("fontVariationSettings", fontVariationSettings),
debugStringConvertibleItem("fontSize", fontSize),
debugStringConvertibleItem("fontSizeMultiplier", fontSizeMultiplier),
debugStringConvertibleItem("fontWeight", fontWeight),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class TextAttributes : public DebugStringConvertible {

// Font
std::string fontFamily{""};
std::string fontVariationSettings{""};
Float fontSize{std::numeric_limits<Float>::quiet_NaN()};
Float fontSizeMultiplier{std::numeric_limits<Float>::quiet_NaN()};
std::optional<FontWeight> fontWeight{};
Expand Down Expand Up @@ -115,6 +116,7 @@ struct hash<facebook::react::TextAttributes> {
textAttributes.backgroundColor,
textAttributes.opacity,
textAttributes.fontFamily,
textAttributes.fontVariationSettings,
textAttributes.fontSize,
textAttributes.fontSizeMultiplier,
textAttributes.fontWeight,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -856,6 +856,7 @@ constexpr static MapBuffer::Key TA_KEY_LINE_BREAK_STRATEGY = 25;
constexpr static MapBuffer::Key TA_KEY_ROLE = 26;
constexpr static MapBuffer::Key TA_KEY_TEXT_TRANSFORM = 27;
constexpr static MapBuffer::Key TA_KEY_ALIGNMENT_VERTICAL = 28;
constexpr static MapBuffer::Key TA_KEY_FONT_VARIATION_SETTINGS = 29;

// constants for ParagraphAttributes serialization
constexpr static MapBuffer::Key PA_KEY_MAX_NUMBER_OF_LINES = 0;
Expand Down Expand Up @@ -929,6 +930,9 @@ inline MapBuffer toMapBuffer(const TextAttributes& textAttributes) {
if (!textAttributes.fontFamily.empty()) {
builder.putString(TA_KEY_FONT_FAMILY, textAttributes.fontFamily);
}
if (!textAttributes.fontVariationSettings.empty()) {
builder.putString(TA_KEY_FONT_VARIATION_SETTINGS, textAttributes.fontVariationSettings);
}
if (!std::isnan(textAttributes.fontSize)) {
builder.putDouble(TA_KEY_FONT_SIZE, textAttributes.fontSize);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ static TextAttributes convertRawProp(
"fontFamily",
sourceTextAttributes.fontFamily,
defaultTextAttributes.fontFamily);
textAttributes.fontVariationSettings = convertRawProp(
context,
rawProps,
"fontVariationSettings",
sourceTextAttributes.fontVariationSettings,
defaultTextAttributes.fontVariationSettings);
textAttributes.fontSize = convertRawProp(
context,
rawProps,
Expand Down Expand Up @@ -244,6 +250,8 @@ void BaseTextProps::setProp(
defaults, value, textAttributes, foregroundColor, "color");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, fontFamily, "fontFamily");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, fontVariationSettings, "fontVariationSettings");
REBUILD_FIELD_SWITCH_CASE(
defaults, value, textAttributes, fontSize, "fontSize");
REBUILD_FIELD_SWITCH_CASE(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ AndroidTextInputProps::AndroidTextInputProps(
convertRawProp(context, rawProps, "fontWeight", sourceProps.fontWeight, {})),
fontFamily(CoreFeatures::enablePropIteratorSetter? sourceProps.fontFamily :
convertRawProp(context, rawProps, "fontFamily", sourceProps.fontFamily, {})),
fontVariationSettings(CoreFeatures::enablePropIteratorSetter? sourceProps.fontVariationSettings :
convertRawProp(context, rawProps, "fontVariationSettings", sourceProps.fontVariationSettings, {})),
// See AndroidTextInputComponentDescriptor for usage
// TODO T63008435: can these, and this feature, be removed entirely?
hasPadding(CoreFeatures::enablePropIteratorSetter? sourceProps.hasPadding : hasValue(rawProps, sourceProps.hasPadding, "padding")),
Expand Down Expand Up @@ -246,6 +248,7 @@ void AndroidTextInputProps::setProp(
RAW_SET_PROP_SWITCH_CASE_BASIC(includeFontPadding);
RAW_SET_PROP_SWITCH_CASE_BASIC(fontWeight);
RAW_SET_PROP_SWITCH_CASE_BASIC(fontFamily);
RAW_SET_PROP_SWITCH_CASE_BASIC(fontVariationSettings);

case CONSTEXPR_RAW_PROPS_KEY_HASH("value"): {
fromRawValue(context, value, this->value, {});
Expand Down Expand Up @@ -343,6 +346,7 @@ folly::dynamic AndroidTextInputProps::getDynamic() const {
props["includeFontPadding"] = includeFontPadding;
props["fontWeight"] = fontWeight;
props["fontFamily"] = fontFamily;
props["fontVariationSettings"] = fontVariationSettings;
props["cursorColor"] = toAndroidRepr(cursorColor);
props["mostRecentEventCount"] = mostRecentEventCount;
props["text"] = text;
Expand Down
Loading
Loading