跪拜 Guibai
← Back to the summary

Font Metric Mismatch Breaks Vertical Centering in Flutter on iOS and Android

Title: Why text is never centered! Documenting a bug Author: Jewel105 Tags: Flutter, Android, iOS

The design recently switched to a new font package. After using it, vertical centering is never right; changing line height works until the language changes. iOS is fixed, Android still isn't.

1. Problem

1. iOS centered, Android not centered

Cause: the font's vertical metrics are inconsistent. The English font's hhea and OS/2 typo metric sets allocate upper and lower space differently. Different platforms use different metric sets, so the same layout produces different baselines and whitespace, causing visual offset.

Platform Metric set Original: ascent / descent / line gap After fix
iOS hhea 1050 / -274 / 0 1029 / -295 / 0
Android OS/2 typo 750 / -250 / 324 1029 / -295 / 0

What are font vertical metrics?

Font vertical metrics determine the vertical layout space of text, mainly including three indicators:

image.png

2. Chinese centered, English not centered

Cause: the font package's English, Chinese, and Japanese fonts have different natural line heights, so a fixed line-height multiplier cannot work across languages.

2. Solutions

1. Unify English font metrics

Unify the ascent, descent, and line gap of hhea and OS/2 typo, and calibrate the baseline; preserve glyphs, widths, and total line height.

2. Use Auto as the global theme default, adapting to different languages

In the global theme, set height: kTextHeightNone by default so each language uses its font's natural line height. Its value 0 means native line height, not zero height.

Comments

Top 1 from juejin.cn, machine-translated. The original thread is authoritative.

booboosui

Adding two metrics that aren't in your table: besides sTypo, OS/2 also has usWinAscent/usWinDescent as a third set, and when bit 7 of fsSelection (USE_TYPO_METRICS) is not set, the spec calls for exactly usWin, not sTypo. Aligning hhea and sTypo without setting that bit will still cause drift when you switch to another layout engine (in fonttools, it's font["OS/2"].fsSelection |= 1<<7). Another number: all three of your groups sum to 1324 (750+250+324, 1050+274+0, 1029+295+0). That means the line height never changed; the issue is purely the baseline's position within the line box. The fix essentially moves Android's baseline from 750 to 1029 — a downward shift of 0.279em relative to the original Android, still 21 units short of iOS's 1050. So this move isn't 'centering', it's 'pushing the text down' — places that were previously aligned via padding will now be off by 0.279em downward. To completely avoid the font's own metrics, setting a global Auto height isn't enough: when falling back to system fonts, their metrics are yet another set. In Flutter, the targeted solution is strut; with forceStrutHeight set to true, the line box only respects (height+leading)×fontSize, regardless of which font is rendering.