Fabric: Calculating positions of attachments as part of text measurement

Summary:
This diff changes API we use to measure text. Previously, a platform-specific measure infra returned just the size of the text, now it returns the size and an array of frames that describe where attachments are placed.

Changelog: [Internal] Fabric-specific internal change.

Reviewed By: sammy-SC

Differential Revision: D20268041

fbshipit-source-id: 7c065607b6af18a36318db0aab24dad0f171d33a
This commit is contained in:
Valentin Shergin
2020-03-09 16:36:46 -07:00
committed by Facebook Github Bot
parent d1ad29b7db
commit 90ada5abb1
10 changed files with 94 additions and 40 deletions
@@ -70,10 +70,12 @@ Size ParagraphShadowNode::measure(LayoutConstraints layoutConstraints) const {
return layoutConstraints.clamp({0, 0});
}
return textLayoutManager_->measure(
AttributedStringBox{content.attributedString},
content.paragraphAttributes,
layoutConstraints);
return textLayoutManager_
->measure(
AttributedStringBox{content.attributedString},
content.paragraphAttributes,
layoutConstraints)
.size;
}
void ParagraphShadowNode::layout(LayoutContext layoutContext) {
@@ -180,10 +180,12 @@ Size AndroidTextInputShadowNode::measure(
return {0, 0};
}
return textLayoutManager_->measure(
AttributedStringBox{attributedString},
getConcreteProps().paragraphAttributes,
layoutConstraints);
return textLayoutManager_
->measure(
AttributedStringBox{attributedString},
getConcreteProps().paragraphAttributes,
layoutConstraints)
.size;
}
void AndroidTextInputShadowNode::layout(LayoutContext layoutContext) {
@@ -81,10 +81,12 @@ void TextInputShadowNode::updateStateIfNeeded() {
#pragma mark - LayoutableShadowNode
Size TextInputShadowNode::measure(LayoutConstraints layoutConstraints) const {
return textLayoutManager_->measure(
attributedStringBoxToMeasure(),
getConcreteProps().getEffectiveParagraphAttributes(),
layoutConstraints);
return textLayoutManager_
->measure(
attributedStringBoxToMeasure(),
getConcreteProps().getEffectiveParagraphAttributes(),
layoutConstraints)
.size;
}
void TextInputShadowNode::layout(LayoutContext layoutContext) {
@@ -16,6 +16,23 @@
namespace facebook {
namespace react {
/*
* Describes a result of text measuring.
*/
class TextMeasurement final {
public:
class Attachment final {
public:
Rect frame;
bool isClipped;
};
using Attachments = std::vector<Attachment>;
Size size;
Attachments attachments;
};
// The Key type that is used for Text Measure Cache.
// The equivalence and hashing operations of this are defined to respect the
// nature of text measuring.
@@ -39,7 +56,7 @@ constexpr auto kSimpleThreadSafeCacheSizeCap = size_t{256};
*/
using TextMeasureCache = SimpleThreadSafeCache<
TextMeasureCacheKey,
Size,
TextMeasurement,
kSimpleThreadSafeCacheSizeCap>;
inline bool areTextAttributesEquivalentLayoutWise(
@@ -22,7 +22,7 @@ void *TextLayoutManager::getNativeTextLayoutManager() const {
return self_;
}
Size TextLayoutManager::measure(
TextMeasurement TextLayoutManager::measure(
AttributedStringBox attributedStringBox,
ParagraphAttributes paragraphAttributes,
LayoutConstraints layoutConstraints) const {
@@ -36,7 +36,7 @@ Size TextLayoutManager::measure(
});
}
Size TextLayoutManager::doMeasure(
TextMeasurement TextLayoutManager::doMeasure(
AttributedString attributedString,
ParagraphAttributes paragraphAttributes,
LayoutConstraints layoutConstraints) const {
@@ -69,7 +69,7 @@ Size TextLayoutManager::doMeasure(
reinterpret_cast<ReadableMap::javaobject>(attributedStringRNM.get()));
local_ref<ReadableMap::javaobject> paragraphAttributesRM = make_local(
reinterpret_cast<ReadableMap::javaobject>(paragraphAttributesRNM.get()));
return yogaMeassureToSize(measure(
auto size = yogaMeassureToSize(measure(
fabricUIManager,
-1,
componentName.get(),
@@ -80,6 +80,8 @@ Size TextLayoutManager::doMeasure(
maximumSize.width,
minimumSize.height,
maximumSize.height));
return TextMeasurement{size, {}};
}
} // namespace react
@@ -32,7 +32,7 @@ class TextLayoutManager {
/*
* Measures `attributedString` using native text rendering infrastructure.
*/
Size measure(
TextMeasurement measure(
AttributedStringBox attributedStringBox,
ParagraphAttributes paragraphAttributes,
LayoutConstraints layoutConstraints) const;
@@ -44,7 +44,7 @@ class TextLayoutManager {
void *getNativeTextLayoutManager() const;
private:
Size doMeasure(
TextMeasurement doMeasure(
AttributedString attributedString,
ParagraphAttributes paragraphAttributes,
LayoutConstraints layoutConstraints) const;
@@ -11,6 +11,7 @@
#import <react/attributedstring/ParagraphAttributes.h>
#import <react/core/LayoutConstraints.h>
#import <react/graphics/Geometry.h>
#import <react/textlayoutmanager/TextMeasureCache.h>
NS_ASSUME_NONNULL_BEGIN
@@ -19,13 +20,13 @@ NS_ASSUME_NONNULL_BEGIN
*/
@interface RCTTextLayoutManager : NSObject
- (facebook::react::Size)measureAttributedString:(facebook::react::AttributedString)attributedString
paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes
layoutConstraints:(facebook::react::LayoutConstraints)layoutConstraints;
- (facebook::react::TextMeasurement)measureAttributedString:(facebook::react::AttributedString)attributedString
paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes
layoutConstraints:(facebook::react::LayoutConstraints)layoutConstraints;
- (facebook::react::Size)measureNSAttributedString:(NSAttributedString *)attributedString
paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes
layoutConstraints:(facebook::react::LayoutConstraints)layoutConstraints;
- (facebook::react::TextMeasurement)measureNSAttributedString:(NSAttributedString *)attributedString
paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes
layoutConstraints:(facebook::react::LayoutConstraints)layoutConstraints;
- (void)drawAttributedString:(facebook::react::AttributedString)attributedString
paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes
@@ -32,15 +32,15 @@ static NSLineBreakMode RCTNSLineBreakModeFromEllipsizeMode(EllipsizeMode ellipsi
}
}
- (facebook::react::Size)measureNSAttributedString:(NSAttributedString *)attributedString
paragraphAttributes:(ParagraphAttributes)paragraphAttributes
layoutConstraints:(LayoutConstraints)layoutConstraints
- (TextMeasurement)measureNSAttributedString:(NSAttributedString *)attributedString
paragraphAttributes:(ParagraphAttributes)paragraphAttributes
layoutConstraints:(LayoutConstraints)layoutConstraints
{
if (attributedString.length == 0) {
// This is not really an optimization because that should be checked much earlier on the call stack.
// Sometimes, very irregularly, measuring an empty string crashes/freezes iOS internal text infrastructure.
// This is our last line of defense.
return {0, 0};
return {};
}
CGSize maximumSize = CGSize{layoutConstraints.maximumSize.width, CGFLOAT_MAX};
@@ -55,12 +55,38 @@ static NSLineBreakMode RCTNSLineBreakModeFromEllipsizeMode(EllipsizeMode ellipsi
CGSize size = [layoutManager usedRectForTextContainer:textContainer].size;
return {size.width, size.height};
__block auto attachments = TextMeasurement::Attachments{};
[textStorage
enumerateAttribute:NSAttachmentAttributeName
inRange:NSMakeRange(0, textStorage.length)
options:0
usingBlock:^(NSTextAttachment *attachment, NSRange range, BOOL *stop) {
if (!attachment) {
return;
}
CGSize attachmentSize = attachment.bounds.size;
CGRect glyphRect = [layoutManager boundingRectForGlyphRange:range inTextContainer:textContainer];
UIFont *font = [textStorage attribute:NSFontAttributeName atIndex:range.location effectiveRange:nil];
CGRect frame = {{glyphRect.origin.x,
glyphRect.origin.y + glyphRect.size.height - attachmentSize.height + font.descender},
attachmentSize};
auto rect = facebook::react::Rect{facebook::react::Point{frame.origin.x, frame.origin.y},
facebook::react::Size{frame.size.width, frame.size.height}};
attachments.push_back(TextMeasurement::Attachment{rect, false});
}];
return TextMeasurement{{size.width, size.height}, attachments};
}
- (facebook::react::Size)measureAttributedString:(AttributedString)attributedString
paragraphAttributes:(ParagraphAttributes)paragraphAttributes
layoutConstraints:(LayoutConstraints)layoutConstraints
- (TextMeasurement)measureAttributedString:(AttributedString)attributedString
paragraphAttributes:(ParagraphAttributes)paragraphAttributes
layoutConstraints:(LayoutConstraints)layoutConstraints
{
return [self measureNSAttributedString:[self _nsAttributedStringFromAttributedString:attributedString]
paragraphAttributes:paragraphAttributes
@@ -34,7 +34,7 @@ class TextLayoutManager {
/*
* Measures `attributedString` using native text rendering infrastructure.
*/
Size measure(
TextMeasurement measure(
AttributedStringBox attributedStringBox,
ParagraphAttributes paragraphAttributes,
LayoutConstraints layoutConstraints) const;
@@ -25,20 +25,20 @@ std::shared_ptr<void> TextLayoutManager::getNativeTextLayoutManager() const
return self_;
}
Size TextLayoutManager::measure(
TextMeasurement TextLayoutManager::measure(
AttributedStringBox attributedStringBox,
ParagraphAttributes paragraphAttributes,
LayoutConstraints layoutConstraints) const
{
RCTTextLayoutManager *textLayoutManager = (RCTTextLayoutManager *)unwrapManagedObject(self_);
auto size = Size{};
auto measurement = TextMeasurement{};
switch (attributedStringBox.getMode()) {
case AttributedStringBox::Mode::Value: {
auto &attributedString = attributedStringBox.getValue();
size = measureCache_.get(
measurement = measureCache_.get(
{attributedString, paragraphAttributes, layoutConstraints}, [&](TextMeasureCacheKey const &key) {
return [textLayoutManager measureAttributedString:attributedString
paragraphAttributes:paragraphAttributes
@@ -51,14 +51,16 @@ Size TextLayoutManager::measure(
NSAttributedString *nsAttributedString =
(NSAttributedString *)unwrapManagedObject(attributedStringBox.getOpaquePointer());
size = [textLayoutManager measureNSAttributedString:nsAttributedString
paragraphAttributes:paragraphAttributes
layoutConstraints:layoutConstraints];
measurement = [textLayoutManager measureNSAttributedString:nsAttributedString
paragraphAttributes:paragraphAttributes
layoutConstraints:layoutConstraints];
break;
}
}
return layoutConstraints.clamp(size);
measurement.size = layoutConstraints.clamp(measurement.size);
return measurement;
}
} // namespace react