|
1 |
| -#if !TARGET_OS_OSX |
2 |
| - |
3 | 1 | #import "CoreSVG.h"
|
4 | 2 | #import <dlfcn.h>
|
5 | 3 | #import <objc/runtime.h>
|
|
14 | 12 | static void (*CoreSVGContextDrawSVGDocument)(CGContextRef context, CGSVGDocumentRef document);
|
15 | 13 | static CGSize (*CoreSVGDocumentGetCanvasSize)(CGSVGDocumentRef document);
|
16 | 14 |
|
17 |
| -#if TARGET_OS_IOS || TARGET_OS_WATCH |
| 15 | +#if !TARGET_OS_OSX |
18 | 16 | 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; |
19 | 22 | #endif
|
20 | 23 |
|
21 | 24 | 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]; |
27 | 30 | }
|
28 | 31 |
|
29 | 32 | @implementation CoreSVGWrapper
|
30 | 33 |
|
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; |
38 | 41 | }
|
39 | 42 |
|
40 | 43 | + (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 | + } |
49 | 59 | #endif
|
50 | 60 | }
|
51 | 61 |
|
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]; |
58 | 72 | }
|
59 | 73 |
|
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]; |
95 | 90 | #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); |
169 | 102 | #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 (...) { |
177 | 136 | UIGraphicsEndImageContext();
|
178 |
| - |
179 |
| - CoreSVGDocumentRelease(document); |
180 |
| - |
181 |
| - return image; |
| 137 | + return nil; |
| 138 | + } |
| 139 | + UIGraphicsEndImageContext(); |
| 140 | +#endif |
| 141 | + |
| 142 | + |
| 143 | + return image; |
182 | 144 | }
|
183 | 145 |
|
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; |
191 | 152 | }
|
192 | 153 |
|
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 | + } |
199 | 165 | #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 | + } |
201 | 172 | #endif
|
202 |
| - }); |
203 |
| - return supports; |
| 173 | + }); |
| 174 | + return supports; |
204 | 175 | }
|
205 | 176 |
|
206 | 177 | @end
|
207 |
| - |
208 |
| -#endif |
0 commit comments