Minimize EditText Spans 8/9: CustomStyleSpan (#36577)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/36577

This is part of a series of changes to minimize the number of spans committed to EditText, as a mitigation for platform issues on Samsung devices. See this [GitHub thread]( https://github.com/facebook/react-native/issues/35936#issuecomment-1411437789) for greater context on the platform behavior.

This change allows us to strip CustomStyleSpan. We already set all but `fontVariant` on the underlying EditText, so we just need to route that through as well.

Note that because this span is non-parcelable, it is seemingly not subject to the buggy behavior on Samsung devices of infinitely cloning the spans, but non-parcelable spans have different issues on the devices (they disappear), so moving `fontVariant` to the top-level makes sense here.

Changelog:
[Android][Fixed] - Minimize EditText Spans 8/N: CustomStyleSpan

Reviewed By: javache

Differential Revision: D44297384

fbshipit-source-id: ed4c000e961dd456a2a8f4397e27c23a87defb6e
This commit is contained in:
Nick Gerleman
2023-03-24 12:31:22 -07:00
committed by Facebook GitHub Bot
parent 104cb7f81e
commit b384bb613b
3 changed files with 59 additions and 0 deletions
@@ -71,6 +71,10 @@ public class CustomStyleSpan extends MetricAffectingSpan implements ReactSpan {
return mFontFamily;
}
public @Nullable String getFontFeatureSettings() {
return mFeatureSettings;
}
private static void apply(
Paint paint,
int style,
@@ -65,6 +65,7 @@ import com.facebook.react.views.text.TextLayoutManager;
import com.facebook.react.views.view.ReactViewBackgroundManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* A wrapper around the EditText that lets us better control what happens when an EditText gets
@@ -518,6 +519,14 @@ public class ReactEditText extends AppCompatEditText
}
}
@Override
public void setFontFeatureSettings(String fontFeatureSettings) {
if (!Objects.equals(fontFeatureSettings, getFontFeatureSettings())) {
super.setFontFeatureSettings(fontFeatureSettings);
mTypefaceDirty = true;
}
}
public void maybeUpdateTypeface() {
if (!mTypefaceDirty) {
return;
@@ -529,6 +538,17 @@ public class ReactEditText extends AppCompatEditText
ReactTypefaceUtils.applyStyles(
getTypeface(), mFontStyle, mFontWeight, mFontFamily, getContext().getAssets());
setTypeface(newTypeface);
// Match behavior of CustomStyleSpan and enable SUBPIXEL_TEXT_FLAG when setting anything
// nonstandard
if (mFontStyle != UNSET
|| mFontWeight != UNSET
|| mFontFamily != null
|| getFontFeatureSettings() != null) {
setPaintFlags(getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG);
} else {
setPaintFlags(getPaintFlags() & (~Paint.SUBPIXEL_TEXT_FLAG));
}
}
// VisibleForTesting from {@link TextInputEventsTestCase}.
@@ -738,6 +758,19 @@ public class ReactEditText extends AppCompatEditText
}
});
}
stripSpansOfKind(
sb,
CustomStyleSpan.class,
new SpanPredicate<CustomStyleSpan>() {
@Override
public boolean test(CustomStyleSpan span) {
return span.getStyle() == mFontStyle
&& Objects.equals(span.getFontFamily(), mFontFamily)
&& span.getWeight() == mFontWeight
&& Objects.equals(span.getFontFeatureSettings(), getFontFeatureSettings());
}
});
}
private <T> void stripSpansOfKind(
@@ -795,6 +828,22 @@ public class ReactEditText extends AppCompatEditText
spanFlags);
}
}
if (mFontStyle != UNSET
|| mFontWeight != UNSET
|| mFontFamily != null
|| getFontFeatureSettings() != null) {
workingText.setSpan(
new CustomStyleSpan(
mFontStyle,
mFontWeight,
getFontFeatureSettings(),
mFontFamily,
getContext().getAssets()),
0,
workingText.length(),
spanFlags);
}
}
private static boolean sameTextForSpan(
@@ -69,6 +69,7 @@ import com.facebook.react.views.text.DefaultStyleValuesUtil;
import com.facebook.react.views.text.ReactBaseTextShadowNode;
import com.facebook.react.views.text.ReactTextUpdate;
import com.facebook.react.views.text.ReactTextViewManagerCallback;
import com.facebook.react.views.text.ReactTypefaceUtils;
import com.facebook.react.views.text.TextAttributeProps;
import com.facebook.react.views.text.TextInlineImageSpan;
import com.facebook.react.views.text.TextLayoutManager;
@@ -413,6 +414,11 @@ public class ReactTextInputManager extends BaseViewManager<ReactEditText, Layout
view.setFontStyle(fontStyle);
}
@ReactProp(name = ViewProps.FONT_VARIANT)
public void setFontVariant(ReactEditText view, @Nullable ReadableArray fontVariant) {
view.setFontFeatureSettings(ReactTypefaceUtils.parseFontVariant(fontVariant));
}
@ReactProp(name = ViewProps.INCLUDE_FONT_PADDING, defaultBoolean = true)
public void setIncludeFontPadding(ReactEditText view, boolean includepad) {
view.setIncludeFontPadding(includepad);