|
16 | 16 |
|
17 | 17 | #if TARGET_OS_IOS || TARGET_OS_WATCH
|
18 | 18 | 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; |
19 | 24 | #endif
|
20 | 25 |
|
21 | 26 | 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]; |
27 | 32 | }
|
28 | 33 |
|
29 | 34 | @implementation CoreSVGWrapper
|
30 | 35 |
|
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; |
38 | 43 | }
|
39 | 44 |
|
40 | 45 | + (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 | + |
47 | 52 | #if TARGET_OS_IOS || TARGET_OS_WATCH
|
48 |
| - CoreSVGImageWithDocumentSEL = NSSelectorFromString(Base64DecodedString(@"X2ltYWdlV2l0aENHU1ZHRG9jdW1lbnQ6")); |
| 53 | + CoreSVGImageWithDocumentSEL = NSSelectorFromString(Base64DecodedString(@"X2ltYWdlV2l0aENHU1ZHRG9jdW1lbnQ6")); |
| 54 | + CoreSVGDocumentSEL = NSSelectorFromString(Base64DecodedString(@"X0NHU1ZHRG9jdW1lbnQ=")); |
49 | 55 | #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); |
69 | 60 | }
|
| 61 | +#endif |
70 | 62 | }
|
71 | 63 |
|
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 | +} |
93 | 75 |
|
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]; |
95 | 89 | #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); |
97 | 98 | #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 | + |
166 | 100 | #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 (...) { |
177 | 106 | UIGraphicsEndImageContext();
|
178 |
| - |
179 |
| - CoreSVGDocumentRelease(document); |
180 |
| - |
181 |
| - return image; |
| 107 | + return nil; |
| 108 | + } |
| 109 | + UIGraphicsEndImageContext(); |
| 110 | +#endif |
| 111 | + |
| 112 | + return image; |
182 | 113 | }
|
183 | 114 |
|
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; |
191 | 121 | }
|
192 | 122 |
|
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 | + } |
199 | 134 | #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 | + } |
201 | 141 | #endif
|
202 |
| - }); |
203 |
| - return supports; |
| 142 | + }); |
| 143 | + return supports; |
204 | 144 | }
|
205 | 145 |
|
206 | 146 | @end
|
|
0 commit comments