Skip to content

Commit 4ffb3e3

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

File tree

3 files changed

+113
-194
lines changed

3 files changed

+113
-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: 99 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -16,191 +16,131 @@
1616

1717
#if TARGET_OS_IOS || TARGET_OS_WATCH
1818
static SEL CoreSVGImageWithDocumentSEL = NULL;
19+
static SEL CoreSVGDocumentSEL = NULL;
20+
#endif
21+
#if TARGET_OS_OSX
22+
static Class CoreSVGImageRepClass = NULL;
23+
static Ivar CoreSVGImageRepDocumentIvar = NULL;
1924
#endif
2025

2126
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];
27+
NSData *data = [[NSData alloc] initWithBase64EncodedString:base64String options:NSDataBase64DecodingIgnoreUnknownCharacters];
28+
if (!data) {
29+
return nil;
30+
}
31+
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
2732
}
2833

2934
@implementation CoreSVGWrapper
3035

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;
36+
+ (instancetype)shared {
37+
static dispatch_once_t onceToken;
38+
static CoreSVGWrapper *wrapper;
39+
dispatch_once(&onceToken, ^{
40+
wrapper = [[CoreSVGWrapper alloc] init];
41+
});
42+
return wrapper;
3843
}
3944

4045
+ (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-
46+
CoreSVGDocumentRetain = (CGSVGDocumentRef (*)(CGSVGDocumentRef))dlsym(RTLD_DEFAULT, Base64DecodedString(@"Q0dTVkdEb2N1bWVudFJldGFpbg==").UTF8String);
47+
CoreSVGDocumentRelease = (void (*)(CGSVGDocumentRef))dlsym(RTLD_DEFAULT, Base64DecodedString(@"Q0dTVkdEb2N1bWVudFJlbGVhc2U=").UTF8String);
48+
CoreSVGDocumentCreateFromData = (CGSVGDocumentRef (*)(CFDataRef data, CFDictionaryRef options))dlsym(RTLD_DEFAULT, Base64DecodedString(@"Q0dTVkdEb2N1bWVudENyZWF0ZUZyb21EYXRh").UTF8String);
49+
CoreSVGContextDrawSVGDocument = (void (*)(CGContextRef context, CGSVGDocumentRef document))dlsym(RTLD_DEFAULT, Base64DecodedString(@"Q0dDb250ZXh0RHJhd1NWR0RvY3VtZW50").UTF8String);
50+
CoreSVGDocumentGetCanvasSize = (CGSize (*)(CGSVGDocumentRef document))dlsym(RTLD_DEFAULT, Base64DecodedString(@"Q0dTVkdEb2N1bWVudEdldENhbnZhc1NpemU=").UTF8String);
51+
4752
#if TARGET_OS_IOS || TARGET_OS_WATCH
48-
CoreSVGImageWithDocumentSEL = NSSelectorFromString(Base64DecodedString(@"X2ltYWdlV2l0aENHU1ZHRG9jdW1lbnQ6"));
53+
CoreSVGImageWithDocumentSEL = NSSelectorFromString(Base64DecodedString(@"X2ltYWdlV2l0aENHU1ZHRG9jdW1lbnQ6"));
54+
CoreSVGDocumentSEL = NSSelectorFromString(Base64DecodedString(@"X0NHU1ZHRG9jdW1lbnQ="));
4955
#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];
56+
#if TARGET_OS_OSX
57+
CoreSVGImageRepClass = NSClassFromString(Base64DecodedString(@"X05TU1ZHSW1hZ2VSZXA="));
58+
if (CoreSVGImageRepClass) {
59+
CoreSVGImageRepDocumentIvar = class_getInstanceVariable(CoreSVGImageRepClass, Base64DecodedString(@"X2RvY3VtZW50").UTF8String);
6960
}
61+
#endif
7062
}
7163

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();
64+
- (PlatformImage *)imageFromSVGData:(NSData *)data {
65+
if (!data) {
66+
return nil;
67+
}
68+
69+
if (![self.class supportsVectorSVGImage]) {
70+
return nil;
71+
}
72+
73+
return [self createVectorSVGWithData:data];
74+
}
9375

94-
return image;
76+
- (PlatformImage *)createVectorSVGWithData:(NSData *)data {
77+
if (!data) return nil;
78+
79+
PlatformImage *image;
80+
81+
#if TARGET_OS_OSX
82+
Class imageRepClass = CoreSVGImageRepClass;
83+
NSImageRep *imageRep = [[imageRepClass alloc] initWithData:data];
84+
if (!imageRep) {
85+
return nil;
86+
}
87+
image = [[NSImage alloc] initWithSize:imageRep.size];
88+
[image addRepresentation:imageRep];
9589
#else
96-
return [self createBitmapSVGWithData:data targetSize:CGSizeZero preserveAspectRatio:YES];
90+
CGSVGDocumentRef document = CoreSVGDocumentCreateFromData((__bridge CFDataRef)data, NULL);
91+
92+
if (!document) {
93+
return nil;
94+
}
95+
96+
image = ((UIImage *(*)(id,SEL,CGSVGDocumentRef))[UIImage.class methodForSelector:CoreSVGImageWithDocumentSEL])(UIImage.class, CoreSVGImageWithDocumentSEL, document);
97+
CoreSVGDocumentRelease(document);
9798
#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-
99+
166100
#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();
101+
// Test render to catch potential CoreSVG crashes
102+
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), NO, 1.0);
103+
@try {
104+
[image drawInRect:CGRectMake(0, 0, 1, 1)];
105+
} @catch (...) {
177106
UIGraphicsEndImageContext();
178-
179-
CoreSVGDocumentRelease(document);
180-
181-
return image;
107+
return nil;
108+
}
109+
UIGraphicsEndImageContext();
110+
#endif
111+
112+
return image;
182113
}
183114

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;
115+
+ (BOOL)isSVGData:(NSData *)data {
116+
if (!data) {
117+
return NO;
118+
}
119+
// Check end with SVG tag
120+
return [data rangeOfData:[kSVGTagEnd dataUsingEncoding:NSUTF8StringEncoding] options:NSDataSearchBackwards range: NSMakeRange(data.length - MIN(100, data.length), MIN(100, data.length))].location != NSNotFound;
191121
}
192122

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];
123+
+ (BOOL)supportsVectorSVGImage {
124+
static dispatch_once_t onceToken;
125+
static BOOL supports;
126+
dispatch_once(&onceToken, ^{
127+
#if PLATFORM_OS_OSX
128+
// macOS 10.15+ supports SVG built-in rendering, use selector to check is more accurate
129+
if (SDNSSVGImageRepClass) {
130+
supports = YES;
131+
} else {
132+
supports = NO;
133+
}
199134
#else
200-
supports = NO;
135+
// iOS 13+ supports SVG built-in rendering, use selector to check is more accurate
136+
if ([UIImage respondsToSelector:CoreSVGImageWithDocumentSEL]) {
137+
supports = YES;
138+
} else {
139+
supports = NO;
140+
}
201141
#endif
202-
});
203-
return supports;
142+
});
143+
return supports;
204144
}
205145

206146
@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)