1/**
2 * Copyright (c) 2015-present, Horcrux.
3 * All rights reserved.
4 *
5 * This source code is licensed under the MIT-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9#import "RNSVGImage.h"
10#import "RCTConvert+RNSVG.h"
11
12#if __has_include(<React/RCTImageLoader.h>)
13
14#import <React/RCTImageLoader.h>
15
16#else
17
18#import <React/RCTImageLoaderProtocol.h>
19#import <React/RCTImageShadowView.h>
20#import <React/RCTImageURLLoader.h>
21#import <React/RCTImageView.h>
22
23#endif // RCT_NEW_ARCH_ENABLED
24
25#import <React/RCTBridge.h>
26#import <React/RCTLog.h>
27#import "RNSVGViewBox.h"
28
29#ifdef RCT_NEW_ARCH_ENABLED
30#import <React/RCTConversions.h>
31#import <React/RCTFabricComponentsPlugins.h>
32#import <React/RCTImageResponseObserverProxy.h>
33#import <React/RCTImageSource.h>
34#import <react/renderer/components/rnsvg/ComponentDescriptors.h>
35#import <react/renderer/components/view/conversions.h>
36#import <react/renderer/imagemanager/RCTImagePrimitivesConversions.h>
37#import <rnsvg/RNSVGImageComponentDescriptor.h>
38#import "RNSVGFabricConversions.h"
39
40using namespace facebook::react;
41#endif // RCT_NEW_ARCH_ENABLED
42
43@implementation RNSVGImage {
44  CGImageRef _image;
45  CGSize _imageSize;
46  RCTImageLoaderCancellationBlock _reloadImageCancellationBlock;
47
48#ifdef RCT_NEW_ARCH_ENABLED
49  RNSVGImageShadowNode::ConcreteState::Shared _state;
50  RCTImageResponseObserverProxy _imageResponseObserverProxy;
51#endif // RCT_NEW_ARCH_ENABLED
52}
53#ifdef RCT_NEW_ARCH_ENABLED
54
55- (instancetype)initWithFrame:(CGRect)frame
56{
57  if (self = [super initWithFrame:frame]) {
58    static const auto defaultProps = std::make_shared<const RNSVGImageProps>();
59    _props = defaultProps;
60
61    _imageResponseObserverProxy = RCTImageResponseObserverProxy(self);
62  }
63  return self;
64}
65
66#pragma mark - RCTComponentViewProtocol
67
68+ (ComponentDescriptorProvider)componentDescriptorProvider
69{
70  return concreteComponentDescriptorProvider<RNSVGImageComponentDescriptor>();
71}
72
73- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
74{
75  const auto &newProps = *std::static_pointer_cast<const RNSVGImageProps>(props);
76
77  self.x = [RNSVGLength lengthWithString:RCTNSStringFromString(newProps.x)];
78  self.y = [RNSVGLength lengthWithString:RCTNSStringFromString(newProps.y)];
79  if (RCTNSStringFromStringNilIfEmpty(newProps.height)) {
80    self.imageheight = [RNSVGLength lengthWithString:RCTNSStringFromString(newProps.height)];
81  }
82  if (RCTNSStringFromStringNilIfEmpty(newProps.width)) {
83    self.imagewidth = [RNSVGLength lengthWithString:RCTNSStringFromString(newProps.width)];
84  }
85  self.align = RCTNSStringFromStringNilIfEmpty(newProps.align);
86  self.meetOrSlice = intToRNSVGVBMOS(newProps.meetOrSlice);
87
88  setCommonRenderableProps(newProps, self);
89  _props = std::static_pointer_cast<RNSVGImageProps const>(props);
90}
91
92- (void)updateState:(State::Shared const &)state oldState:(State::Shared const &)oldState
93{
94  RCTAssert(state, @"`state` must not be null.");
95  RCTAssert(
96      std::dynamic_pointer_cast<RNSVGImageShadowNode::ConcreteState const>(state),
97      @"`state` must be a pointer to `RNSVGImageShadowNode::ConcreteState`.");
98
99  auto oldImageState = std::static_pointer_cast<RNSVGImageShadowNode::ConcreteState const>(_state);
100  auto newImageState = std::static_pointer_cast<RNSVGImageShadowNode::ConcreteState const>(state);
101
102  [self _setStateAndResubscribeImageResponseObserver:newImageState];
103}
104
105- (void)_setStateAndResubscribeImageResponseObserver:(RNSVGImageShadowNode::ConcreteState::Shared const &)state
106{
107  if (_state) {
108    auto &observerCoordinator = _state->getData().getImageRequest().getObserverCoordinator();
109    observerCoordinator.removeObserver(_imageResponseObserverProxy);
110  }
111
112  _state = state;
113
114  if (_state) {
115    auto &observerCoordinator = _state->getData().getImageRequest().getObserverCoordinator();
116    observerCoordinator.addObserver(_imageResponseObserverProxy);
117  }
118}
119
120#pragma mark - RCTImageResponseDelegate
121
122- (void)didReceiveImage:(UIImage *)image metadata:(id)metadata fromObserver:(void const *)observer
123{
124  if (!_eventEmitter || !_state) {
125    // Notifications are delivered asynchronously and might arrive after the view is already recycled.
126    // In the future, we should incorporate an `EventEmitter` into a separate object owned by `ImageRequest` or `State`.
127    // See for more info: T46311063.
128    return;
129  }
130  dispatch_async(dispatch_get_main_queue(), ^{
131    self->_image = CGImageRetain(image.CGImage);
132    self->_imageSize = CGSizeMake(CGImageGetWidth(self->_image), CGImageGetHeight(self->_image));
133    [self invalidate];
134  });
135}
136
137- (void)didReceiveProgress:(float)progress fromObserver:(void const *)observer
138{
139}
140
141- (void)didReceiveFailureFromObserver:(void const *)observer
142{
143  if (_image) {
144    CGImageRelease(_image);
145  }
146  _image = nil;
147}
148
149- (void)prepareForRecycle
150{
151  [super prepareForRecycle];
152  [self _setStateAndResubscribeImageResponseObserver:nullptr];
153
154  _x = nil;
155  _y = nil;
156  _imageheight = nil;
157  _imagewidth = nil;
158  _src = nil;
159  _align = nil;
160  _meetOrSlice = kRNSVGVBMOSMeet;
161
162  if (_image) {
163    CGImageRelease(_image);
164  }
165  _image = nil;
166  _imageSize = CGSizeZero;
167  _reloadImageCancellationBlock = nil;
168}
169#endif // RCT_NEW_ARCH_ENABLED
170
171- (void)setSrc:(RCTImageSource *)src
172{
173#ifdef RCT_NEW_ARCH_ENABLED
174#else
175  if (src == _src) {
176    return;
177  }
178  _src = src;
179  CGImageRelease(_image);
180  _image = nil;
181  if (src.size.width != 0 && src.size.height != 0) {
182    _imageSize = src.size;
183  } else {
184    _imageSize = CGSizeMake(0, 0);
185  }
186
187  RCTImageLoaderCancellationBlock previousCancellationBlock = _reloadImageCancellationBlock;
188  if (previousCancellationBlock) {
189    previousCancellationBlock();
190    _reloadImageCancellationBlock = nil;
191  }
192
193  _reloadImageCancellationBlock = [[self.bridge moduleForName:@"ImageLoader"]
194      loadImageWithURLRequest:src.request
195                     callback:^(NSError *error, UIImage *image) {
196                       dispatch_async(dispatch_get_main_queue(), ^{
197                         self->_image = CGImageRetain(image.CGImage);
198                         self->_imageSize = CGSizeMake(CGImageGetWidth(self->_image), CGImageGetHeight(self->_image));
199                         [self invalidate];
200                       });
201                     }];
202#endif // RCT_NEW_ARCH_ENABLED
203}
204
205- (void)setX:(RNSVGLength *)x
206{
207  if ([x isEqualTo:_x]) {
208    return;
209  }
210  [self invalidate];
211  _x = x;
212}
213
214- (void)setY:(RNSVGLength *)y
215{
216  if ([y isEqualTo:_y]) {
217    return;
218  }
219  [self invalidate];
220  _y = y;
221}
222
223- (void)setImagewidth:(RNSVGLength *)width
224{
225  if ([width isEqualTo:_imagewidth]) {
226    return;
227  }
228  [self invalidate];
229  _imagewidth = width;
230}
231
232- (void)setImageheight:(RNSVGLength *)height
233{
234  if ([height isEqualTo:_imageheight]) {
235    return;
236  }
237  [self invalidate];
238  _imageheight = height;
239}
240
241- (void)setAlign:(NSString *)align
242{
243  if ([align isEqualToString:_align]) {
244    return;
245  }
246  [self invalidate];
247  _align = align;
248}
249
250- (void)setMeetOrSlice:(RNSVGVBMOS)meetOrSlice
251{
252  if (meetOrSlice == _meetOrSlice) {
253    return;
254  }
255  [self invalidate];
256  _meetOrSlice = meetOrSlice;
257}
258
259- (void)dealloc
260{
261  CGImageRelease(_image);
262}
263
264- (void)renderLayerTo:(CGContextRef)context rect:(CGRect)rect
265{
266  if (CGSizeEqualToSize(CGSizeZero, _imageSize)) {
267    return;
268  }
269  CGContextSaveGState(context);
270
271  // add hit area
272  CGRect hitArea = [self getHitArea];
273  CGPathRef hitAreaPath = CGPathCreateWithRect(hitArea, nil);
274  [self setHitArea:hitAreaPath];
275  CGPathRelease(hitAreaPath);
276  self.pathBounds = hitArea;
277  self.fillBounds = hitArea;
278  self.strokeBounds = hitArea;
279
280  // apply viewBox transform on Image render.
281  CGRect imageBounds = CGRectMake(0, 0, _imageSize.width, _imageSize.height);
282  CGAffineTransform viewbox = [RNSVGViewBox getTransform:imageBounds
283                                                   eRect:hitArea
284                                                   align:self.align
285                                             meetOrSlice:self.meetOrSlice];
286
287  [self clip:context];
288  CGContextClipToRect(context, hitArea);
289  CGContextConcatCTM(context, viewbox);
290  CGContextTranslateCTM(context, 0, imageBounds.size.height);
291  CGContextScaleCTM(context, 1, -1);
292  CGContextDrawImage(context, imageBounds, _image);
293  CGContextRestoreGState(context);
294
295  CGRect bounds = hitArea;
296  self.clientRect = bounds;
297
298  CGAffineTransform current = CGContextGetCTM(context);
299  CGAffineTransform svgToClientTransform = CGAffineTransformConcat(current, self.svgView.invInitialCTM);
300
301  self.ctm = svgToClientTransform;
302  self.screenCTM = current;
303
304  CGAffineTransform transform = CGAffineTransformConcat(self.matrix, self.transforms);
305  CGPoint mid = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
306  CGPoint center = CGPointApplyAffineTransform(mid, transform);
307
308  self.bounds = bounds;
309  if (!isnan(center.x) && !isnan(center.y)) {
310    self.center = center;
311  }
312  self.frame = bounds;
313}
314
315- (CGRect)getHitArea
316{
317  CGFloat x = [self relativeOnWidth:self.x];
318  CGFloat y = [self relativeOnHeight:self.y];
319  CGFloat width = [self relativeOnWidth:self.imagewidth];
320  CGFloat height = [self relativeOnHeight:self.imageheight];
321  if (width == 0) {
322    width = _imageSize.width;
323  }
324  if (height == 0) {
325    height = _imageSize.height;
326  }
327
328  return CGRectMake(x, y, width, height);
329}
330
331- (CGPathRef)getPath:(CGContextRef)context
332{
333  return (CGPathRef)CFAutorelease(CGPathCreateWithRect([self getHitArea], nil));
334}
335
336@end
337
338#ifdef RCT_NEW_ARCH_ENABLED
339Class<RCTComponentViewProtocol> RNSVGImageCls(void)
340{
341  return RNSVGImage.class;
342}
343#endif // RCT_NEW_ARCH_ENABLED
344