Skip to content

Commit 2ed2ab3

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

File tree

3 files changed

+149
-201
lines changed

3 files changed

+149
-201
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
Lines changed: 135 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
#if !TARGET_OS_OSX
2-
31
#import "CoreSVG.h"
42
#import <dlfcn.h>
53
#import <objc/runtime.h>
@@ -14,195 +12,166 @@
1412
static void (*CoreSVGContextDrawSVGDocument)(CGContextRef context, CGSVGDocumentRef document);
1513
static CGSize (*CoreSVGDocumentGetCanvasSize)(CGSVGDocumentRef document);
1614

17-
#if TARGET_OS_IOS || TARGET_OS_WATCH
15+
#if !TARGET_OS_OSX
1816
static SEL CoreSVGImageWithDocumentSEL = NULL;
17+
static SEL CoreSVGDocumentSEL = NULL;
18+
#endif
19+
#if TARGET_OS_OSX
20+
static Class CoreSVGImageRepClass = NULL;
21+
static Ivar CoreSVGImageRepDocumentIvar = NULL;
1922
#endif
2023

2124
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];
25+
NSData *data = [[NSData alloc] initWithBase64EncodedString:base64String options:NSDataBase64DecodingIgnoreUnknownCharacters];
26+
if (!data) {
27+
return nil;
28+
}
29+
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
2730
}
2831

2932
@implementation CoreSVGWrapper
3033

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

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

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

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];
69-
}
70-
}
71-
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();
93-
94-
return image;
74+
- (PlatformImage *)createVectorSVGWithData:(NSData *)data {
75+
if (!data) return nil;
76+
77+
PlatformImage *image;
78+
79+
#if TARGET_OS_OSX
80+
if (!CoreSVGImageRepClass) {
81+
return nil;
82+
}
83+
Class imageRepClass = CoreSVGImageRepClass;
84+
NSImageRep *imageRep = [[imageRepClass alloc] initWithData:data];
85+
if (!imageRep) {
86+
return nil;
87+
}
88+
image = [[NSImage alloc] initWithSize:imageRep.size];
89+
[image addRepresentation:imageRep];
9590
#else
96-
return [self createBitmapSVGWithData:data targetSize:CGSizeZero preserveAspectRatio:YES];
97-
#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-
166-
#if TARGET_OS_IOS || TARGET_OS_WATCH
167-
CGContextTranslateCTM(context, 0, targetSize.height);
168-
CGContextScaleCTM(context, 1, -1);
91+
if (!CoreSVGDocumentCreateFromData || !CoreSVGDocumentRelease) {
92+
return nil;
93+
}
94+
CGSVGDocumentRef document = CoreSVGDocumentCreateFromData((__bridge CFDataRef)data, NULL);
95+
96+
if (!document) {
97+
return nil;
98+
}
99+
100+
image = ((UIImage *(*)(id,SEL,CGSVGDocumentRef))[UIImage.class methodForSelector:CoreSVGImageWithDocumentSEL])(UIImage.class, CoreSVGImageWithDocumentSEL, document);
101+
CoreSVGDocumentRelease(document);
169102
#endif
170-
171-
CGContextConcatCTM(context, scaleTransform);
172-
CGContextConcatCTM(context, offsetTransform);
173-
174-
CoreSVGContextDrawSVGDocument(context, document);
175-
176-
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
103+
104+
#if TARGET_OS_OSX
105+
// Test render to catch potential CoreSVG crashes on macOS
106+
NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc]
107+
initWithBitmapDataPlanes:NULL
108+
pixelsWide:1
109+
pixelsHigh:1
110+
bitsPerSample:8
111+
samplesPerPixel:4
112+
hasAlpha:YES
113+
isPlanar:NO
114+
colorSpaceName:NSCalibratedRGBColorSpace
115+
bytesPerRow:0
116+
bitsPerPixel:0];
117+
118+
NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithBitmapImageRep:bitmap];
119+
[NSGraphicsContext saveGraphicsState];
120+
[NSGraphicsContext setCurrentContext:context];
121+
122+
@try {
123+
[image drawInRect:NSMakeRect(0, 0, 1, 1)];
124+
} @catch (...) {
125+
[NSGraphicsContext restoreGraphicsState];
126+
return nil;
127+
}
128+
129+
[NSGraphicsContext restoreGraphicsState];
130+
#else
131+
// Test render to catch potential CoreSVG crashes
132+
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), NO, 1.0);
133+
@try {
134+
[image drawInRect:CGRectMake(0, 0, 1, 1)];
135+
} @catch (...) {
177136
UIGraphicsEndImageContext();
178-
179-
CoreSVGDocumentRelease(document);
180-
181-
return image;
137+
return nil;
138+
}
139+
UIGraphicsEndImageContext();
140+
#endif
141+
142+
143+
return image;
182144
}
183145

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;
146+
+ (BOOL)isSVGData:(NSData *)data {
147+
if (!data) {
148+
return NO;
149+
}
150+
// Check end with SVG tag
151+
return [data rangeOfData:[kSVGTagEnd dataUsingEncoding:NSUTF8StringEncoding] options:NSDataSearchBackwards range: NSMakeRange(data.length - MIN(100, data.length), MIN(100, data.length))].location != NSNotFound;
191152
}
192153

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];
154+
+ (BOOL)supportsVectorSVGImage {
155+
static dispatch_once_t onceToken;
156+
static BOOL supports;
157+
dispatch_once(&onceToken, ^{
158+
#if TARGET_OS_OSX
159+
// macOS 10.15+ supports SVG built-in rendering, use selector to check is more accurate
160+
if (CoreSVGImageRepClass) {
161+
supports = YES;
162+
} else {
163+
supports = NO;
164+
}
199165
#else
200-
supports = NO;
166+
// iOS 13+ supports SVG built-in rendering, use selector to check is more accurate
167+
if ([UIImage respondsToSelector:CoreSVGImageWithDocumentSEL]) {
168+
supports = YES;
169+
} else {
170+
supports = NO;
171+
}
201172
#endif
202-
});
203-
return supports;
173+
});
174+
return supports;
204175
}
205176

206177
@end
207-
208-
#endif

0 commit comments

Comments
 (0)