fix(textinput): center single-line text correctly at >100% display scale#16302
fix(textinput): center single-line text correctly at >100% display scale#16302FaithfulAudio wants to merge 2 commits into
Conversation
WindowsTextInputComponentView::GetContentSize() measured RichEdit content with a DPI of `pointScaleFactor * GetDpiForSystem()` and then divided the returned natural size by `pointScaleFactor`. TxGetNaturalSize actually reports in the device pixels of the measuring DC (GetDC(nullptr) -> screen DC at the system DPI, typically 96), which is independent of the per-monitor display scale in pointScaleFactor. As a result content height was under-measured by exactly the display scale at any scale > 100%, and calculateContentVerticalOffset() over-centered the text so it parked low and was bottom-clipped. Normalize both conversions by the DC's real DPI (GetDeviceCaps LOGPIXELS): extent uses DIP<->HIMETRIC at the fixed 96 DIPs/inch, and device px are converted back to DIPs with px*96/hdcDpi. Link Gdi32 explicitly for GetDeviceCaps. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
|
@microsoft-github-policy-service agree [company="Facilitron"] |
|
@microsoft-github-policy-service agree company="Facilitron" |
|
Update — honest validation results from a patched framework source build, including a finding reviewers should weigh. We compiled this fix into Microsoft.ReactNative from source (binary-gated: patched dll confirmed as the loaded module) and re-ran the probe. On THIS machine the raw input rendered pixel-identical before/after — for an instructive reason: the machine is DPI-degenerate for this bug (system DPI is 240, equal to the 2.5 pointScaleFactor, so the old More significant: programmatic ink measurement shows the rendered offset equals |

Problem
On a single-line
<TextInput>at any display scale > 100% (125%, 250%, …), the text and caret sit too low in the control and are clipped at the bottom.Root cause
WindowsTextInputComponentView::GetContentSize()measures RichEdit content viaTxGetNaturalSize, which returns sizes in the device pixels of the measuring DC. The DC comes fromGetDC(nullptr)— a screen DC whose logical DPI (GetDeviceCaps(hdc, LOGPIXELS)) is the system DPI (typically 96), unrelated to the per-monitor scale inpointScaleFactor. The code conflated the two: it built the HIMETRIC extent withdpi = pointScaleFactor * GetDpiForSystem()and divided the returned natural size bypointScaleFactor. Net: measured content height is short by exactly the display scale, socalculateContentVerticalOffset()centers with an undersizedcontentHeightand pushes the text down and off the bottom.Fix
Normalize both conversions by the DC's real DPI: DIP↔HIMETRIC always uses 96 (DIPs are 1/96in by definition); device px → DIP uses
px * 96 / GetDeviceCaps(hdc, LOGPIXELS).pointScaleFactor/GetDpiForSystem()are no longer used in the measurement.Gdi32.libis linked explicitly (#pragma comment) —GetDeviceCapsis a Gdi32 export and Microsoft.ReactNative does not otherwise reference it (LNK2019 without it in a framework source build).Validation
Bug reproduced in a production RNW 0.83.2 new-arch app (Facilitron FIT — an Expo monorepo app shipping a full Windows target) on Windows 11 ARM64, Debug, physical panel at a true 250% display scale. A stock single-line TextInput (deep
Libraries/Components/TextInput/TextInputimport, 44-DIP box, fontSize 18) parks its text on the bottom border and clips, while the app-side workaround this patch obsoletes (bottom paddingfontSize*2.3*(1-1/scale)) centers the same input:This diff has been compiled into Microsoft.ReactNative from source on this machine (yarn-patch framework source build; the changed measurement verified present in the built binary — and the
Gdi32.libpragma requirement was found that way). Remaining for upstream: CI build + pixel-level after-shots at 100/125/150/250%.Caveats for reviewers: base commit
8e869d7is itself "[0.83] fix text input scaling (#16291)" — please confirm this refines (does not regress) that change. Authored against0.83-stableto match our production app; happy to re-cut ontomainif preferred. Separately noted (not addressed here):pointScaleFactorcan go stale across monitor moves (no WM_DPICHANGED refresh).Microsoft Reviewers: Open in CodeFlow