1#include "T3SourceTextShadowNode.h"
2#include "T3SourceTextRunShadowNode.h"
3#include <react/renderer/components/view/ViewShadowNode.h>
4#import <react/renderer/textlayoutmanager/RCTAttributedTextUtils.h>
5
6#include <algorithm>
7#include <cmath>
8
9namespace facebook::react {
10
11T3SourceTextShadowNode::T3SourceTextShadowNode(
12 const ShadowNode &sourceShadowNode, const ShadowNodeFragment &fragment)
13 : ConcreteViewShadowNode(sourceShadowNode, fragment){};
14
15Size T3SourceTextShadowNode::measureContent(
16 const LayoutContext &layoutContext,
17 const LayoutConstraints &layoutConstraints) const {
18 const auto &baseProps = getConcreteProps();
19
20 auto baseTextAttributes = TextAttributes::defaultTextAttributes();
21 baseTextAttributes.backgroundColor = baseProps.backgroundColor;
22 baseTextAttributes.allowFontScaling = baseProps.allowFontScaling;
23
24 Float fontSizeMultiplier = 1.0;
25 if (baseTextAttributes.allowFontScaling) {
26 fontSizeMultiplier = layoutContext.fontSizeMultiplier;
27 }
28
29 auto baseAttributedString = AttributedString{};
30 const auto &children = getChildren();
31 for (size_t i = 0; i < children.size(); i++) {
32 const auto child = children[i].get();
33 if (auto textViewChild =
34 dynamic_cast<const T3SourceTextRunShadowNode *>(child)) {
35 auto &props = textViewChild->getConcreteProps();
36 auto fragment = AttributedString::Fragment{};
37 auto textAttributes = TextAttributes::defaultTextAttributes();
38
39 textAttributes.allowFontScaling = baseProps.allowFontScaling;
40 textAttributes.backgroundColor = props.backgroundColor;
41 textAttributes.fontSize = props.fontSize * fontSizeMultiplier;
42 textAttributes.lineHeight = props.lineHeight * fontSizeMultiplier;
43 textAttributes.foregroundColor = props.color;
44 textAttributes.textShadowColor = props.shadowColor;
45 textAttributes.textShadowOffset = props.shadowOffset;
46 textAttributes.textShadowRadius = props.shadowRadius;
47 textAttributes.letterSpacing = props.letterSpacing;
48 textAttributes.textDecorationColor = props.textDecorationColor;
49 textAttributes.fontFamily = props.fontFamily;
50
51 if (props.fontStyle == T3SourceTextRunFontStyle::Italic) {
52 textAttributes.fontStyle = FontStyle::Italic;
53 } else {
54 textAttributes.fontStyle = FontStyle::Normal;
55 }
56
57 if (props.fontWeight == T3SourceTextRunFontWeight::Bold) {
58 textAttributes.fontWeight = FontWeight::Bold;
59 } else if (props.fontWeight == T3SourceTextRunFontWeight::UltraLight) {
60 textAttributes.fontWeight = FontWeight::UltraLight;
61 } else if (props.fontWeight == T3SourceTextRunFontWeight::Light) {
62 textAttributes.fontWeight = FontWeight::Light;
63 } else if (props.fontWeight == T3SourceTextRunFontWeight::Medium) {
64 textAttributes.fontWeight = FontWeight::Medium;
65 } else if (props.fontWeight == T3SourceTextRunFontWeight::Semibold) {
66 textAttributes.fontWeight = FontWeight::Semibold;
67 } else if (props.fontWeight == T3SourceTextRunFontWeight::Heavy) {
68 textAttributes.fontWeight = FontWeight::Heavy;
69 } else {
70 textAttributes.fontWeight = FontWeight::Regular;
71 }
72
73 if (props.textDecorationLine ==
74 T3SourceTextRunTextDecorationLine::LineThrough) {
75 textAttributes.textDecorationLineType =
76 TextDecorationLineType::Strikethrough;
77 } else if (props.textDecorationLine ==
78 T3SourceTextRunTextDecorationLine::Underline) {
79 textAttributes.textDecorationLineType =
80 TextDecorationLineType::Underline;
81 } else {
82 textAttributes.textDecorationLineType = TextDecorationLineType::None;
83 }
84
85 if (props.textDecorationStyle ==
86 T3SourceTextRunTextDecorationStyle::Solid) {
87 textAttributes.textDecorationStyle = TextDecorationStyle::Solid;
88 } else if (props.textDecorationStyle ==
89 T3SourceTextRunTextDecorationStyle::Dotted) {
90 textAttributes.textDecorationStyle = TextDecorationStyle::Dotted;
91 } else if (props.textDecorationStyle ==
92 T3SourceTextRunTextDecorationStyle::Dashed) {
93 textAttributes.textDecorationStyle = TextDecorationStyle::Dashed;
94 } else if (props.textDecorationStyle ==
95 T3SourceTextRunTextDecorationStyle::Double) {
96 textAttributes.textDecorationStyle = TextDecorationStyle::Double;
97 }
98
99 if (props.textAlign == T3SourceTextRunTextAlign::Left) {
100 textAttributes.alignment = TextAlignment::Left;
101 } else if (props.textAlign == T3SourceTextRunTextAlign::Right) {
102 textAttributes.alignment = TextAlignment::Right;
103 } else if (props.textAlign == T3SourceTextRunTextAlign::Center) {
104 textAttributes.alignment = TextAlignment::Center;
105 } else if (props.textAlign == T3SourceTextRunTextAlign::Justify) {
106 textAttributes.alignment = TextAlignment::Justified;
107 } else if (props.textAlign == T3SourceTextRunTextAlign::Auto) {
108 textAttributes.alignment = TextAlignment::Natural;
109 }
110
111 textAttributes.backgroundColor = props.backgroundColor;
112
113 fragment.string = props.text;
114 fragment.textAttributes = textAttributes;
115
116 baseAttributedString.appendFragment(std::move(fragment));
117 }
118 }
119
120 _attributedString = baseAttributedString;
121
122 NSMutableAttributedString *convertedAttributedString =
123 [RCTNSAttributedStringFromAttributedString(baseAttributedString)
124 mutableCopy];
125 // Match React Native's baseline offset in both measurement and rendering.
126 RCTApplyBaselineOffset(convertedAttributedString);
127
128 const CGFloat maximumWidth =
129 std::isfinite(layoutConstraints.maximumSize.width)
130 ? layoutConstraints.maximumSize.width
131 : CGFLOAT_MAX;
132 NSTextStorage *textStorage = [[NSTextStorage alloc]
133 initWithAttributedString:convertedAttributedString];
134 NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
135 layoutManager.usesFontLeading = NO;
136 NSTextContainer *textContainer = [[NSTextContainer alloc]
137 initWithSize:CGSizeMake(maximumWidth, CGFLOAT_MAX)];
138 textContainer.lineFragmentPadding = 0;
139 textContainer.maximumNumberOfLines = baseProps.numberOfLines;
140 if (baseProps.ellipsizeMode == T3SourceTextEllipsizeMode::Head) {
141 textContainer.lineBreakMode = NSLineBreakByTruncatingHead;
142 } else if (baseProps.ellipsizeMode == T3SourceTextEllipsizeMode::Middle) {
143 textContainer.lineBreakMode = NSLineBreakByTruncatingMiddle;
144 } else if (baseProps.ellipsizeMode == T3SourceTextEllipsizeMode::Tail) {
145 textContainer.lineBreakMode = NSLineBreakByTruncatingTail;
146 } else {
147 textContainer.lineBreakMode = NSLineBreakByClipping;
148 }
149 [layoutManager addTextContainer:textContainer];
150 [textStorage addLayoutManager:layoutManager];
151 [layoutManager ensureLayoutForTextContainer:textContainer];
152 const CGRect usedRect =
153 [layoutManager usedRectForTextContainer:textContainer];
154
155 return {
156 std::clamp(static_cast<Float>(std::ceil(usedRect.size.width)),
157 layoutConstraints.minimumSize.width,
158 layoutConstraints.maximumSize.width),
159 std::clamp(static_cast<Float>(std::ceil(usedRect.size.height)),
160 layoutConstraints.minimumSize.height,
161 layoutConstraints.maximumSize.height),
162 };
163}
164
165void T3SourceTextShadowNode::layout(LayoutContext layoutContext) {
166 ensureUnsealed();
167 setStateData(T3SourceTextStateReal{
168 _attributedString,
169 });
170}
171} // namespace facebook::react