Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Fix single-line TextInput text/caret sitting low and bottom-clipped at >100% display scale (GetContentSize DPI normalization)",
"packageName": "react-native-windows",
"email": "collindanielschneide@gmail.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -1314,13 +1314,35 @@ std::pair<float, float> WindowsTextInputComponentView::GetContentSize() const no

// Use the layout width as the constraint (always multiline)
float availableWidth = m_layoutMetrics.frame.size.width;
float scale = m_layoutMetrics.pointScaleFactor;
float dpi = m_layoutMetrics.pointScaleFactor * GetDpiForSystem();

// TxGetNaturalSize measures in the *device pixels of `hdc`*. GetDC(nullptr)
// returns a screen DC whose logical DPI is the system DPI - typically 96 - and
// is unrelated to the per-monitor display scale carried in pointScaleFactor.
// The previous code conflated the two (dpi = pointScaleFactor *
// GetDpiForSystem()) and then divided the returned natural size by
// pointScaleFactor, so at any display scale > 100% the measured content height
// came back short by exactly that scale and calculateContentVerticalOffset()
// over-centered the text (parked low / bottom-clipped). Normalize by the
// screen DC's real DPI instead. DIPs are 1/96in by definition, so the
// DIP<->HIMETRIC leg always uses 96.
//
// GetDpiForSystem() (user32) reports exactly what GetDeviceCaps(hdc,
// LOGPIXELSX/Y) reports for a GetDC(nullptr) screen DC - both return the
// system DPI for the caller's DPI-awareness context - so this avoids taking a
// Gdi32 dependency for the measurement. RNW already calls GetDpiForSystem
// elsewhere in this file. Windows reports square logical DPI for the screen
// (LOGPIXELSX == LOGPIXELSY), so one value covers both axes.
const UINT hdcDpi = GetDpiForSystem();
constexpr float HIMETRIC_PER_INCH = 2540.0f;
constexpr float DIPS_PER_INCH = 96.0f;

// Height is deliberately unconstrained. Do NOT scale LONG_MAX into HIMETRIC:
// LONG_MAX * 2540 / 96 is ~5.7e10, far outside LONG, and converting an
// out-of-range floating-point value back to an integer type is undefined
// behavior. LONG_MAX already means "unbounded" to RichEdit.
SIZE extentHimetric = {
static_cast<LONG>(availableWidth * scale * HIMETRIC_PER_INCH / dpi),
static_cast<LONG>(std::numeric_limits<LONG>::max() * HIMETRIC_PER_INCH / dpi)};
static_cast<LONG>(availableWidth * HIMETRIC_PER_INCH / DIPS_PER_INCH),
std::numeric_limits<LONG>::max()};

SIZE naturalSize = {0, 0};

Expand All @@ -1340,8 +1362,10 @@ std::pair<float, float> WindowsTextInputComponentView::GetContentSize() const no
return {0.0f, 0.0f};
}

float contentWidth = static_cast<float>(naturalSize.cx) / scale;
float contentHeight = static_cast<float>(naturalSize.cy) / scale;
// naturalSize is in the DC's device pixels; convert device px -> DIPs using the
// DC's actual DPI (px * 96 / hdcDpi), not pointScaleFactor.
float contentWidth = static_cast<float>(naturalSize.cx) * DIPS_PER_INCH / hdcDpi;
float contentHeight = static_cast<float>(naturalSize.cy) * DIPS_PER_INCH / hdcDpi;

return {contentWidth, contentHeight};
}
Expand Down
Loading