Platform specific TextLayoutManager Headers (#50889)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/50889

This effectively reverts D67064488

We are trying to smash together functions, variables, virtual or non-virtual, all required by different platforms, into a single header, often using #ifdefs that are not what we want (apart from a bad editor experience, `#ifdef ANDROID` may or may not be compiled into the react-native-cxx platform, and we cannot use it for the Android platform reliably).

For Facsimile, we are going to be introducing more potential divergence, with the idea of `PreparedText`, where we can generate intermediate products as part of the layout process to be reused later. I'm planning to design that in a way which can be eventually reused across platforms, but not everywhere.

I think the best path for this is going to be to allow each platform to have their own headers, instead of the current messiness, then allow shared code (e.g. in `ParagraphShadowNode`) to pick how to interact at compile time. I added an example of this as part of `TextLayoutManagerExtended`, to customize how we act if a `TextLayoutManager` chooses not to implement `measureLines`.

Changelog: [Internal]

Reviewed By: rshest

Differential Revision: D73557126

fbshipit-source-id: 9851ebba691b0d123f6a355126f2d5b003aceba0
This commit is contained in:
Nick Gerleman
2025-04-29 18:45:58 -07:00
committed by Facebook GitHub Bot
parent 2efe8094c0
commit 2e42eab6df
7 changed files with 229 additions and 43 deletions
@@ -17,6 +17,7 @@
#include <react/renderer/graphics/rounding.h>
#include <react/renderer/telemetry/TransactionTelemetry.h>
#include <react/renderer/textlayoutmanager/TextLayoutContext.h>
#include <react/renderer/textlayoutmanager/TextLayoutManagerExtended.h>
#include "ParagraphState.h"
@@ -215,8 +216,17 @@ Float ParagraphShadowNode::baseline(
AttributedStringBox attributedStringBox{attributedString};
return LineMeasurement::baseline(textLayoutManager_->measureLines(
attributedStringBox, getConcreteProps().paragraphAttributes, size));
if constexpr (TextLayoutManagerExtended::supportsLineMeasurement()) {
auto lines =
TextLayoutManagerExtended(*textLayoutManager_)
.measureLines(
attributedStringBox, content.paragraphAttributes, size);
return LineMeasurement::baseline(lines);
} else {
LOG(WARNING)
<< "Baseline alignment is not supported by the current platform";
return 0;
}
}
void ParagraphShadowNode::layout(LayoutContext layoutContext) {
@@ -239,9 +249,15 @@ void ParagraphShadowNode::layout(LayoutContext layoutContext) {
AttributedStringBox attributedStringBox{content.attributedString};
if (getConcreteProps().onTextLayout) {
auto linesMeasurements = textLayoutManager_->measureLines(
attributedStringBox, content.paragraphAttributes, size);
getConcreteEventEmitter().onTextLayout(linesMeasurements);
if constexpr (TextLayoutManagerExtended::supportsLineMeasurement()) {
auto linesMeasurements =
TextLayoutManagerExtended(*textLayoutManager_)
.measureLines(
attributedStringBox, content.paragraphAttributes, size);
getConcreteEventEmitter().onTextLayout(linesMeasurements);
} else {
LOG(WARNING) << "onTextLayout is not supported by the current platform";
}
}
if (content.attachments.empty()) {
@@ -7,6 +7,8 @@
#pragma once
#include <glog/logging.h>
#include <react/renderer/attributedstring/AttributedString.h>
#include <react/renderer/attributedstring/AttributedStringBox.h>
#include <react/renderer/components/text/BaseTextShadowNode.h>
@@ -18,6 +20,7 @@
#include <react/renderer/core/LayoutContext.h>
#include <react/renderer/textlayoutmanager/TextLayoutContext.h>
#include <react/renderer/textlayoutmanager/TextLayoutManager.h>
#include <react/renderer/textlayoutmanager/TextLayoutManagerExtended.h>
#include <react/utils/ContextContainer.h>
namespace facebook::react {
@@ -106,9 +109,18 @@ class BaseTextInputShadowNode : public ConcreteViewShadowNode<
&(YogaLayoutableShadowNode::yogaNode_), YGEdgeTop);
AttributedStringBox attributedStringBox{attributedString};
return LineMeasurement::baseline(textLayoutManager_->measureLines(
attributedStringBox, props.paragraphAttributes, size)) +
top;
if constexpr (TextLayoutManagerExtended::supportsLineMeasurement()) {
auto lines =
TextLayoutManagerExtended(*textLayoutManager_)
.measureLines(
attributedStringBox, props.paragraphAttributes, size);
return LineMeasurement::baseline(lines) + top;
} else {
LOG(WARNING)
<< "Baseline alignment is not supported by the current platform";
return top;
}
}
/*
@@ -0,0 +1,61 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <glog/logging.h>
#include <react/renderer/attributedstring/AttributedStringBox.h>
#include <react/renderer/attributedstring/ParagraphAttributes.h>
#include <react/renderer/graphics/Size.h>
#include <react/renderer/textlayoutmanager/TextLayoutManager.h>
#include <react/renderer/textlayoutmanager/TextMeasureCache.h>
namespace facebook::react {
namespace detail {
/**
* TextLayoutManagerExtended acts as an adapter for TextLayoutManager methods
* which may not exist for a specific platform. Callers can check at
* compile-time whether a method is supported, and calling if it is not will
* terminate.
*/
template <typename TextLayoutManagerT>
class TextLayoutManagerExtended {
public:
static constexpr bool supportsLineMeasurement() {
return requires(TextLayoutManagerT textLayoutManager) {
{
textLayoutManager.measureLines(
AttributedStringBox{}, ParagraphAttributes{}, Size{})
} -> std::same_as<LinesMeasurements>;
};
}
TextLayoutManagerExtended(const TextLayoutManagerT& textLayoutManager)
: textLayoutManager_(textLayoutManager) {}
LinesMeasurements measureLines(
const AttributedStringBox& attributedStringBox,
const ParagraphAttributes& paragraphAttributes,
const Size& size) {
if constexpr (supportsLineMeasurement()) {
return textLayoutManager_.measureLines(
attributedStringBox, paragraphAttributes, size);
}
LOG(FATAL) << "Platform TextLayoutManager does not support measureLines";
}
private:
const TextLayoutManagerT& textLayoutManager_;
};
} // namespace detail
using TextLayoutManagerExtended =
detail::TextLayoutManagerExtended<TextLayoutManager>;
} // namespace facebook::react
@@ -26,7 +26,6 @@ class TextLayoutManager;
class TextLayoutManager {
public:
TextLayoutManager(const ContextContainer::Shared& contextContainer);
virtual ~TextLayoutManager() = default;
/*
* Not copyable.
@@ -43,13 +42,12 @@ class TextLayoutManager {
/*
* Measures `attributedString` using native text rendering infrastructure.
*/
virtual TextMeasurement measure(
TextMeasurement measure(
const AttributedStringBox& attributedStringBox,
const ParagraphAttributes& paragraphAttributes,
const TextLayoutContext& layoutContext,
const LayoutConstraints& layoutConstraints) const;
#ifdef ANDROID
/**
* Measures an AttributedString on the platform, as identified by some
* opaque cache ID.
@@ -58,30 +56,18 @@ class TextLayoutManager {
int64_t cacheId,
const ParagraphAttributes& paragraphAttributes,
const LayoutConstraints& layoutConstraints) const;
#endif
/*
* Measures lines of `attributedString` using native text rendering
* infrastructure.
*/
virtual LinesMeasurements measureLines(
LinesMeasurements measureLines(
const AttributedStringBox& attributedStringBox,
const ParagraphAttributes& paragraphAttributes,
const Size& size) const;
#ifdef __APPLE__
/*
* Returns an opaque pointer to platform-specific TextLayoutManager.
* Is used on a native views layer to delegate text rendering to the manager.
*/
std::shared_ptr<void> getNativeTextLayoutManager() const;
#endif
protected:
private:
std::shared_ptr<const ContextContainer> contextContainer_;
#ifdef __APPLE__
std::shared_ptr<void> nativeTextLayoutManager_;
#endif
TextMeasureCache textMeasureCache_;
LineMeasureCache lineMeasureCache_;
};
@@ -11,8 +11,7 @@ namespace facebook::react {
TextLayoutManager::TextLayoutManager(
const ContextContainer::Shared& /*contextContainer*/)
: textMeasureCache_(kSimpleThreadSafeCacheSizeCap),
lineMeasureCache_(kSimpleThreadSafeCacheSizeCap) {}
: textMeasureCache_(kSimpleThreadSafeCacheSizeCap) {}
TextMeasurement TextLayoutManager::measure(
const AttributedStringBox& attributedStringBox,
@@ -29,20 +28,4 @@ TextMeasurement TextLayoutManager::measure(
return TextMeasurement{{0, 0}, attachments};
}
#ifdef ANDROID
TextMeasurement TextLayoutManager::measureCachedSpannableById(
int64_t /*cacheId*/,
const ParagraphAttributes& /*paragraphAttributes*/,
const LayoutConstraints& /*layoutConstraints*/) const {
return {};
}
#endif
LinesMeasurements TextLayoutManager::measureLines(
const AttributedStringBox& /*attributedStringBox*/,
const ParagraphAttributes& /*paragraphAttributes*/,
const Size& /*size*/) const {
return {};
};
} // namespace facebook::react
@@ -0,0 +1,57 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/attributedstring/AttributedStringBox.h>
#include <react/renderer/attributedstring/ParagraphAttributes.h>
#include <react/renderer/core/LayoutConstraints.h>
#include <react/renderer/textlayoutmanager/TextLayoutContext.h>
#include <react/renderer/textlayoutmanager/TextMeasureCache.h>
#include <react/utils/ContextContainer.h>
#include <memory>
namespace facebook::react {
class TextLayoutManager;
/*
* Cross platform facade for text measurement (e.g. Android-specific
* TextLayoutManager)
*/
class TextLayoutManager {
public:
TextLayoutManager(const ContextContainer::Shared& contextContainer);
virtual ~TextLayoutManager() = default;
/*
* Not copyable.
*/
TextLayoutManager(const TextLayoutManager&) = delete;
TextLayoutManager& operator=(const TextLayoutManager&) = delete;
/*
* Not movable.
*/
TextLayoutManager(TextLayoutManager&&) = delete;
TextLayoutManager& operator=(TextLayoutManager&&) = delete;
/*
* Measures `attributedString` using native text rendering infrastructure.
*/
virtual TextMeasurement measure(
const AttributedStringBox& attributedStringBox,
const ParagraphAttributes& paragraphAttributes,
const TextLayoutContext& layoutContext,
const LayoutConstraints& layoutConstraints) const;
protected:
std::shared_ptr<const ContextContainer> contextContainer_;
TextMeasureCache textMeasureCache_;
};
} // namespace facebook::react
@@ -0,0 +1,71 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/attributedstring/AttributedStringBox.h>
#include <react/renderer/attributedstring/ParagraphAttributes.h>
#include <react/renderer/core/LayoutConstraints.h>
#include <react/renderer/textlayoutmanager/TextLayoutContext.h>
#include <react/renderer/textlayoutmanager/TextMeasureCache.h>
#include <react/utils/ContextContainer.h>
#include <memory>
namespace facebook::react {
/*
* Cross platform facade for text measurement (e.g. Android-specific
* TextLayoutManager)
*/
class TextLayoutManager {
public:
TextLayoutManager(const ContextContainer::Shared& contextContainer);
/*
* Not copyable.
*/
TextLayoutManager(const TextLayoutManager&) = delete;
TextLayoutManager& operator=(const TextLayoutManager&) = delete;
/*
* Not movable.
*/
TextLayoutManager(TextLayoutManager&&) = delete;
TextLayoutManager& operator=(TextLayoutManager&&) = delete;
/*
* Measures `attributedString` using native text rendering infrastructure.
*/
TextMeasurement measure(
const AttributedStringBox& attributedStringBox,
const ParagraphAttributes& paragraphAttributes,
const TextLayoutContext& layoutContext,
const LayoutConstraints& layoutConstraints) const;
/*
* Measures lines of `attributedString` using native text rendering
* infrastructure.
*/
LinesMeasurements measureLines(
const AttributedStringBox& attributedStringBox,
const ParagraphAttributes& paragraphAttributes,
const Size& size) const;
/*
* Returns an opaque pointer to platform-specific TextLayoutManager.
* Is used on a native views layer to delegate text rendering to the manager.
*/
std::shared_ptr<void> getNativeTextLayoutManager() const;
protected:
std::shared_ptr<const ContextContainer> contextContainer_;
std::shared_ptr<void> nativeTextLayoutManager_;
TextMeasureCache textMeasureCache_;
LineMeasureCache lineMeasureCache_;
};
} // namespace facebook::react