Skip to content

Commit b7d375b

Browse files
committed
fix: simplify the svg decoder
1 parent 12c1972 commit b7d375b

File tree

3 files changed

+116
-194
lines changed

3 files changed

+116
-194
lines changed

packages/react-native-bottom-tabs/ios/SVG/CoreSVG.h

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1-
#if !TARGET_OS_OSX
2-
1+
#if TARGET_OS_OSX
2+
#import <AppKit/AppKit.h>
3+
typedef NSImage PlatformImage;
4+
#else
35
#import <UIKit/UIKit.h>
6+
typedef UIImage PlatformImage;
7+
#endif
8+
9+
410

511
@interface CoreSVGWrapper : NSObject
612

7-
+ (instancetype)sharedWrapper;
13+
+ (instancetype)shared;
814

9-
- (UIImage *)imageFromSVGData:(NSData *)data;
10-
- (UIImage *)imageFromSVGData:(NSData *)data targetSize:(CGSize)targetSize;
11-
- (UIImage *)imageFromSVGData:(NSData *)data targetSize:(CGSize)targetSize preserveAspectRatio:(BOOL)preserveAspectRatio;
15+
- (PlatformImage *)imageFromSVGData:(NSData *)data;
1216

13-
- (BOOL)isSVGData:(NSData *)data;
14-
+ (BOOL)supportsVectorSVG;
17+
+ (BOOL)isSVGData:(NSData *)data;
18+
+ (BOOL)supportsVectorSVGImage;
1519

1620
@end
17-
18-
#endif

packages/react-native-bottom-tabs/ios/SVG/CoreSVG.mm

Lines changed: 102 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
#import <dlfcn.h>
55
#import <objc/runtime.h>
66

7+
8+
9+
710
#define kSVGTagEnd @"</svg>"
811

912
typedef struct CF_BRIDGED_TYPE(id) CGSVGDocument *CGSVGDocumentRef;
@@ -16,191 +19,131 @@
1619

1720
#if TARGET_OS_IOS || TARGET_OS_WATCH
1821
static SEL CoreSVGImageWithDocumentSEL = NULL;
22+
static SEL CoreSVGDocumentSEL = NULL;
23+
#endif
24+
#if TARGET_OS_OSX
25+
static Class CoreSVGImageRepClass = NULL;
26+
static Ivar CoreSVGImageRepDocumentIvar = NULL;
1927
#endif
2028

2129
static inline NSString *Base64DecodedString(NSString *base64String) {
22-
NSData *data = [[NSData alloc] initWithBase64EncodedString:base64String options:NSDataBase64DecodingIgnoreUnknownCharacters];
23-
if (!data) {
24-
return nil;
25-
}
26-
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
30+
NSData *data = [[NSData alloc] initWithBase64EncodedString:base64String options:NSDataBase64DecodingIgnoreUnknownCharacters];
31+
if (!data) {
32+
return nil;
33+
}
34+
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
2735
}
2836

2937
@implementation CoreSVGWrapper
3038

