From c4876d0313fe27c6009ba6c70530552ffe8ced2b Mon Sep 17 00:00:00 2001 From: Valentin Shergin Date: Wed, 4 Dec 2019 18:34:29 -0800 Subject: [PATCH] Fabric: Introducing `AttributedStringBox` Summary: The diff implements a new class called `AttributedStringBox` that represents an object storing a shared `AttributedString` *or* a shared pointer to some opaque platform-specific object that can be used as an attributed string. The class serves two main purposes: - Represent type-erased attributed string entity (which can be platform-specific or platform-independent); - Represent a container that can be copied with constant complexity. Why? Several reasons: - Sometimes it makes sense to keep an attributed string as a shared resource. This way we don't need to pay for expensive copying and we also implement a copy-on-write semantics on top of that if needed. - We need to extend a TextLayoutMeasure API to support measuring some platform-specific attributed string implementation to remove the necessity of converting a string back and forth between representations. That's especially important for TextInput because we will need to measure that very efficiently (and the source of measuring, in this case, is a platform attributed string). In other words, we need something to store inside TextInputState to measure and update very efficiently. The source of this data might be a native TextInput control or a data from React, to represent that kinda object we need this data structure (and interfaces that deal with it). Changelog: [Internal] Fabric-specific internal change. Reviewed By: sammy-SC Differential Revision: D18670793 fbshipit-source-id: bc0164f801f28642f7c6da340af12acf33b85d24 --- .../attributedstring/AttributedStringBox.cpp | 44 +++++++++++++ .../attributedstring/AttributedStringBox.h | 62 +++++++++++++++++++ .../text/paragraph/ParagraphShadowNode.cpp | 5 +- .../AndroidTextInputShadowNode.cpp | 5 +- .../platform/android/TextLayoutManager.cpp | 4 +- .../platform/android/TextLayoutManager.h | 3 +- .../platform/ios/TextLayoutManager.h | 4 +- .../platform/ios/TextLayoutManager.mm | 4 +- 8 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 ReactCommon/fabric/attributedstring/AttributedStringBox.cpp create mode 100644 ReactCommon/fabric/attributedstring/AttributedStringBox.h diff --git a/ReactCommon/fabric/attributedstring/AttributedStringBox.cpp b/ReactCommon/fabric/attributedstring/AttributedStringBox.cpp new file mode 100644 index 00000000000..d0846b7252d --- /dev/null +++ b/ReactCommon/fabric/attributedstring/AttributedStringBox.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "AttributedStringBox.h" + +namespace facebook { +namespace react { + +AttributedStringBox::AttributedStringBox() + : mode_(Mode::Value), + value_(std::make_shared(AttributedString{})), + opaquePointer_({}){}; + +AttributedStringBox::AttributedStringBox(AttributedString const &value) + : mode_(Mode::Value), + value_(std::make_shared(value)), + opaquePointer_({}){}; + +AttributedStringBox::AttributedStringBox( + std::shared_ptr const &opaquePointer) + : mode_(Mode::OpaquePointer), value_({}), opaquePointer_(opaquePointer) {} + +AttributedStringBox::Mode AttributedStringBox::getMode() const { + return mode_; +} + +AttributedString const &AttributedStringBox::getValue() const { + assert(mode_ == AttributedStringBox::Mode::Value); + assert(value_); + return *value_; +} + +std::shared_ptr AttributedStringBox::getOpaquePointer() const { + assert(mode_ == AttributedStringBox::Mode::OpaquePointer); + assert(opaquePointer_); + return opaquePointer_; +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/attributedstring/AttributedStringBox.h b/ReactCommon/fabric/attributedstring/AttributedStringBox.h new file mode 100644 index 00000000000..ee8cf099223 --- /dev/null +++ b/ReactCommon/fabric/attributedstring/AttributedStringBox.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) Facebook, Inc. and its 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 + +#include + +namespace facebook { +namespace react { + +/* + * Represents an object storing a shared `AttributedString` or a shared pointer + * to some opaque platform-specific object that can be used as an attributed + * string. The class serves two main purposes: + * - Represent type-erased attributed string entity (which can be + * platform-specific or platform-independent); + * - Represent a container that can be copied with constant complexity. + */ +class AttributedStringBox final { + public: + enum class Mode { Value, OpaquePointer }; + + /* + * Default constructor constructs an empty string. + */ + AttributedStringBox(); + + /* + * Custom explicit constructors. + */ + explicit AttributedStringBox(AttributedString const &value); + explicit AttributedStringBox(std::shared_ptr const &opaquePointer); + + /* + * Movable, Copyable, Assignable. + */ + AttributedStringBox(AttributedStringBox const &other) = default; + AttributedStringBox(AttributedStringBox &&other) noexcept = default; + AttributedStringBox &operator=(AttributedStringBox const &other) = default; + AttributedStringBox &operator=(AttributedStringBox &&other) = default; + + /* + * Getters. + */ + Mode getMode() const; + AttributedString const &getValue() const; + std::shared_ptr getOpaquePointer() const; + + private: + Mode mode_; + std::shared_ptr value_; + std::shared_ptr opaquePointer_; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/text/paragraph/ParagraphShadowNode.cpp b/ReactCommon/fabric/components/text/paragraph/ParagraphShadowNode.cpp index e1a0be32944..de977c229d0 100644 --- a/ReactCommon/fabric/components/text/paragraph/ParagraphShadowNode.cpp +++ b/ReactCommon/fabric/components/text/paragraph/ParagraphShadowNode.cpp @@ -7,6 +7,7 @@ #include "ParagraphShadowNode.h" +#include #include "ParagraphState.h" namespace facebook { @@ -62,7 +63,9 @@ Size ParagraphShadowNode::measure(LayoutConstraints layoutConstraints) const { } return textLayoutManager_->measure( - attributedString, getProps()->paragraphAttributes, layoutConstraints); + AttributedStringBox{attributedString}, + getProps()->paragraphAttributes, + layoutConstraints); } void ParagraphShadowNode::layout(LayoutContext layoutContext) { diff --git a/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputShadowNode.cpp b/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputShadowNode.cpp index 3affefe7f44..c0d7203a982 100644 --- a/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputShadowNode.cpp +++ b/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputShadowNode.cpp @@ -8,6 +8,7 @@ #include "AndroidTextInputShadowNode.h" #include +#include #include #include #include @@ -111,7 +112,9 @@ Size AndroidTextInputShadowNode::measure( } return textLayoutManager_->measure( - attributedString, getProps()->paragraphAttributes, layoutConstraints); + AttributedStringBox{attributedString}, + getProps()->paragraphAttributes, + layoutConstraints); } void AndroidTextInputShadowNode::layout(LayoutContext layoutContext) { diff --git a/ReactCommon/fabric/textlayoutmanager/platform/android/TextLayoutManager.cpp b/ReactCommon/fabric/textlayoutmanager/platform/android/TextLayoutManager.cpp index 88a7b161b18..9c96296129e 100644 --- a/ReactCommon/fabric/textlayoutmanager/platform/android/TextLayoutManager.cpp +++ b/ReactCommon/fabric/textlayoutmanager/platform/android/TextLayoutManager.cpp @@ -23,9 +23,11 @@ void *TextLayoutManager::getNativeTextLayoutManager() const { } Size TextLayoutManager::measure( - AttributedString attributedString, + AttributedStringBox attributedStringBox, ParagraphAttributes paragraphAttributes, LayoutConstraints layoutConstraints) const { + auto &attributedString = attributedStringBox.getValue(); + return measureCache_.get( MeasureCacheKey{attributedString, paragraphAttributes, layoutConstraints}, [&](MeasureCacheKey const &key) { diff --git a/ReactCommon/fabric/textlayoutmanager/platform/android/TextLayoutManager.h b/ReactCommon/fabric/textlayoutmanager/platform/android/TextLayoutManager.h index 1e126f311d5..06551d178d3 100644 --- a/ReactCommon/fabric/textlayoutmanager/platform/android/TextLayoutManager.h +++ b/ReactCommon/fabric/textlayoutmanager/platform/android/TextLayoutManager.h @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -35,7 +36,7 @@ class TextLayoutManager { * Measures `attributedString` using native text rendering infrastructure. */ Size measure( - AttributedString attributedString, + AttributedStringBox attributedStringBox, ParagraphAttributes paragraphAttributes, LayoutConstraints layoutConstraints) const; diff --git a/ReactCommon/fabric/textlayoutmanager/platform/ios/TextLayoutManager.h b/ReactCommon/fabric/textlayoutmanager/platform/ios/TextLayoutManager.h index 56ecc5f69a4..80a04e8e16c 100644 --- a/ReactCommon/fabric/textlayoutmanager/platform/ios/TextLayoutManager.h +++ b/ReactCommon/fabric/textlayoutmanager/platform/ios/TextLayoutManager.h @@ -9,7 +9,7 @@ #include -#include +#include #include #include #include @@ -34,7 +34,7 @@ class TextLayoutManager { * Measures `attributedString` using native text rendering infrastructure. */ Size measure( - AttributedString attributedString, + AttributedStringBox attributedStringBox, ParagraphAttributes paragraphAttributes, LayoutConstraints layoutConstraints) const; diff --git a/ReactCommon/fabric/textlayoutmanager/platform/ios/TextLayoutManager.mm b/ReactCommon/fabric/textlayoutmanager/platform/ios/TextLayoutManager.mm index 4d3f7ee861e..614c26e0f7b 100644 --- a/ReactCommon/fabric/textlayoutmanager/platform/ios/TextLayoutManager.mm +++ b/ReactCommon/fabric/textlayoutmanager/platform/ios/TextLayoutManager.mm @@ -30,10 +30,12 @@ void *TextLayoutManager::getNativeTextLayoutManager() const } Size TextLayoutManager::measure( - AttributedString attributedString, + AttributedStringBox attributedStringBox, ParagraphAttributes paragraphAttributes, LayoutConstraints layoutConstraints) const { + auto &attributedString = attributedStringBox.getValue(); + return measureCache_.get( MeasureCacheKey{attributedString, paragraphAttributes, layoutConstraints}, [&](MeasureCacheKey const &key) { RCTTextLayoutManager *textLayoutManager = (__bridge RCTTextLayoutManager *)self_;