Skip to content
Draft
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 @@ -55,6 +55,12 @@ std::string BstrToStdString(BSTR bstr, int cp = CP_UTF8) {
return str;
}

// GetDeviceCaps (used in GetContentSize to normalize RichEdit's device-pixel
// measurements to DIPs) is exported from Gdi32. Microsoft.ReactNative does not
// otherwise reference Gdi32, so link it explicitly to avoid an unresolved
// external symbol (LNK2019) in builds that don't already pull it in transitively.
#pragma comment(lib, "Gdi32.lib")

MSO_CLASS_GUID(ITextHost, "13E670F4-1A5A-11cf-ABEB-00AA00B65EA1") // IID_ITextHost
MSO_CLASS_GUID(ITextServices, "8D33F740-CF58-11CE-A89D-00AA006CADC5") // IID_ITextServices
MSO_CLASS_GUID(ITextServices2, "8D33F741-CF58-11CE-A89D-00AA006CADC5") // IID_ITextServices2
Expand Down Expand Up @@ -1314,13 +1320,25 @@ 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 (GetDeviceCaps LOGPIXELS) 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 both conversions by the DC's real DPI instead. DIPs
// are 1/96in by definition, so the DIP<->HIMETRIC leg always uses 96.
const int hdcDpiX = GetDeviceCaps(hdc, LOGPIXELSX);
const int hdcDpiY = GetDeviceCaps(hdc, LOGPIXELSY);
constexpr float HIMETRIC_PER_INCH = 2540.0f;
constexpr float DIPS_PER_INCH = 96.0f;

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),
static_cast<LONG>(std::numeric_limits<LONG>::max() * HIMETRIC_PER_INCH / DIPS_PER_INCH)};

SIZE naturalSize = {0, 0};

Expand All @@ -1340,8 +1358,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 / hdcDpiX;
float contentHeight = static_cast<float>(naturalSize.cy) * DIPS_PER_INCH / hdcDpiY;

return {contentWidth, contentHeight};
}
Expand Down