diff --git a/change/react-native-windows-6ffd41f8-bf53-4629-9917-3c2349d77112.json b/change/react-native-windows-6ffd41f8-bf53-4629-9917-3c2349d77112.json new file mode 100644 index 00000000000..05c0ddb1066 --- /dev/null +++ b/change/react-native-windows-6ffd41f8-bf53-4629-9917-3c2349d77112.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Partial implementation of TextLayoutManager::measureLines", + "packageName": "react-native-windows", + "email": "30809111+acoates-ms@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/textlayoutmanager/TextLayoutManager.cpp b/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/textlayoutmanager/TextLayoutManager.cpp index 051ee1addaa..4c8328699e0 100644 --- a/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/textlayoutmanager/TextLayoutManager.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/textlayoutmanager/TextLayoutManager.cpp @@ -17,14 +17,11 @@ namespace facebook::react { void TextLayoutManager::GetTextLayout( - AttributedStringBox attributedStringBox, - ParagraphAttributes paragraphAttributes, - LayoutConstraints layoutConstraints, + const AttributedString &attributedString, + const ParagraphAttributes ¶graphAttributes, + Size size, winrt::com_ptr &spTextLayout) noexcept { - if (attributedStringBox.getValue().isEmpty()) - return; - - auto fragments = attributedStringBox.getValue().getFragments(); + auto fragments = attributedString.getFragments(); auto outerFragment = fragments[0]; DWRITE_FONT_STYLE style = DWRITE_FONT_STYLE_NORMAL; @@ -86,14 +83,14 @@ void TextLayoutManager::GetTextLayout( } winrt::check_hresult(spTextFormat->SetTextAlignment(alignment)); - auto str = GetTransformedText(attributedStringBox); + auto str = GetTransformedText(attributedString); winrt::check_hresult(Microsoft::ReactNative::DWriteFactory()->CreateTextLayout( str.c_str(), // The string to be laid out and formatted. static_cast(str.size()), // The length of the string. spTextFormat.get(), // The text format to apply to the string (contains font information, etc). - layoutConstraints.maximumSize.width, // The width of the layout box. - layoutConstraints.maximumSize.height, // The height of the layout box. + size.width, // The width of the layout box. + size.height, // The height of the layout box. spTextLayout.put() // The IDWriteTextLayout interface pointer. )); @@ -129,9 +126,20 @@ void TextLayoutManager::GetTextLayout( } } +void TextLayoutManager::GetTextLayout( + const AttributedStringBox &attributedStringBox, + const ParagraphAttributes ¶graphAttributes, + LayoutConstraints layoutConstraints, + winrt::com_ptr &spTextLayout) noexcept { + if (attributedStringBox.getValue().isEmpty()) + return; + + GetTextLayout(attributedStringBox.getValue(), paragraphAttributes, layoutConstraints.maximumSize, spTextLayout); +} + TextMeasurement TextLayoutManager::measure( - AttributedStringBox attributedStringBox, - ParagraphAttributes paragraphAttributes, + const AttributedStringBox &attributedStringBox, + const ParagraphAttributes ¶graphAttributes, const TextLayoutContext &layoutContext, LayoutConstraints layoutConstraints, std::shared_ptr /* hostTextStorage */) const { @@ -185,31 +193,12 @@ TextMeasurement TextLayoutManager::measure( */ TextMeasurement TextLayoutManager::measureCachedSpannableById( int64_t cacheId, - ParagraphAttributes const ¶graphAttributes, + const ParagraphAttributes ¶graphAttributes, LayoutConstraints layoutConstraints) const { assert(false); return {}; } -LinesMeasurements TextLayoutManager::measureLines( - AttributedString attributedString, - ParagraphAttributes paragraphAttributes, - Size size) const { - assert(false); - return {}; -} - -std::shared_ptr TextLayoutManager::getHostTextStorage( - AttributedString attributedString, - ParagraphAttributes paragraphAttributes, - LayoutConstraints layoutConstraints) const { - return nullptr; -} - -void *TextLayoutManager::getNativeTextLayoutManager() const { - return (void *)this; -} - Microsoft::ReactNative::TextTransform ConvertTextTransform(std::optional const &transform) { if (transform) { switch (transform.value()) { @@ -229,9 +218,92 @@ Microsoft::ReactNative::TextTransform ConvertTextTransform(std::optional spTextLayout; + + GetTextLayout(attributedString, paragraphAttributes, size, spTextLayout); + + if (spTextLayout) { + std::vector lineMetrics; + uint32_t actualLineCount; + spTextLayout->GetLineMetrics(nullptr, 0, &actualLineCount); + lineMetrics.resize(static_cast(actualLineCount)); + winrt::check_hresult(spTextLayout->GetLineMetrics(lineMetrics.data(), actualLineCount, &actualLineCount)); + uint32_t startRange = 0; + const auto count = (paragraphAttributes.maximumNumberOfLines > 0) + ? std::min(static_cast(paragraphAttributes.maximumNumberOfLines), actualLineCount) + : actualLineCount; + for (uint32_t i = 0; i < count; ++i) { + UINT32 actualHitTestCount = 0; + spTextLayout->HitTestTextRange( + startRange, + lineMetrics[i].length, + 0, // x + 0, // y + NULL, + 0, // metrics count + &actualHitTestCount); + + // Allocate enough room to return all hit-test metrics. + std::vector hitTestMetrics(actualHitTestCount); + spTextLayout->HitTestTextRange( + startRange, + lineMetrics[i].length, + 0, // x + 0, // y + &hitTestMetrics[0], + static_cast(hitTestMetrics.size()), + &actualHitTestCount); + + float width = 0; + for (auto tm : hitTestMetrics) { + width += tm.width; + } + + std::string str; + for (const auto &fragment : attributedString.getFragments()) { + str = str + + winrt::to_string(Microsoft::ReactNative::TransformableText::TransformText( + winrt::hstring{Microsoft::Common::Unicode::Utf8ToUtf16(fragment.string)}, + ConvertTextTransform(fragment.textAttributes.textTransform))); + } + + lineMeasurements.emplace_back(LineMeasurement( + str.substr(startRange, lineMetrics[i].length), + {{hitTestMetrics[0].left, hitTestMetrics[0].top}, // origin + {width, lineMetrics[i].height}}, + 0.0f, // TODO descender + 0.0f, // TODO: capHeight + 0.0f, // TODO ascender + 0.0f // TODO: xHeight + )); + + startRange += lineMetrics[i].length; + } + } + + return lineMeasurements; +} + +std::shared_ptr TextLayoutManager::getHostTextStorage( + const AttributedString &attributedString, + const ParagraphAttributes ¶graphAttributes, + LayoutConstraints layoutConstraints) const { + return nullptr; +} + +void *TextLayoutManager::getNativeTextLayoutManager() const { + return (void *)this; +} + +winrt::hstring TextLayoutManager::GetTransformedText(const AttributedString &attributedString) { winrt::hstring result{}; - for (const auto &fragment : attributedStringBox.getValue().getFragments()) { + for (const auto &fragment : attributedString.getFragments()) { result = result + Microsoft::ReactNative::TransformableText::TransformText( winrt::hstring{Microsoft::Common::Unicode::Utf8ToUtf16(fragment.string)}, diff --git a/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/textlayoutmanager/TextLayoutManager.h b/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/textlayoutmanager/TextLayoutManager.h index f89c8308baa..0e3d11b73da 100644 --- a/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/textlayoutmanager/TextLayoutManager.h +++ b/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/textlayoutmanager/TextLayoutManager.h @@ -35,8 +35,8 @@ class TextLayoutManager { * Measures `attributedStringBox` using native text rendering infrastructure. */ TextMeasurement measure( - AttributedStringBox attributedStringBox, - ParagraphAttributes paragraphAttributes, + const AttributedStringBox &attributedStringBox, + const ParagraphAttributes ¶graphAttributes, const TextLayoutContext &layoutContext, LayoutConstraints layoutConstraints, std::shared_ptr /* hostTextStorage */) const; @@ -45,12 +45,14 @@ class TextLayoutManager { * Measures lines of `attributedString` using native text rendering * infrastructure. */ - LinesMeasurements measureLines(AttributedString attributedString, ParagraphAttributes paragraphAttributes, Size size) - const; + LinesMeasurements measureLines( + const AttributedString &attributedString, + const ParagraphAttributes ¶graphAttributes, + Size size) const; std::shared_ptr getHostTextStorage( - AttributedString attributedString, - ParagraphAttributes paragraphAttributes, + const AttributedString &attributedString, + const ParagraphAttributes ¶graphAttributes, LayoutConstraints layoutConstraints) const; /** @@ -59,7 +61,7 @@ class TextLayoutManager { */ TextMeasurement measureCachedSpannableById( int64_t cacheId, - ParagraphAttributes const ¶graphAttributes, + const ParagraphAttributes ¶graphAttributes, LayoutConstraints layoutConstraints) const; /* @@ -69,15 +71,20 @@ class TextLayoutManager { void *getNativeTextLayoutManager() const; static void GetTextLayout( - AttributedStringBox attributedStringBox, - ParagraphAttributes paragraphAttributes, + const AttributedStringBox &attributedStringBox, + const ParagraphAttributes ¶graphAttributes, LayoutConstraints layoutConstraints, winrt::com_ptr &spTextLayout) noexcept; #pragma endregion private: - static winrt::hstring GetTransformedText(AttributedStringBox const &attributedStringBox); + static winrt::hstring GetTransformedText(const AttributedString &attributedString); + static void GetTextLayout( + const AttributedString &attributedString, + const ParagraphAttributes ¶graphAttributes, + Size size, + winrt::com_ptr &spTextLayout) noexcept; ContextContainer::Shared m_contextContainer; #pragma warning(push)