Hacktoberfest 2026:維護者為十月標記出來的 issue,仍然開放、適合新手。 瀏覽 Hacktoberfest issue

[Android] `maxFontSizeMultiplier` doesn't cap line height on Android (Fabric) — height keeps growing with device font scale even when JS pre-caps the value

未關閉
#58,124 0 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

維護者通常 1 天內回覆

還沒有人認領這個 Issue。

評估

難度
4/5
預估耗時
3-5 天
新手友好度
55/100
Issue 類型
缺陷
描述清晰度
基本清楚
活躍度
活躍
技術堆疊
android, react-native
領域
mobile-dev

研究方向

從連結的重現程式碼 src/App.tsx 開始,比較裝置字型縮放受限和不受限時的 onLayout 高度。追蹤 Android Fabric 文字配置路徑中對 line-height 的縮放處理,並使用回報的 maxFontSizeMultiplier 和明確 lineHeight 情況作為回歸情境。完成標準是:當裝置字型縮放超過 1.5 時,渲染高度仍保持受限,並且有一個涵蓋這兩種情況的回歸測試。

由索引模型根據 Issue 內容生成。

描述

Needs: Triage :mag: Platform: Android Type: New Architecture

Description

On Android with the New Architecture (Fabric) enabled, maxFontSizeMultiplier does not fully cap rendered text size. Even when an explicit, already-capped lineHeight value is passed from JS (a fixed number that does not depend on the device's actual font scale), the rendered layout height still changes depending on the device's real system font scale setting — as if the native layer re-applies the raw (uncapped) Configuration.fontScale on top of the value React Native was given.

fontSize itself appears to be capped correctly (min(fontScale, maxFontSizeMultiplier), matching TextAttributeProps.getEffectiveFontSize() / PixelUtil.toPixelFromSP()), but the line height / leading used for layout does not seem to go through the same capped calculation, and continues to track the device's raw font scale.

Expected Behavior

With maxFontSizeMultiplier set to a fixed value, the effective scaling multiplier applied to a <Text> (including its line height / layout height) should never exceed that value, regardless of how much larger the device's actual system font scale is — matching the documented behavior ("Specifies the largest possible scale a font can reach when allowFontScaling is enabled").

Actual Behavior

Rendered text height keeps growing with the device's real font scale even when maxFontSizeMultiplier is fixed and even when an already-capped lineHeight is explicitly supplied from JS. Only fully disabling allowFontScaling and computing sizes manually produces a stable, correctly-capped result.

Steps to reproduce

1. Minimal repro — fixed maxFontSizeMultiplier, size still varies by device font scale

import { PixelRatio, Text, View } from 'react-native';

export default function FontScaleTest() {
  const fontScale = PixelRatio.getFontScale();
  return (
    <View style={{ flex: 1, justifyContent: 'center' }}>
      <Text
        allowFontScaling={true}
        maxFontSizeMultiplier={1.5}
        style={{ fontSize: 20, backgroundColor: 'yellow' }}
        onLayout={e =>
          console.log(
            'fontScale=',
            fontScale,
            'height=',
            e.nativeEvent.layout.height,
          )
        }
      >
        가나다
      </Text>
    </View>
  );
}

Set the device's system font size so that PixelRatio.getFontScale() reports 1.5, force-quit and cold-start the app, and note the logged height. Then set the device font size so getFontScale() reports 2.0, force-quit and cold-start again, and compare.

Expected: identical height in both cases, since maxFontSizeMultiplier={1.5} should cap the effective multiplier to 1.5 in both (min(1.5, 1.5) = 1.5 and min(2.0, 1.5) = 1.5).

Actual:

Device fontScale height
1.5 34.844444
2.0 39.822224

A ~14.3% difference, reproduced consistently across cold starts.

2. Isolating the cause — explicit pre-capped lineHeight still leaks

To rule out "the automatic default line-height calculation ignores the cap," we passed an explicit lineHeight computed in JS so it is numerically identical regardless of the device's font scale (since maxFontSizeMultiplier should cap both cases to the same 1.5):

<Text
  allowFontScaling
  maxFontSizeMultiplier={1.5}
  numberOfLines={1}
  style={{
    fontSize: 20,
    lineHeight: 20 * 1.5 * 1.2 /* = 36, same value in both cases */,
  }}
  onLayout={e =>
    console.log('fontScale=', fontScale, 'height=', e.nativeEvent.layout.height)
  }
>
  Aa가나다
</Text>
fontScale=1.5 fontScale=2.0 delta
Auto lineHeight 34.844444 39.822224 +14.3%
Explicit fixed lineHeight (36 in both cases) 36.266666 43.377773 +19.6%

Even though the exact same JS-computed number (36) was passed to lineHeight in both cases, the rendered height still differs by ~19.6% depending on the device's real font scale. This rules out "default line-height calculation doesn't check the cap" as the sole explanation — something in the native layer appears to reintroduce the raw, uncapped fontScale/scaledDensity even when the JS side has already fully accounted for the cap.

3. Confirmed workaround — disabling allowFontScaling entirely and computing everything manually removes the discrepancy

const cappedScale = Math.min(fontScale, 1.5);
const fontSize = 20 * cappedScale;     // 30 in both cases
const lineHeight = fontSize * 1.1615;  // 34.845 in both cases

<Text allowFontScaling={false} numberOfLines={1} style={{ fontSize, lineHeight }} onLayout={...}>
  Aa가나다
</Text>
fontScale=1.5 fontScale=2.0
height 35.200001 35.200001

Identical. This strongly suggests the leak only happens through the allowFontScaling={true} / SP-based native scaling path, and is avoided entirely by using allowFontScaling={false} with manual DIP-based sizing.

Additional Context

  • We initially suspected this could be the previously-fixed issue in #47499 (Fabric ignoring maxFontSizeMultiplier entirely), fixed by #47614 and shipped in 0.78 — but that fix predates both our original 0.83.1 install and the 0.87.0 re-verification below, and the magnitude of the discrepancy (~14–20%) doesn't match "completely ignored" (which would produce a difference closer to the raw fontScale ratio, 2.0 / 1.5 ≈ 33%).

  • We also ruled out the New Architecture "layout not recalculated after returning from background without a full restart" issue (#51768) by reproducing with a full cold start (force-quit + relaunch) between each font-scale change.

  • The pattern (fontSize appears correctly capped; line height / layout height does not) points at a discrepancy between the code path that sizes glyphs and the code path that determines line height/leading for layout purposes, but we were not able to pin down the exact native function responsible.

  • Minimal repro repository (local RN CLI project, [email protected], New Architecture enabled, matches the environment above exactly): https://github.com/silverj0805/RNFontScaleRepro. src/App.tsx bundles all three cases above into one screen with tab switching and on-screen PixelRatio.getFontScale() / onLayout height readouts (see the screenshots above), so results can be checked without a console attached.

  • This bug was originally reproduced and reported against [email protected]. After the first version of this issue was flagged as targeting an unsupported version, the repro project was upgraded end-to-end to 0.87.0 (the latest stable release at time of writing) and re-verified on an Android emulator (Pixel 8 Pro, API 35, font_scale set via adb shell settings put system font_scale <value>, cold-started between changes) — the bug reproduces identically on 0.87.0, with onLayout height values matching to the sub-pixel:

    Test fontScale=1.5 fontScale=2.0
    1 (auto lineHeight) 35.000000 40.333344
    2 (explicit fixed lineHeight=36) 36.000000 43.333313
    3 (workaround: allowFontScaling=false + manual math) 35.000000 35.000000

    Same shape as the physical-device numbers: Test 1 and 2 keep growing with the device's raw fontScale past the 1.5 cap, Test 3 stays exactly fixed — on a stock Pixel emulator, independent of the Galaxy S21/One UI hardware above, so this isn't Samsung-specific either. (These emulator numbers were measured with ABC as the sample text rather than the Hangul text shown in the physical-device numbers and code snippets above — the pattern is identical either way, since the leak is in the line-height calculation, not the glyphs.)

React Native Version

0.87.0

Affected Platforms

Runtime - Android

Areas

Fabric - The New Renderer

Output of npx @react-native-community/cli info

System:
  OS: macOS 26.4.1
  CPU: (8) arm64 Apple M1 Pro
  Memory: 192.83 MB / 16.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 22.17.0
    path: /opt/homebrew/opt/node@22/bin/node
  Yarn:
    version: 3.6.4
    path: /opt/homebrew/opt/node@22/bin/yarn
  npm:
    version: 10.9.2
    path: /opt/homebrew/opt/node@22/bin/npm
  Watchman:
    version: 2025.06.30.00
    path: /opt/homebrew/bin/watchman
Managers:
  CocoaPods:
    version: 1.15.2
    path: /Users/choieunjeong/.rbenv/shims/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 25.4
      - iOS 26.4
      - macOS 26.4
      - tvOS 26.4
      - visionOS 26.4
      - watchOS 26.4
  Android SDK:
    API Levels:
      - "31"
      - "33"
      - "34"
      - "35"
      - "36"
      - "37"
    Build Tools:
      - 30.0.2
      - 30.0.3
      - 33.0.0
      - 33.0.1
      - 34.0.0
      - 35.0.0
      - 36.0.0
      - 37.0.0
    System Images:
      - android-35 | Google Play ARM 64 v8a
      - android-36.1 | Google APIs ARM 64 v8a
      - android-36 | Google APIs ARM 64 v8a
      - android-36 | Google Play ARM 64 v8a
    Android NDK: Not Found
IDEs:
  Android Studio: 2025.2 AI-252.28238.7.2523.14688667
  Xcode:
    version: 26.4/17E192
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.11
    path: /usr/bin/javac
  Ruby:
    version: 2.7.6
    path: /Users/choieunjeong/.rbenv/shims/ruby
npmPackages:
  "@react-native-community/cli":
    installed: 20.2.0
    wanted: 20.2.0
  react:
    installed: 19.2.3
    wanted: 19.2.3
  react-native:
    installed: 0.87.0
    wanted: 0.87.0
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: true
iOS:
  hermesEnabled: true
  newArchEnabled: true

Stacktrace or Logs

Not applicable — this is not a crash and produces no error, warning, or stack trace. The bug is a numeric layout discrepancy, demonstrated instead by the `onLayout` height measurements and screenshots above (same JS input, different rendered `height` depending on the device's raw font scale).

MANDATORY Reproducer

https://github.com/silverj0805/RNFontScaleRepro

Screenshots and Videos

From the linked reproducer, same maxFontSizeMultiplier={1.5} cap in every screenshot, only the device's actual font scale differs (1.5 vs 2.0):

The problem — case 1 (automatic lineHeight):

Device fontScale = 1.5 Device fontScale = 2.0
Case 1 at fontScale 1.5 Case 1 at fontScale 2.0

The problem — case 2 (explicit, pre-capped lineHeight):

Device fontScale = 1.5 Device fontScale = 2.0
Case 2 at fontScale 1.5 Case 2 at fontScale 2.0

The workaround — case 3 (allowFontScaling={false} + manual math), for comparison:

Device fontScale = 1.5 Device fontScale = 2.0
Case 3 at fontScale 1.5 Case 3 at fontScale 2.0
主要語言
C++
星號
127k
分支
25.3k
PR 合併指標
30 天內沒有已合併 PR

環境準備

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

react/react-native 的其他 Issue

查看 react/react-native 的全部 Issue

相似的 Issue

更多 C++ Issue

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。