You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

438 lines
11 KiB

/**
* Copyright (c) 2015-present, Horcrux.
* All rights reserved.
*
* This source code is licensed under the MIT-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RNSVGSvgView.h"
#import <React/RCTLog.h>
#import "RNSVGNode.h"
#import "RNSVGViewBox.h"
#ifdef RCT_NEW_ARCH_ENABLED
#import <React/RCTConversions.h>
#import <React/RCTFabricComponentsPlugins.h>
#import <react/renderer/components/rnsvg/ComponentDescriptors.h>
#import <react/renderer/components/view/conversions.h>
#import "RNSVGFabricConversions.h"
#endif // RCT_NEW_ARCH_ENABLED
@implementation RNSVGSvgView {
NSMutableDictionary<NSString *, RNSVGNode *> *_clipPaths;
NSMutableDictionary<NSString *, RNSVGNode *> *_templates;
NSMutableDictionary<NSString *, RNSVGPainter *> *_painters;
NSMutableDictionary<NSString *, RNSVGNode *> *_markers;
NSMutableDictionary<NSString *, RNSVGNode *> *_masks;
CGAffineTransform _invviewBoxTransform;
bool rendered;
}
#ifdef RCT_NEW_ARCH_ENABLED
using namespace facebook::react;
#endif // RCT_NEW_ARCH_ENABLED
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
#if !TARGET_OS_OSX // Not available on macOS
// This is necessary to ensure that [self setNeedsDisplay] actually triggers
// a redraw when our parent transitions between hidden and visible.
self.contentMode = UIViewContentModeRedraw;
#endif // TARGET_OS_OSX
rendered = false;
#ifdef RCT_NEW_ARCH_ENABLED
static const auto defaultProps = std::make_shared<const RNSVGSvgViewProps>();
_props = defaultProps;
#if !TARGET_OS_OSX // On macOS, views are transparent by default
// TODO: think if we can do it better
self.opaque = NO;
#endif // TARGET_OS_OSX
#endif // RCT_NEW_ARCH_ENABLED
}
return self;
}
#ifdef RCT_NEW_ARCH_ENABLED
#pragma mark - RCTComponentViewProtocol
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<RNSVGSvgViewComponentDescriptor>();
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
const auto &newProps = *std::static_pointer_cast<const RNSVGSvgViewProps>(props);
self.minX = newProps.minX;
self.minY = newProps.minY;
self.vbWidth = newProps.vbWidth;
self.vbHeight = newProps.vbHeight;
self.bbWidth = [RNSVGLength lengthWithString:RCTNSStringFromString(newProps.bbWidth)];
self.bbHeight = [RNSVGLength lengthWithString:RCTNSStringFromString(newProps.bbHeight)];
self.align = RCTNSStringFromStringNilIfEmpty(newProps.align);
self.meetOrSlice = intToRNSVGVBMOS(newProps.meetOrSlice);
if (RCTUIColorFromSharedColor(newProps.tintColor)) {
self.tintColor = RCTUIColorFromSharedColor(newProps.tintColor);
}
if (RCTUIColorFromSharedColor(newProps.color)) {
self.tintColor = RCTUIColorFromSharedColor(newProps.color);
}
[super updateProps:props oldProps:oldProps];
}
- (void)prepareForRecycle
{
[super prepareForRecycle];
_minX = 0;
_minY = 0;
_vbWidth = 0;
_vbHeight = 0;
_bbWidth = 0;
_bbHeight = 0;
_align = nil;
_meetOrSlice = kRNSVGVBMOSMeet;
_responsible = NO;
_active = NO;
_boundingBox = CGRectZero;
_initialCTM = CGAffineTransformIdentity;
_invInitialCTM = CGAffineTransformIdentity;
_viewBoxTransform = CGAffineTransformIdentity;
_clipPaths = nil;
_templates = nil;
_painters = nil;
_markers = nil;
_masks = nil;
_invviewBoxTransform = CGAffineTransformIdentity;
rendered = NO;
}
#endif // RCT_NEW_ARCH_ENABLED
- (void)insertReactSubview:(RNSVGView *)subview atIndex:(NSInteger)atIndex
{
[super insertReactSubview:subview atIndex:atIndex];
[self insertSubview:subview atIndex:atIndex];
[self invalidate];
}
- (void)removeReactSubview:(RNSVGView *)subview
{
[super removeReactSubview:subview];
[self invalidate];
}
- (void)didUpdateReactSubviews
{
// Do nothing, as subviews are inserted by insertReactSubview:
}
- (void)clearChildCache
{
if (!rendered) {
return;
}
rendered = false;
for (__kindof RNSVGNode *node in self.subviews) {
if ([node isKindOfClass:[RNSVGNode class]]) {
[node clearChildCache];
}
}
}
- (void)invalidate
{
RNSVGPlatformView *parent = self.superview;
if ([parent isKindOfClass:[RNSVGNode class]]) {
if (!rendered) {
return;
}
RNSVGNode *svgNode = (RNSVGNode *)parent;
[svgNode invalidate];
rendered = false;
return;
}
[self setNeedsDisplay];
}
- (void)tintColorDidChange
{
[self invalidate];
[self clearChildCache];
}
- (void)setMinX:(CGFloat)minX
{
if (minX == _minX) {
return;
}
[self invalidate];
[self clearChildCache];
_minX = minX;
}
- (void)setMinY:(CGFloat)minY
{
if (minY == _minY) {
return;
}
[self invalidate];
[self clearChildCache];
_minY = minY;
}
- (void)setVbWidth:(CGFloat)vbWidth
{
if (vbWidth == _vbWidth) {
return;
}
[self invalidate];
[self clearChildCache];
_vbWidth = vbWidth;
}
- (void)setVbHeight:(CGFloat)vbHeight
{
if (_vbHeight == vbHeight) {
return;
}
[self invalidate];
[self clearChildCache];
_vbHeight = vbHeight;
}
- (void)setBbWidth:(RNSVGLength *)bbWidth
{
if ([bbWidth isEqualTo:_bbWidth]) {
return;
}
[self invalidate];
[self clearChildCache];
_bbWidth = bbWidth;
}
- (void)setBbHeight:(RNSVGLength *)bbHeight
{
if ([bbHeight isEqualTo:_bbHeight]) {
return;
}
[self invalidate];
[self clearChildCache];
_bbHeight = bbHeight;
}
- (void)setAlign:(NSString *)align
{
if ([align isEqualToString:_align]) {
return;
}
[self invalidate];
[self clearChildCache];
_align = align;
}
- (void)setMeetOrSlice:(RNSVGVBMOS)meetOrSlice
{
if (meetOrSlice == _meetOrSlice) {
return;
}
[self invalidate];
[self clearChildCache];
_meetOrSlice = meetOrSlice;
}
- (void)drawToContext:(CGContextRef)context withRect:(CGRect)rect
{
rendered = true;
_clipPaths = nil;
_templates = nil;
_painters = nil;
self.initialCTM = CGContextGetCTM(context);
self.invInitialCTM = CGAffineTransformInvert(self.initialCTM);
if (self.align) {
CGRect tRect = CGRectMake(self.minX, self.minY, self.vbWidth, self.vbHeight);
_viewBoxTransform = [RNSVGViewBox getTransform:tRect eRect:rect align:self.align meetOrSlice:self.meetOrSlice];
_invviewBoxTransform = CGAffineTransformInvert(_viewBoxTransform);
CGContextConcatCTM(context, _viewBoxTransform);
} else {
_viewBoxTransform = CGAffineTransformIdentity;
_invviewBoxTransform = CGAffineTransformIdentity;
}
for (RNSVGView *node in self.subviews) {
if ([node isKindOfClass:[RNSVGNode class]]) {
RNSVGNode *svg = (RNSVGNode *)node;
if (svg.responsible && !self.responsible) {
self.responsible = YES;
}
[svg parseReference];
[svg renderTo:context rect:rect];
} else {
[node drawRect:rect];
}
}
}
- (void)drawRect:(CGRect)rect
{
RNSVGPlatformView *parent = self.superview;
if ([parent isKindOfClass:[RNSVGNode class]]) {
return;
}
_boundingBox = rect;
CGContextRef context = UIGraphicsGetCurrentContext();
[self drawToContext:context withRect:rect];
}
- (RNSVGPlatformView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
CGPoint transformed = point;
if (self.align) {
transformed = CGPointApplyAffineTransform(transformed, _invviewBoxTransform);
}
for (RNSVGNode *node in [self.subviews reverseObjectEnumerator]) {
if (![node isKindOfClass:[RNSVGNode class]]) {
continue;
}
if (event) {
node.active = NO;
}
RNSVGPlatformView *hitChild = [node hitTest:transformed withEvent:event];
if (hitChild) {
node.active = YES;
return (node.responsible || (node != hitChild)) ? hitChild : self;
}
}
return nil;
}
- (NSString *)getDataURL
{
UIGraphicsBeginImageContextWithOptions(_boundingBox.size, NO, 0);
[self clearChildCache];
[self drawRect:_boundingBox];
[self clearChildCache];
[self invalidate];
NSData *imageData = UIImagePNGRepresentation(UIGraphicsGetImageFromCurrentImageContext());
NSString *base64 = [imageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
UIGraphicsEndImageContext();
return base64;
}
- (NSString *)getDataURLwithBounds:(CGRect)bounds
{
UIGraphicsBeginImageContextWithOptions(bounds.size, NO, 1);
[self clearChildCache];
[self drawRect:bounds];
[self clearChildCache];
[self invalidate];
NSData *imageData = UIImagePNGRepresentation(UIGraphicsGetImageFromCurrentImageContext());
NSString *base64 = [imageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
UIGraphicsEndImageContext();
return base64;
}
- (void)reactSetInheritedBackgroundColor:(RNSVGColor *)inheritedBackgroundColor
{
self.backgroundColor = inheritedBackgroundColor;
}
- (void)defineClipPath:(__kindof RNSVGNode *)clipPath clipPathName:(NSString *)clipPathName
{
if (!_clipPaths) {
_clipPaths = [[NSMutableDictionary alloc] init];
}
[_clipPaths setObject:clipPath forKey:clipPathName];
}
- (RNSVGNode *)getDefinedClipPath:(NSString *)clipPathName
{
return _clipPaths ? [_clipPaths objectForKey:clipPathName] : nil;
}
- (void)defineTemplate:(RNSVGNode *)definedTemplate templateName:(NSString *)templateName
{
if (!_templates) {
_templates = [[NSMutableDictionary alloc] init];
}
[_templates setObject:definedTemplate forKey:templateName];
}
- (RNSVGNode *)getDefinedTemplate:(NSString *)templateName
{
return _templates ? [_templates objectForKey:templateName] : nil;
}
- (void)definePainter:(RNSVGPainter *)painter painterName:(NSString *)painterName
{
if (!_painters) {
_painters = [[NSMutableDictionary alloc] init];
}
[_painters setObject:painter forKey:painterName];
}
- (RNSVGPainter *)getDefinedPainter:(NSString *)painterName;
{
return _painters ? [_painters objectForKey:painterName] : nil;
}
- (void)defineMarker:(RNSVGNode *)marker markerName:(NSString *)markerName
{
if (!_markers) {
_markers = [[NSMutableDictionary alloc] init];
}
[_markers setObject:marker forKey:markerName];
}
- (RNSVGNode *)getDefinedMarker:(NSString *)markerName;
{
return _markers ? [_markers objectForKey:markerName] : nil;
}
- (void)defineMask:(RNSVGNode *)mask maskName:(NSString *)maskName
{
if (!_masks) {
_masks = [[NSMutableDictionary alloc] init];
}
[_masks setObject:mask forKey:maskName];
}
- (RNSVGNode *)getDefinedMask:(NSString *)maskName;
{
return _masks ? [_masks objectForKey:maskName] : nil;
}
- (CGRect)getContextBounds
{
return CGContextGetClipBoundingBox(UIGraphicsGetCurrentContext());
}
- (CGAffineTransform)getViewBoxTransform
{
return _viewBoxTransform;
}
@end
#ifdef RCT_NEW_ARCH_ENABLED
Class<RCTComponentViewProtocol> RNSVGSvgViewCls(void)
{
return RNSVGSvgView.class;
}
#endif // RCT_NEW_ARCH_ENABLED