31-
+ (instancetype)sharedWrapper {
32-
static dispatch_once_t onceToken;
33-
static CoreSVGWrapper *wrapper;
34-
dispatch_once(&onceToken, ^{
35-
wrapper = [[CoreSVGWrapper alloc] init];
36-
});
37-
return wrapper;
39+
+ (instancetype)shared {
40+
static dispatch_once_t onceToken;
41+
static CoreSVGWrapper *wrapper;
42+
dispatch_once(&onceToken, ^{
43+
wrapper = [[CoreSVGWrapper alloc] init];
44+
});
45+
return wrapper;
3846
}
3947

4048
+ (void)initialize {
41-
CoreSVGDocumentRetain = (CGSVGDocumentRef (*)(CGSVGDocumentRef))dlsym(RTLD_DEFAULT, Base64DecodedString(@"Q0dTVkdEb2N1bWVudFJldGFpbg==").UTF8String);
42-
CoreSVGDocumentRelease = (void (*)(CGSVGDocumentRef))dlsym(RTLD_DEFAULT, Base64DecodedString(@"Q0dTVkdEb2N1bWVudFJlbGVhc2U=").UTF8String);
43-
CoreSVGDocumentCreateFromData = (CGSVGDocumentRef (*)(CFDataRef data, CFDictionaryRef options))dlsym(RTLD_DEFAULT, Base64DecodedString(@"Q0dTVkdEb2N1bWVudENyZWF0ZUZyb21EYXRh").UTF8String);
44-
CoreSVGContextDrawSVGDocument = (void (*)(CGContextRef context, CGSVGDocumentRef document))dlsym(RTLD_DEFAULT, Base64DecodedString(@"Q0dDb250ZXh0RHJhd1NWR0RvY3VtZW50").UTF8String);
45-
CoreSVGDocumentGetCanvasSize = (CGSize (*)(CGSVGDocumentRef document))dlsym(RTLD_DEFAULT, Base64DecodedString(@"Q0dTVkdEb2N1bWVudEdldENhbnZhc1NpemU=").UTF8String);
46-
49+
CoreSVGDocumentRetain = (CGSVGDocumentRef (*)(CGSVGDocumentRef))dlsym(RTLD_DEFAULT, Base64DecodedString(@"Q0dTVkdEb2N1bWVudFJldGFpbg==").UTF8String);
50+
CoreSVGDocumentRelease = (void (*)(CGSVGDocumentRef))dlsym(RTLD_DEFAULT, Base64DecodedString(@"Q0dTVkdEb2N1bWVudFJlbGVhc2U=").UTF8String);
51+
CoreSVGDocumentCreateFromData = (CGSVGDocumentRef (*)(CFDataRef data, CFDictionaryRef options))dlsym(RTLD_DEFAULT, Base64DecodedString(@"Q0dTVkdEb2N1bWVudENyZWF0ZUZyb21EYXRh").UTF8String);
52+
CoreSVGContextDrawSVGDocument = (void (*)(CGContextRef context, CGSVGDocumentRef document))dlsym(RTLD_DEFAULT, Base64DecodedString(@"Q0dDb250ZXh0RHJhd1NWR0RvY3VtZW50").UTF8String);
53+
CoreSVGDocumentGetCanvasSize = (CGSize (*)(CGSVGDocumentRef document))dlsym(RTLD_DEFAULT, Base64DecodedString(@"Q0dTVkdEb2N1bWVudEdldENhbnZhc1NpemU=").UTF8String);
54+
4755
#if TARGET_OS_IOS || TARGET_OS_WATCH
48-
CoreSVGImageWithDocumentSEL = NSSelectorFromString(Base64DecodedString(@"X2ltYWdlV2l0aENHU1ZHRG9jdW1lbnQ6"));
56+
CoreSVGImageWithDocumentSEL = NSSelectorFromString(Base64DecodedString(@"X2ltYWdlV2l0aENHU1ZHRG9jdW1lbnQ6"));
57+
CoreSVGDocumentSEL = NSSelectorFromString(Base64DecodedString(@"X0NHU1ZHRG9jdW1lbnQ="));
4958
#endif
50-
}
51-
52-
- (UIImage *)imageFromSVGData:(NSData *)data {
53-
return [self imageFromSVGData:data targetSize:CGSizeZero preserveAspectRatio:YES];
54-
}
55-
56-
- (UIImage *)imageFromSVGData:(NSData *)data targetSize:(CGSize)targetSize {
57-
return [self imageFromSVGData:data targetSize:targetSize preserveAspectRatio:YES];
58-
}
59-
60-
- (UIImage *)imageFromSVGData:(NSData *)data targetSize:(CGSize)targetSize preserveAspectRatio:(BOOL)preserveAspectRatio {
61-
if (!data) {
62-
return nil;
63-
}
64-
65-
if (CGSizeEqualToSize(targetSize, CGSizeZero) && [self.class supportsVectorSVG]) {
66-
return [self createVectorSVGWithData:data];
67-
} else {
68-
return [self createBitmapSVGWithData:data targetSize:targetSize preserveAspectRatio:preserveAspectRatio];
59+
#if SD_MAC
60+
CoreSVGImageRepClass = NSClassFromString(Base64DecodedString(@"X05TU1ZHSW1hZ2VSZXA="));
61+
if (CoreSVGImageRepClass) {
62+
CoreSVGImageRepDocumentIvar = class_getInstanceVariable(CoreSVGImageRepClass, Base64DecodedString(@"X2RvY3VtZW50").UTF8String);
6963
}
64+
#endif
7065
}
7166

72-
- (UIImage *)createVectorSVGWithData:(NSData *)data {
73-
if (!data) return nil;
74-
75-
#if TARGET_OS_IOS || TARGET_OS_WATCH
76-
CGSVGDocumentRef document = CoreSVGDocumentCreateFromData((__bridge CFDataRef)data, NULL);
77-
if (!document) {
78-
return nil;
79-
}
80-
81-
UIImage *image = ((UIImage *(*)(id,SEL,CGSVGDocumentRef))[UIImage.class methodForSelector:CoreSVGImageWithDocumentSEL])(UIImage.class, CoreSVGImageWithDocumentSEL, document);
82-
CoreSVGDocumentRelease(document);
83-
84-
// Test render to catch potential CoreSVG crashes
85-
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), NO, 1.0);
86-
@try {
87-
[image drawInRect:CGRectMake(0, 0, 1, 1)];
88-
} @catch (...) {
89-
UIGraphicsEndImageContext();
90-
return nil;
91-
}
92-
UIGraphicsEndImageContext();
67+
- (PlatformImage *)imageFromSVGData:(NSData *)data {
68+
if (!data) {
69+
return nil;
70+
}
71+
72+
if (![self.class supportsVectorSVGImage]) {
73+
return nil;
74+
}
75+
76+
return [self createVectorSVGWithData:data];
77+
}
9378

94-
return image;
79+
- (PlatformImage *)createVectorSVGWithData:(NSData *)data {
80+
if (!data) return nil;
81+
82+
PlatformImage *image;
83+
84+
#if TARGET_OS_OSX
85+
Class imageRepClass = CoreSVGImageRepClass;
86+
NSImageRep *imageRep = [[imageRepClass alloc] initWithData:data];
87+
if (!imageRep) {
88+
return nil;
89+
}
90+
image = [[NSImage alloc] initWithSize:imageRep.size];
91+
[image addRepresentation:imageRep];
9592
#else
96-
return [self createBitmapSVGWithData:data targetSize:CGSizeZero preserveAspectRatio:YES];
93+
CGSVGDocumentRef document = CoreSVGDocumentCreateFromData((__bridge CFDataRef)data, NULL);
94+
95+
if (!document) {
96+
return nil;
97+
}
98+
99+
image = ((UIImage *(*)(id,SEL,CGSVGDocumentRef))[UIImage.class methodForSelector:CoreSVGImageWithDocumentSEL])(UIImage.class, CoreSVGImageWithDocumentSEL, document);
100+
CoreSVGDocumentRelease(document);
97101
#endif
98-
}
99-
100-
- (UIImage *)createBitmapSVGWithData:(NSData *)data targetSize:(CGSize)targetSize preserveAspectRatio:(BOOL)preserveAspectRatio {
101-
if (!data) return nil;
102-
103-
CGSVGDocumentRef document = CoreSVGDocumentCreateFromData((__bridge CFDataRef)data, NULL);
104-
if (!document) {
105-
return nil;
106-
}
107-
108-
CGSize size = CoreSVGDocumentGetCanvasSize(document);
109-
if (size.width <= 0 || size.height <= 0) {
110-
CoreSVGDocumentRelease(document);
111-
return nil;
112-
}
113-
114-
CGFloat xScale, yScale;
115-
116-
if (CGSizeEqualToSize(targetSize, CGSizeZero)) {
117-
targetSize = size;
118-
xScale = yScale = 1.0;
119-
} else {
120-
CGFloat xRatio = targetSize.width / size.width;
121-
CGFloat yRatio = targetSize.height / size.height;
122-
123-
if (preserveAspectRatio) {
124-
if (targetSize.width <= 0) {
125-
yScale = yRatio;
126-
xScale = yRatio;
127-
targetSize.width = size.width * xScale;
128-
} else if (targetSize.height <= 0) {
129-
xScale = xRatio;
130-
yScale = xRatio;
131-
targetSize.height = size.height * yScale;
132-
} else {
133-
xScale = MIN(xRatio, yRatio);
134-
yScale = MIN(xRatio, yRatio);
135-
targetSize.width = size.width * xScale;
136-
targetSize.height = size.height * yScale;
137-
}
138-
} else {
139-
if (targetSize.width <= 0) {
140-
targetSize.width = size.width;
141-
yScale = yRatio;
142-
xScale = 1.0;
143-
} else if (targetSize.height <= 0) {
144-
xScale = xRatio;
145-
yScale = 1.0;
146-
targetSize.height = size.height;
147-
} else {
148-
xScale = xRatio;
149-
yScale = yRatio;
150-
}
151-
}
152-
}
153-
154-
CGAffineTransform scaleTransform = CGAffineTransformMakeScale(xScale, yScale);
155-
CGAffineTransform offsetTransform = CGAffineTransformIdentity;
156-
157-
if (preserveAspectRatio) {
158-
CGFloat offsetX = (targetSize.width / xScale - size.width) / 2;
159-
CGFloat offsetY = (targetSize.height / yScale - size.height) / 2;
160-
offsetTransform = CGAffineTransformMakeTranslation(offsetX, offsetY);
161-
}
162-
163-
UIGraphicsBeginImageContextWithOptions(targetSize, NO, 0);
164-
CGContextRef context = UIGraphicsGetCurrentContext();
165-
102+
166103
#if TARGET_OS_IOS || TARGET_OS_WATCH
167-
CGContextTranslateCTM(context, 0, targetSize.height);
168-
CGContextScaleCTM(context, 1, -1);
169-
#endif
170-
171-
CGContextConcatCTM(context, scaleTransform);
172-
CGContextConcatCTM(context, offsetTransform);
173-
174-
CoreSVGContextDrawSVGDocument(context, document);
175-
176-
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
104+
// Test render to catch potential CoreSVG crashes
105+
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), NO, 1.0);
106+
@try {
107+
[image drawInRect:CGRectMake(0, 0, 1, 1)];
108+
} @catch (...) {
177109
UIGraphicsEndImageContext();
178-
179-
CoreSVGDocumentRelease(document);
180-
181-
return image;
110+
return nil;
111+
}
112+
UIGraphicsEndImageContext();
113+
#endif
114+
115+
return image;
182116
}
183117

