1/*
2 * Copyright (c) Meta Platforms, Inc. and 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#import <ABI48_0_0React/ABI48_0_0RCTBaseTextShadowView.h>
9
10#import <ABI48_0_0React/ABI48_0_0RCTShadowView+Layout.h>
11
12#import <ABI48_0_0React/ABI48_0_0RCTRawTextShadowView.h>
13#import <ABI48_0_0React/ABI48_0_0RCTVirtualTextShadowView.h>
14
15NSString *const ABI48_0_0RCTBaseTextShadowViewEmbeddedShadowViewAttributeName =
16    @"ABI48_0_0RCTBaseTextShadowViewEmbeddedShadowViewAttributeName";
17
18static void ABI48_0_0RCTInlineViewYogaNodeDirtied(ABI48_0_0YGNodeRef node)
19{
20  // An inline view (a view nested inside of a text node) does not have a parent
21  // in the Yoga tree. Consequently, we have to manually propagate the inline
22  // view's dirty signal up through the text nodes. At some point, it'll reach
23  // the outermost text node which has a Yoga node and then Yoga will take over
24  // the dirty signal propagation.
25  ABI48_0_0RCTShadowView *inlineView = (__bridge ABI48_0_0RCTShadowView *)ABI48_0_0YGNodeGetContext(node);
26  ABI48_0_0RCTBaseTextShadowView *baseTextShadowView = (ABI48_0_0RCTBaseTextShadowView *)inlineView.ABI48_0_0ReactSuperview;
27
28  [baseTextShadowView dirtyLayout];
29}
30
31@implementation ABI48_0_0RCTBaseTextShadowView
32
33- (instancetype)init
34{
35  if (self = [super init]) {
36    _textAttributes = [ABI48_0_0RCTTextAttributes new];
37  }
38
39  return self;
40}
41
42- (void)setABI48_0_0ReactTag:(NSNumber *)ABI48_0_0ReactTag
43{
44  [super setABI48_0_0ReactTag:ABI48_0_0ReactTag];
45  _textAttributes.tag = ABI48_0_0ReactTag;
46}
47
48#pragma mark - Life Cycle
49
50- (void)insertABI48_0_0ReactSubview:(ABI48_0_0RCTShadowView *)subview atIndex:(NSInteger)index
51{
52  [super insertABI48_0_0ReactSubview:subview atIndex:index];
53
54  [self dirtyLayout];
55
56  if (![subview isKindOfClass:[ABI48_0_0RCTVirtualTextShadowView class]]) {
57    ABI48_0_0YGNodeSetDirtiedFunc(subview.yogaNode, ABI48_0_0RCTInlineViewYogaNodeDirtied);
58  }
59}
60
61- (void)removeABI48_0_0ReactSubview:(ABI48_0_0RCTShadowView *)subview
62{
63  if (![subview isKindOfClass:[ABI48_0_0RCTVirtualTextShadowView class]]) {
64    ABI48_0_0YGNodeSetDirtiedFunc(subview.yogaNode, NULL);
65  }
66
67  [self dirtyLayout];
68
69  [super removeABI48_0_0ReactSubview:subview];
70}
71
72#pragma mark - attributedString
73
74- (NSAttributedString *)attributedTextWithBaseTextAttributes:(nullable ABI48_0_0RCTTextAttributes *)baseTextAttributes
75{
76  ABI48_0_0RCTTextAttributes *textAttributes;
77
78  if (baseTextAttributes) {
79    textAttributes = [baseTextAttributes copy];
80    [textAttributes applyTextAttributes:self.textAttributes];
81  } else {
82    textAttributes = [self.textAttributes copy];
83  }
84
85  if (cachedAttributedText && [cachedTextAttributes isEqual:textAttributes]) {
86    return cachedAttributedText;
87  }
88
89  NSMutableAttributedString *attributedText = [NSMutableAttributedString new];
90
91  [attributedText beginEditing];
92
93  for (ABI48_0_0RCTShadowView *shadowView in self.ABI48_0_0ReactSubviews) {
94    // Special Case: ABI48_0_0RCTRawTextShadowView
95    if ([shadowView isKindOfClass:[ABI48_0_0RCTRawTextShadowView class]]) {
96      ABI48_0_0RCTRawTextShadowView *rawTextShadowView = (ABI48_0_0RCTRawTextShadowView *)shadowView;
97      NSString *text = rawTextShadowView.text;
98      if (text) {
99        NSAttributedString *rawTextAttributedString =
100            [[NSAttributedString alloc] initWithString:[textAttributes applyTextAttributesToText:text]
101                                            attributes:textAttributes.effectiveTextAttributes];
102        [attributedText appendAttributedString:rawTextAttributedString];
103      }
104      continue;
105    }
106
107    // Special Case: ABI48_0_0RCTBaseTextShadowView
108    if ([shadowView isKindOfClass:[ABI48_0_0RCTBaseTextShadowView class]]) {
109      ABI48_0_0RCTBaseTextShadowView *baseTextShadowView = (ABI48_0_0RCTBaseTextShadowView *)shadowView;
110      NSAttributedString *baseTextAttributedString =
111          [baseTextShadowView attributedTextWithBaseTextAttributes:textAttributes];
112      [attributedText appendAttributedString:baseTextAttributedString];
113      continue;
114    }
115
116    // Generic Case: Any ABI48_0_0RCTShadowView
117    NSTextAttachment *attachment = [NSTextAttachment new];
118    NSMutableAttributedString *embeddedShadowViewAttributedString = [NSMutableAttributedString new];
119    [embeddedShadowViewAttributedString beginEditing];
120    [embeddedShadowViewAttributedString
121        appendAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]];
122    [embeddedShadowViewAttributedString addAttribute:ABI48_0_0RCTBaseTextShadowViewEmbeddedShadowViewAttributeName
123                                               value:shadowView
124                                               range:(NSRange){0, embeddedShadowViewAttributedString.length}];
125    [embeddedShadowViewAttributedString endEditing];
126    [attributedText appendAttributedString:embeddedShadowViewAttributedString];
127  }
128
129  [attributedText endEditing];
130
131  [self clearLayout];
132
133  cachedAttributedText = [attributedText copy];
134  cachedTextAttributes = textAttributes;
135
136  return cachedAttributedText;
137}
138
139- (void)dirtyLayout
140{
141  [super dirtyLayout];
142  cachedAttributedText = nil;
143  cachedTextAttributes = nil;
144}
145
146- (void)didUpdateABI48_0_0ReactSubviews
147{
148  [super didUpdateABI48_0_0ReactSubviews];
149  [self dirtyLayout];
150}
151
152- (void)didSetProps:(NSArray<NSString *> *)changedProps
153{
154  [super didSetProps:changedProps];
155  [self dirtyLayout];
156}
157
158@end
159