Skip to content

Commit 8d6b41e

Browse files
sammy-SCfacebook-github-bot
authored andcommittedAug 10, 2020
Add support for onTextLayout in Text
Summary: Changelog: [Internal] Add `Text.onTextLayout` implementation Reviewed By: JoshuaGross Differential Revision: D22865139 fbshipit-source-id: 563084754ebdc9fb23463a306c526b97c61f85ec
1 parent 43b2c6d commit 8d6b41e

13 files changed

+190
-16
lines changed
 

‎ReactCommon/react/renderer/components/scrollview/ScrollViewEventEmitter.h

-5
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,6 @@ class ScrollViewMetrics {
2626
Float zoomScale;
2727
};
2828

29-
class ScrollViewEventEmitter;
30-
31-
using SharedScrollViewEventEmitter =
32-
std::shared_ptr<const ScrollViewEventEmitter>;
33-
3429
class ScrollViewEventEmitter : public ViewEventEmitter {
3530
public:
3631
using ViewEventEmitter::ViewEventEmitter;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#include "ParagraphEventEmitter.h"
9+
10+
namespace facebook {
11+
namespace react {
12+
13+
static jsi::Value linesMeasurementsPayload(
14+
jsi::Runtime &runtime,
15+
LinesMeasurements const &linesMeasurements) {
16+
auto payload = jsi::Object(runtime);
17+
auto lines = jsi::Array(runtime, linesMeasurements.size());
18+
19+
for (size_t i = 0; i < linesMeasurements.size(); ++i) {
20+
auto const &lineMeasurement = linesMeasurements[i];
21+
auto jsiLine = jsi::Object(runtime);
22+
jsiLine.setProperty(runtime, "text", lineMeasurement.text);
23+
jsiLine.setProperty(runtime, "x", lineMeasurement.frame.origin.x);
24+
jsiLine.setProperty(runtime, "y", lineMeasurement.frame.origin.y);
25+
jsiLine.setProperty(runtime, "width", lineMeasurement.frame.size.width);
26+
jsiLine.setProperty(runtime, "height", lineMeasurement.frame.size.height);
27+
jsiLine.setProperty(runtime, "descender", lineMeasurement.descender);
28+
jsiLine.setProperty(runtime, "capHeight", lineMeasurement.capHeight);
29+
jsiLine.setProperty(runtime, "ascender", lineMeasurement.ascender);
30+
jsiLine.setProperty(runtime, "xHeight", lineMeasurement.xHeight);
31+
lines.setValueAtIndex(runtime, i, jsiLine);
32+
}
33+
34+
payload.setProperty(runtime, "lines", lines);
35+
36+
return payload;
37+
}
38+
39+
void ParagraphEventEmitter::onTextLayout(
40+
LinesMeasurements const &linesMeasurements) const {
41+
dispatchEvent(
42+
"textLayout",
43+
[linesMeasurements](jsi::Runtime &runtime) {
44+
return linesMeasurementsPayload(runtime, linesMeasurements);
45+
},
46+
EventPriority::AsynchronousBatched);
47+
}
48+
49+
} // namespace react
50+
} // namespace facebook
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include <react/renderer/components/view/ViewEventEmitter.h>
11+
#include <react/renderer/textlayoutmanager/TextMeasureCache.h>
12+
13+
namespace facebook {
14+
namespace react {
15+
16+
class ParagraphEventEmitter : public ViewEventEmitter {
17+
public:
18+
using ViewEventEmitter::ViewEventEmitter;
19+
20+
void onTextLayout(LinesMeasurements const &linesMeasurements) const;
21+
};
22+
23+
} // namespace react
24+
} // namespace facebook

‎ReactCommon/react/renderer/components/text/paragraph/ParagraphProps.cpp

+5-3
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@ ParagraphProps::ParagraphProps(
2424
BaseTextProps(sourceProps, rawProps),
2525
paragraphAttributes(
2626
convertRawProp(rawProps, sourceProps.paragraphAttributes, {})),
27-
isSelectable(convertRawProp(
27+
isSelectable(
28+
convertRawProp(rawProps, "selectable", sourceProps.isSelectable, {})),
29+
onTextLayout(convertRawProp(
2830
rawProps,
29-
"selectable",
30-
sourceProps.isSelectable,
31+
"onTextLayout",
32+
sourceProps.onTextLayout,
3133
{})) {
3234
/*
3335
* These props are applied to `View`, therefore they must not be a part of

‎ReactCommon/react/renderer/components/text/paragraph/ParagraphProps.h

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ class ParagraphProps : public ViewProps, public BaseTextProps {
4141
*/
4242
bool const isSelectable{};
4343

44+
bool const onTextLayout{};
45+
4446
#pragma mark - DebugStringConvertible
4547

4648
#if RN_DEBUG_STRING_CONVERTIBLE

‎ReactCommon/react/renderer/components/text/paragraph/ParagraphShadowNode.cpp

+15-5
Original file line numberDiff line numberDiff line change
@@ -158,16 +158,26 @@ void ParagraphShadowNode::layout(LayoutContext layoutContext) {
158158

159159
updateStateIfNeeded(content);
160160

161-
if (content.attachments.empty()) {
162-
// No attachments, nothing to layout.
163-
return;
164-
}
165-
166161
auto measurement = textLayoutManager_->measure(
167162
AttributedStringBox{content.attributedString},
168163
content.paragraphAttributes,
169164
layoutConstraints);
170165

166+
#ifndef ANDROID
167+
if (getConcreteProps().onTextLayout) {
168+
auto linesMeasurements = textLayoutManager_->measureLines(
169+
content.attributedString,
170+
content.paragraphAttributes,
171+
measurement.size);
172+
getConcreteEventEmitter().onTextLayout(linesMeasurements);
173+
}
174+
#endif
175+
176+
if (content.attachments.empty()) {
177+
// No attachments, nothing to layout.
178+
return;
179+
}
180+
171181
// Iterating on attachments, we clone shadow nodes and moving
172182
// `paragraphShadowNode` that represents clones of `this` object.
173183
auto paragraphShadowNode = static_cast<ParagraphShadowNode *>(this);

‎ReactCommon/react/renderer/components/text/paragraph/ParagraphShadowNode.h

+1-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#pragma once
99

1010
#include <folly/Optional.h>
11+
#include <react/renderer/components/text/ParagraphEventEmitter.h>
1112
#include <react/renderer/components/text/ParagraphProps.h>
1213
#include <react/renderer/components/text/ParagraphState.h>
1314
#include <react/renderer/components/text/TextShadowNode.h>
@@ -22,8 +23,6 @@ namespace react {
2223

2324
extern char const ParagraphComponentName[];
2425

25-
using ParagraphEventEmitter = ViewEventEmitter;
26-
2726
/*
2827
* `ShadowNode` for <Paragraph> component, represents <View>-like component
2928
* containing and displaying text. Text content is represented as nested <Text>

‎ReactCommon/react/renderer/core/ConcreteShadowNode.h

+13
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,19 @@ class ConcreteShadowNode : public BaseShadowNodeT {
9999
return static_cast<ConcreteProps const &>(*props_);
100100
}
101101

102+
/*
103+
* Returns a concrete event emitter object associated with the node.
104+
* Thread-safe after the node is sealed.
105+
*/
106+
ConcreteEventEmitter const &getConcreteEventEmitter() const {
107+
assert(
108+
std::dynamic_pointer_cast<ConcreteEventEmitter const>(
109+
BaseShadowNodeT::getEventEmitter()) &&
110+
"EventEmitter must be an instance of ConcreteEventEmitter class.");
111+
return static_cast<ConcreteEventEmitter const &>(
112+
*BaseShadowNodeT::getEventEmitter());
113+
}
114+
102115
/*
103116
* Returns a concrete state data associated with the node.
104117
* Thread-safe after the node is sealed.

‎ReactCommon/react/renderer/textlayoutmanager/TextMeasureCache.h

+11
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@
1616
namespace facebook {
1717
namespace react {
1818

19+
struct LineMeasurement {
20+
std::string text;
21+
Rect frame;
22+
Float descender;
23+
Float capHeight;
24+
Float ascender;
25+
Float xHeight;
26+
};
27+
28+
using LinesMeasurements = std::vector<LineMeasurement>;
29+
1930
/*
2031
* Describes a result of text measuring.
2132
*/

‎ReactCommon/react/renderer/textlayoutmanager/platform/ios/RCTTextLayoutManager.h

+5
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ using RCTTextLayoutFragmentEnumerationBlock =
3939
paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes
4040
frame:(CGRect)frame;
4141

42+
- (facebook::react::LinesMeasurements)getLinesForAttributedString:(facebook::react::AttributedString)attributedString
43+
paragraphAttributes:
44+
(facebook::react::ParagraphAttributes)paragraphAttributes
45+
size:(CGSize)size;
46+
4247
- (facebook::react::SharedEventEmitter)
4348
getEventEmitterWithAttributeString:(facebook::react::AttributedString)attributedString
4449
paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes

‎ReactCommon/react/renderer/textlayoutmanager/platform/ios/RCTTextLayoutManager.mm

+44
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,50 @@ - (void)drawAttributedString:(AttributedString)attributedString
113113
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:frame.origin];
114114
}
115115

116+
- (LinesMeasurements)getLinesForAttributedString:(facebook::react::AttributedString)attributedString
117+
paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes
118+
size:(CGSize)size
119+
{
120+
NSTextStorage *textStorage = [self
121+
_textStorageAndLayoutManagerWithAttributesString:[self _nsAttributedStringFromAttributedString:attributedString]
122+
paragraphAttributes:paragraphAttributes
123+
size:size];
124+
NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject;
125+
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
126+
127+
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
128+
129+
std::vector<LineMeasurement> paragraphLines{};
130+
auto blockParagraphLines = &paragraphLines;
131+
132+
[layoutManager enumerateLineFragmentsForGlyphRange:glyphRange
133+
usingBlock:^(
134+
CGRect overallRect,
135+
CGRect usedRect,
136+
NSTextContainer *_Nonnull usedTextContainer,
137+
NSRange lineGlyphRange,
138+
BOOL *_Nonnull stop) {
139+
NSRange range = [layoutManager characterRangeForGlyphRange:lineGlyphRange
140+
actualGlyphRange:nil];
141+
NSString *renderedString = [textStorage.string substringWithRange:range];
142+
UIFont *font = [[textStorage attributedSubstringFromRange:range]
143+
attribute:NSFontAttributeName
144+
atIndex:0
145+
effectiveRange:nil];
146+
auto rect = facebook::react::Rect{
147+
facebook::react::Point{usedRect.origin.x, usedRect.origin.y},
148+
facebook::react::Size{usedRect.size.width, usedRect.size.height}};
149+
auto line = LineMeasurement{std::string([renderedString UTF8String]),
150+
rect,
151+
-font.descender,
152+
font.capHeight,
153+
font.ascender,
154+
font.xHeight};
155+
blockParagraphLines->push_back(line);
156+
}];
157+
return paragraphLines;
158+
}
159+
116160
- (NSTextStorage *)_textStorageAndLayoutManagerWithAttributesString:(NSAttributedString *)attributedString
117161
paragraphAttributes:(ParagraphAttributes)paragraphAttributes
118162
size:(CGSize)size

‎ReactCommon/react/renderer/textlayoutmanager/platform/ios/TextLayoutManager.h

+9
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ class TextLayoutManager {
3939
ParagraphAttributes paragraphAttributes,
4040
LayoutConstraints layoutConstraints) const;
4141

42+
/*
43+
* Measures lines of `attributedString` using native text rendering
44+
* infrastructure.
45+
*/
46+
LinesMeasurements measureLines(
47+
AttributedString attributedString,
48+
ParagraphAttributes paragraphAttributes,
49+
Size size) const;
50+
4251
/*
4352
* Returns an opaque pointer to platform-specific TextLayoutManager.
4453
* Is used on a native views layer to delegate text rendering to the manager.

‎ReactCommon/react/renderer/textlayoutmanager/platform/ios/TextLayoutManager.mm

+11-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
*/
77

88
#include "TextLayoutManager.h"
9-
109
#include <react/utils/ManagedObjectWrapper.h>
1110

1211
#import "RCTTextLayoutManager.h"
@@ -63,5 +62,16 @@
6362
return measurement;
6463
}
6564

65+
LinesMeasurements TextLayoutManager::measureLines(
66+
AttributedString attributedString,
67+
ParagraphAttributes paragraphAttributes,
68+
Size size) const
69+
{
70+
RCTTextLayoutManager *textLayoutManager = (RCTTextLayoutManager *)unwrapManagedObject(self_);
71+
return [textLayoutManager getLinesForAttributedString:attributedString
72+
paragraphAttributes:paragraphAttributes
73+
size:{size.width, size.height}];
74+
}
75+
6676
} // namespace react
6777
} // namespace facebook

0 commit comments

Comments
 (0)
Please sign in to comment.