184-
- (BOOL)isSVGData:(NSData *)data {
185-
if (!data) return NO;
186-
187-
NSRange searchRange = NSMakeRange(MAX(0, (NSInteger)data.length - 100), MIN(100, data.length));
188-
return [data rangeOfData:[kSVGTagEnd dataUsingEncoding:NSUTF8StringEncoding]
189-
options:NSDataSearchBackwards
190-
range:searchRange].location != NSNotFound;
118+
+ (BOOL)isSVGData:(NSData *)data {
119+
if (!data) {
120+
return NO;
121+
}
122+
// Check end with SVG tag
123+
return [data rangeOfData:[kSVGTagEnd dataUsingEncoding:NSUTF8StringEncoding] options:NSDataSearchBackwards range: NSMakeRange(data.length - MIN(100, data.length), MIN(100, data.length))].location != NSNotFound;
191124
}
192125

193-
+ (BOOL)supportsVectorSVG {
194-
static dispatch_once_t onceToken;
195-
static BOOL supports;
196-
dispatch_once(&onceToken, ^{
197-
#if TARGET_OS_IOS || TARGET_OS_WATCH
198-
supports = [UIImage respondsToSelector:CoreSVGImageWithDocumentSEL];
126+
+ (BOOL)supportsVectorSVGImage {
127+
static dispatch_once_t onceToken;
128+
static BOOL supports;
129+
dispatch_once(&onceToken, ^{
130+
#if PLATFORM_OS_OSX
131+
// macOS 10.15+ supports SVG built-in rendering, use selector to check is more accurate
132+
if (SDNSSVGImageRepClass) {
133+
supports = YES;
134+
} else {
135+
supports = NO;
136+
}
199137
#else
200-
supports = NO;
138+
// iOS 13+ supports SVG built-in rendering, use selector to check is more accurate
139+
if ([UIImage respondsToSelector:CoreSVGImageWithDocumentSEL]) {
140+
supports = YES;
141+
} else {
142+
supports = NO;
143+
}
201144
#endif
202-
});
203-
return supports;
145+
});
146+
return supports;
204147
}
205148

206149
@end

packages/react-native-bottom-tabs/ios/SVG/SvgDecoder.mm

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,7 @@ @implementation SvgDecoder
77

88
- (BOOL)canDecodeImageData:(NSData *)imageData
99
{
10-
#if TARGET_OS_OSX
11-
return NO;
12-
#endif
13-
14-
if (!imageData || imageData.length == 0) {
15-
return NO;
16-
}
17-
18-
NSString *dataString = [[NSString alloc] initWithData:imageData encoding:NSUTF8StringEncoding];
19-
20-
if (!dataString) {
21-
return NO;
22-
}
23-
24-
NSString *lowercaseString = [dataString lowercaseString];
25-
BOOL containsSVGTag = [lowercaseString containsString:@"<svg"];
26-
BOOL containsXMLDeclaration = [lowercaseString containsString:@"<?xml"];
27-
BOOL containsSVGNamespace = [lowercaseString containsString:@"http://www.w3.org/2000/svg"];
28-
29-
return containsSVGTag || (containsXMLDeclaration && containsSVGNamespace);
10+
return [CoreSVGWrapper isSVGData:imageData];
3011
}
3112

3213

@@ -36,8 +17,7 @@ - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData
3617
resizeMode:(RCTResizeMode)resizeMode
3718
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
3819
{
39-
#if !TARGET_OS_OSX
40-
UIImage *image = [[CoreSVGWrapper alloc] imageFromSVGData:imageData targetSize:size];
20+
UIImage *image = [CoreSVGWrapper.shared imageFromSVGData:imageData];
4121

4222
if (image) {
4323
completionHandler(nil, image);
@@ -48,9 +28,6 @@ - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData
4828
completionHandler(error, nil);
4929
}
5030
return ^{};
51-
#else
52-
return ^{};
53-
#endif
5431
}
5532

5633
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:

0 commit comments

Comments
 (0)