react-native icon indicating copy to clipboard operation
react-native copied to clipboard

[Android] Fix ReactEditText Typeface Inconsistencies for Hint and Typing with Custom Fonts

Open lorenc-tomasz opened this issue 10 months ago • 6 comments

Summary:

This PR ensures that the typeface applied to ReactEditText is also correctly applied to its hint text and fixes an issue where the custom font was not consistently applied when typing.

Fixes: https://github.com/facebook/react-native/issues/50137

Issues Fixed:

  • Hint text typeface inconsistency: Previously, the hint text did not inherit the custom typeface, leading to visual inconsistencies. A new MetricAffectingSpan is introduced to apply the correct typeface to the hint.
  • Font not applied correctly when typing: In some cases, especially on devices where the system font is changed (e.g., Samsung devices allowing users to set a custom system font), the typeface would not be applied correctly when entering text. This is fixed by forcing the typeface to be set in onDraw.

Changelog:

[ANDROID] [FIXED] - Ensure ReactEditText hint text inherits the correct typeface and fix an issue where custom fonts were not applied correctly when typing.

Test Plan:

  • Set a custom font on ReactEditText and verify that the hint text also adopts the correct typeface.
  • Type using a custom font and ensure the typeface remains consistent.
  • Test on Samsung devices where the system font can be changed, ensuring that ReactEditText correctly maintains the specified font instead of falling back to the system default.
  • Verify behavior on various Android versions and devices.

lorenc-tomasz avatar Apr 02 '25 15:04 lorenc-tomasz

Hi @lorenc-tomasz!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at [email protected]. Thanks!

facebook-github-bot avatar Apr 02 '25 15:04 facebook-github-bot

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!

facebook-github-bot avatar Apr 02 '25 16:04 facebook-github-bot

We also measure EditText content using TextLayoutManager, against the Spannable created from text content, which ends up setting the final dimensions of the EditText. If we measure a Spannable that is inconsistent with the version we apply to the EditText, we could truncate content.

I think we would need to make sure that the Spannable being measured, before hitting the EditText, has consistent Typeface. Where is hint we are setting coming from, and how does it get measured by TextLayoutManager in normal path?

NickGerleman avatar Apr 02 '25 22:04 NickGerleman

@NickGerleman Thanks for feedback :) Still learning how it works there :) I have found simpler fix for the issue.

lorenc-tomasz avatar Apr 03 '25 06:04 lorenc-tomasz

Bump the PR

lorenc-tomasz avatar Apr 16 '25 08:04 lorenc-tomasz

@Hardanish-Singh @NickGerleman @cortinico Updated to Koltin.

lorenc-tomasz avatar May 26 '25 05:05 lorenc-tomasz

Updated with recent main.

lorenc-tomasz avatar Jun 10 '25 06:06 lorenc-tomasz

Updated with recent main branch.

lorenc-tomasz avatar Jun 20 '25 18:06 lorenc-tomasz

Updated with recent main branch

lorenc-tomasz avatar Jun 22 '25 19:06 lorenc-tomasz

Hi @Hardanish-Singh :)

could you please look into this PR again, please? I have updated the PR to use Kotlin and to up to date with recent main branch.

Kind regards, Tomasz

lorenc-tomasz avatar Jun 22 '25 19:06 lorenc-tomasz

We also measure EditText content using TextLayoutManager, against the Spannable created from text content, which ends up setting the final dimensions of the EditText. If we measure a Spannable that is inconsistent with the version we apply to the EditText, we could truncate content.

I think we would need to make sure that the Spannable being measured, before hitting the EditText, has consistent Typeface. Where is hint we are setting coming from, and how does it get measured by TextLayoutManager in normal path?

I took a look back at this, and as of recent, we do have code, so that the placeholder we use for measurement, inherits the base attributes set https://github.com/facebook/react-native/blob/main/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputShadowNode.cpp#L225

NickGerleman avatar Jun 24 '25 20:06 NickGerleman

Is this only for when the user customizes the system font?

The thing I mentioned above, I don't think that when we measure the text, on the Fabric side (see https://github.com/facebook/react-native/blob/e3047db0dc9caf06075554cc7abc0beb4f361aba/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.kt#L464 ) we ever consider this as being changed.

I am wondering, if TextInput may read some attribute to apply a font, that isn't considered during measurement?

When we forcibly update the typeface, is it overriding this default font? Or, does the logic for creating typeface resolve it, using the context?

NickGerleman avatar Jun 24 '25 20:06 NickGerleman

@NickGerleman It seems that fix stopped working with 0.80.0. I was trying to find more durable and proper fix in C++ part but without luck.

You can try to reproduce the issue: https://github.com/lorenc-tomasz/react-native-text-input-placeholder-font-issue-reproducer/tree/android-custom-font-issue-0.80.0

IMG_3404

I close this PR unless I will be able to find other fix. At the moment I have two working workaround with 0.80.0. One is to set autoCapitalize={'none'}. It seems it forces placeholder to rerender.

And second one using setNativeProps e.g.

  const setRef = useCallback(
    (node: TextInput | null) => {
      if (node) {
        inputRef.current = node;

        if (typeof ref === 'function') {
          ref(node);
        } else if (ref) {
          const mutableRef = ref as React.MutableRefObject<TextInput | null>;
          mutableRef.current = node;
        }

        // Workaround for Android to apply style before setting placeholder and before setting the styles by component
        // to apply font family correctly
        // TODO: Remove this workaround when RN fixes it: https://github.com/facebook/react-native/issues/50137
        if (isAndroid) {
          const flattenedStyle = StyleSheet.flatten([Typography.FONT_REGULAR, props.style ?? {}]);
          const onlyFontStyle = Object.fromEntries(
            Object.entries(flattenedStyle).filter(([key]) => fontStyleProps.includes(key)),
          );
          node.setNativeProps({
            style: onlyFontStyle,
          });
        }
      }
    },
    [ref],
  );

Both workarounds available in above repo. Maybe you will figure it out.

lorenc-tomasz avatar Jun 25 '25 10:06 lorenc-tomasz