This commit is contained in:
Yamozha
2021-04-02 02:24:13 +03:00
parent c23950b545
commit 7256d79e2c
31493 changed files with 3036630 additions and 0 deletions

View File

@ -0,0 +1,57 @@
/*
* 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 "ParagraphShadowNode.h"
#include <react/config/ReactNativeConfig.h>
#include <react/core/ConcreteComponentDescriptor.h>
#include <react/textlayoutmanager/TextLayoutManager.h>
#include <react/utils/ContextContainer.h>
namespace facebook {
namespace react {
/*
* Descriptor for <Paragraph> component.
*/
class ParagraphComponentDescriptor final
: public ConcreteComponentDescriptor<ParagraphShadowNode> {
public:
ParagraphComponentDescriptor(ComponentDescriptorParameters const &parameters)
: ConcreteComponentDescriptor<ParagraphShadowNode>(parameters) {
// Every single `ParagraphShadowNode` will have a reference to
// a shared `TextLayoutManager`.
textLayoutManager_ = std::make_shared<TextLayoutManager>(contextContainer_);
}
protected:
void adopt(UnsharedShadowNode shadowNode) const override {
ConcreteComponentDescriptor::adopt(shadowNode);
assert(std::dynamic_pointer_cast<ParagraphShadowNode>(shadowNode));
auto paragraphShadowNode =
std::static_pointer_cast<ParagraphShadowNode>(shadowNode);
// `ParagraphShadowNode` uses `TextLayoutManager` to measure text content
// and communicate text rendering metrics to mounting layer.
paragraphShadowNode->setTextLayoutManager(textLayoutManager_);
paragraphShadowNode->dirtyLayout();
// All `ParagraphShadowNode`s must have leaf Yoga nodes with properly
// setup measure function.
paragraphShadowNode->enableMeasurement();
}
private:
SharedTextLayoutManager textLayoutManager_;
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,45 @@
/*
* 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 "ParagraphProps.h"
#include <react/attributedstring/conversions.h>
#include <react/attributedstring/primitives.h>
#include <react/core/propsConversions.h>
#include <react/debug/debugStringConvertibleUtils.h>
#include <glog/logging.h>
namespace facebook {
namespace react {
ParagraphProps::ParagraphProps(
ParagraphProps const &sourceProps,
RawProps const &rawProps)
: ViewProps(sourceProps, rawProps),
BaseTextProps(sourceProps, rawProps),
paragraphAttributes(
convertRawProp(rawProps, sourceProps.paragraphAttributes, {})),
isSelectable(convertRawProp(
rawProps,
"selectable",
sourceProps.isSelectable,
{})){};
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList ParagraphProps::getDebugProps() const {
return ViewProps::getDebugProps() + BaseTextProps::getDebugProps() +
paragraphAttributes.getDebugProps() +
SharedDebugStringConvertibleList{
debugStringConvertibleItem("isSelectable", isSelectable)};
}
#endif
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,52 @@
/*
* 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 <limits>
#include <memory>
#include <react/attributedstring/ParagraphAttributes.h>
#include <react/components/text/BaseTextProps.h>
#include <react/components/view/ViewProps.h>
#include <react/core/Props.h>
namespace facebook {
namespace react {
/*
* Props of <Paragraph> component.
* Most of the props are directly stored in composed `ParagraphAttributes`
* object.
*/
class ParagraphProps : public ViewProps, public BaseTextProps {
public:
ParagraphProps() = default;
ParagraphProps(ParagraphProps const &sourceProps, RawProps const &rawProps);
#pragma mark - Props
/*
* Contains all prop values that affect visual representation of the
* paragraph.
*/
ParagraphAttributes const paragraphAttributes{};
/*
* Defines can the text be selected (and copied) or not.
*/
bool const isSelectable{};
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugProps() const override;
#endif
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,221 @@
/*
* 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 "ParagraphShadowNode.h"
#include <cmath>
#include <react/attributedstring/AttributedStringBox.h>
#include <react/components/view/ViewShadowNode.h>
#include <react/components/view/conversions.h>
#include <react/graphics/rounding.h>
#include "ParagraphState.h"
namespace facebook {
namespace react {
using Content = ParagraphShadowNode::Content;
char const ParagraphComponentName[] = "Paragraph";
Content const &ParagraphShadowNode::getContent() const {
if (content_.has_value()) {
return content_.value();
}
ensureUnsealed();
auto textAttributes = TextAttributes::defaultTextAttributes();
textAttributes.apply(getConcreteProps().textAttributes);
textAttributes.layoutDirection =
YGNodeLayoutGetDirection(&yogaNode_) == YGDirectionRTL
? LayoutDirection::RightToLeft
: LayoutDirection::LeftToRight;
auto attributedString = AttributedString{};
auto attachments = Attachments{};
buildAttributedString(textAttributes, *this, attributedString, attachments);
content_ = Content{
attributedString, getConcreteProps().paragraphAttributes, attachments};
return content_.value();
}
Content ParagraphShadowNode::getContentWithMeasuredAttachments(
LayoutContext const &layoutContext,
LayoutConstraints const &layoutConstraints) const {
auto content = getContent();
if (content.attachments.empty()) {
// Base case: No attachments, nothing to do.
return content;
}
auto localLayoutConstraints = layoutConstraints;
// Having enforced minimum size for text fragments doesn't make much sense.
localLayoutConstraints.minimumSize = Size{0, 0};
auto &fragments = content.attributedString.getFragments();
for (auto const &attachment : content.attachments) {
auto laytableShadowNode =
traitCast<LayoutableShadowNode const *>(attachment.shadowNode);
if (!laytableShadowNode) {
continue;
}
auto size =
laytableShadowNode->measure(layoutContext, localLayoutConstraints);
// Rounding to *next* value on the pixel grid.
size.width += 0.01;
size.height += 0.01;
size = roundToPixel<&ceil>(size, layoutContext.pointScaleFactor);
auto fragmentLayoutMetrics = LayoutMetrics{};
fragmentLayoutMetrics.pointScaleFactor = layoutContext.pointScaleFactor;
fragmentLayoutMetrics.frame.size = size;
fragments[attachment.fragmentIndex].parentShadowView.layoutMetrics =
fragmentLayoutMetrics;
}
return content;
}
void ParagraphShadowNode::setTextLayoutManager(
SharedTextLayoutManager textLayoutManager) {
ensureUnsealed();
textLayoutManager_ = textLayoutManager;
}
void ParagraphShadowNode::updateStateIfNeeded(Content const &content) {
ensureUnsealed();
auto &state = getStateData();
assert(textLayoutManager_);
assert(
(!state.layoutManager || state.layoutManager == textLayoutManager_) &&
"`StateData` refers to a different `TextLayoutManager`");
if (state.attributedString == content.attributedString &&
state.layoutManager == textLayoutManager_) {
return;
}
setStateData(ParagraphState{content.attributedString,
content.paragraphAttributes,
textLayoutManager_});
}
#pragma mark - LayoutableShadowNode
Size ParagraphShadowNode::measure(LayoutConstraints layoutConstraints) const {
auto content =
getContentWithMeasuredAttachments(LayoutContext{}, layoutConstraints);
if (content.attributedString.isEmpty()) {
return layoutConstraints.clamp({0, 0});
}
return textLayoutManager_
->measure(
AttributedStringBox{content.attributedString},
content.paragraphAttributes,
layoutConstraints)
.size;
}
void ParagraphShadowNode::layout(LayoutContext layoutContext) {
ensureUnsealed();
auto layoutMetrics = getLayoutMetrics();
auto availableSize = layoutMetrics.getContentFrame().size;
auto layoutConstraints = LayoutConstraints{
availableSize, availableSize, layoutMetrics.layoutDirection};
auto content =
getContentWithMeasuredAttachments(layoutContext, layoutConstraints);
updateStateIfNeeded(content);
if (content.attachments.empty()) {
// No attachments, nothing to layout.
return;
}
auto measurement = textLayoutManager_->measure(
AttributedStringBox{content.attributedString},
content.paragraphAttributes,
layoutConstraints);
// Iterating on attachments, we clone shadow nodes and moving
// `paragraphShadowNode` that represents clones of `this` object.
auto paragraphShadowNode = static_cast<ParagraphShadowNode *>(this);
// `paragraphOwningShadowNode` is owning pointer to`paragraphShadowNode`
// (besides the initial case when `paragraphShadowNode == this`), we need this
// only to keep it in memory for a while.
auto paragraphOwningShadowNode = ShadowNode::Unshared{};
assert(content.attachments.size() == measurement.attachments.size());
for (auto i = 0; i < content.attachments.size(); i++) {
auto &attachment = content.attachments.at(i);
if (!traitCast<LayoutableShadowNode const *>(attachment.shadowNode)) {
// Not a layoutable `ShadowNode`, no need to lay it out.
continue;
}
auto clonedShadowNode = ShadowNode::Unshared{};
paragraphOwningShadowNode = paragraphShadowNode->cloneTree(
attachment.shadowNode->getFamily(),
[&](ShadowNode const &oldShadowNode) {
clonedShadowNode = oldShadowNode.clone({});
return clonedShadowNode;
});
paragraphShadowNode =
static_cast<ParagraphShadowNode *>(paragraphOwningShadowNode.get());
auto &layoutableShadowNode = const_cast<LayoutableShadowNode &>(
traitCast<LayoutableShadowNode const &>(*clonedShadowNode));
auto attachmentFrame = measurement.attachments[i].frame;
auto attachmentSize = roundToPixel<&ceil>(
attachmentFrame.size, layoutMetrics.pointScaleFactor);
auto attachmentOrigin = roundToPixel<&round>(
attachmentFrame.origin, layoutMetrics.pointScaleFactor);
auto attachmentLayoutContext = layoutContext;
attachmentLayoutContext.absolutePosition += attachmentOrigin;
auto attachmentLayoutConstrains = LayoutConstraints{
attachmentSize, attachmentSize, layoutConstraints.layoutDirection};
// Laying out the `ShadowNode` and the subtree starting from it.
layoutableShadowNode.layoutTree(
attachmentLayoutContext, attachmentLayoutConstrains);
// Altering the origin of the `ShadowNode` (which is defined by text layout,
// not by internal styles and state).
auto attachmentLayoutMetrics = layoutableShadowNode.getLayoutMetrics();
attachmentLayoutMetrics.frame.origin = attachmentOrigin;
layoutableShadowNode.setLayoutMetrics(attachmentLayoutMetrics);
}
// If we ended up cloning something, we need to update the list of children to
// reflect the changes that we made.
if (paragraphShadowNode != this) {
this->children_ =
static_cast<ParagraphShadowNode const *>(paragraphShadowNode)
->children_;
}
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,106 @@
/*
* 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 <folly/Optional.h>
#include <react/components/text/ParagraphProps.h>
#include <react/components/text/ParagraphState.h>
#include <react/components/text/TextShadowNode.h>
#include <react/components/view/ConcreteViewShadowNode.h>
#include <react/core/ConcreteShadowNode.h>
#include <react/core/LayoutContext.h>
#include <react/core/ShadowNode.h>
#include <react/textlayoutmanager/TextLayoutManager.h>
namespace facebook {
namespace react {
extern char const ParagraphComponentName[];
using ParagraphEventEmitter = ViewEventEmitter;
/*
* `ShadowNode` for <Paragraph> component, represents <View>-like component
* containing and displaying text. Text content is represented as nested <Text>
* and <RawText> components.
*/
class ParagraphShadowNode : public ConcreteViewShadowNode<
ParagraphComponentName,
ParagraphProps,
ParagraphEventEmitter,
ParagraphState>,
public BaseTextShadowNode {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
static ShadowNodeTraits BaseTraits() {
auto traits = ConcreteViewShadowNode::BaseTraits();
traits.set(ShadowNodeTraits::Trait::LeafYogaNode);
traits.set(ShadowNodeTraits::Trait::TextKind);
#ifdef ANDROID
// Unsetting `FormsStackingContext` trait is essential on Android where we
// can't mount views inside `TextView`.
traits.unset(ShadowNodeTraits::Trait::FormsStackingContext);
#endif
return traits;
}
/*
* Associates a shared TextLayoutManager with the node.
* `ParagraphShadowNode` uses the manager to measure text content
* and construct `ParagraphState` objects.
*/
void setTextLayoutManager(SharedTextLayoutManager textLayoutManager);
#pragma mark - LayoutableShadowNode
void layout(LayoutContext layoutContext) override;
Size measure(LayoutConstraints layoutConstraints) const override;
/*
* Internal representation of the nested content of the node in a format
* suitable for future processing.
*/
class Content final {
public:
AttributedString attributedString;
ParagraphAttributes paragraphAttributes;
Attachments attachments;
};
private:
/*
* Builds (if needed) and returns a reference to a `Content` object.
*/
Content const &getContent() const;
/*
* Builds and returns a `Content` object with given `layoutConstraints`.
*/
Content getContentWithMeasuredAttachments(
LayoutContext const &layoutContext,
LayoutConstraints const &layoutConstraints) const;
/*
* Creates a `State` object (with `AttributedText` and
* `TextLayoutManager`) if needed.
*/
void updateStateIfNeeded(Content const &content);
SharedTextLayoutManager textLayoutManager_;
/*
* Cached content of the subtree started from the node.
*/
mutable better::optional<Content> content_{};
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,23 @@
/*
* 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 "ParagraphState.h"
#include <react/components/text/conversions.h>
#include <react/debug/debugStringConvertibleUtils.h>
namespace facebook {
namespace react {
#ifdef ANDROID
folly::dynamic ParagraphState::getDynamic() const {
return toDynamic(*this);
}
#endif
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,64 @@
/*
* 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 <react/attributedstring/AttributedString.h>
#include <react/attributedstring/ParagraphAttributes.h>
#include <react/textlayoutmanager/TextLayoutManager.h>
#ifdef ANDROID
#include <folly/dynamic.h>
#endif
namespace facebook {
namespace react {
/*
* State for <Paragraph> component.
* Represents what to render and how to render.
*/
class ParagraphState final {
public:
/*
* All content of <Paragraph> component represented as an `AttributedString`.
*/
AttributedString attributedString;
/*
* Represents all visual attributes of a paragraph of text represented as
* a ParagraphAttributes.
*/
ParagraphAttributes paragraphAttributes;
/*
* `TextLayoutManager` provides a connection to platform-specific
* text rendering infrastructure which is capable to render the
* `AttributedString`.
*/
SharedTextLayoutManager layoutManager;
#ifdef ANDROID
ParagraphState(
AttributedString const &attributedString,
ParagraphAttributes const &paragraphAttributes,
SharedTextLayoutManager const &layoutManager)
: attributedString(attributedString),
paragraphAttributes(paragraphAttributes),
layoutManager(layoutManager) {}
ParagraphState() = default;
ParagraphState(
ParagraphState const &previousState,
folly::dynamic const &data) {
assert(false && "Not supported");
};
folly::dynamic getDynamic() const;
#endif
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,27 @@
/*
* 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 <folly/dynamic.h>
#include <react/attributedstring/conversions.h>
#include <react/components/text/ParagraphState.h>
namespace facebook {
namespace react {
#ifdef ANDROID
inline folly::dynamic toDynamic(ParagraphState const &paragraphState) {
folly::dynamic newState = folly::dynamic::object();
newState["attributedString"] = toDynamic(paragraphState.attributedString);
newState["paragraphAttributes"] =
toDynamic(paragraphState.paragraphAttributes);
newState["hash"] = newState["attributedString"]["hash"];
return newState;
}
#endif
} // namespace react
} // namespace facebook