react-native-fast-image 8.27.1 → 8.27.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -157,18 +157,73 @@ static CGSize FFFPhotoPixelSize(NSURL *url)
157
157
  // body is an image: a page sent instead (e.g. a captive portal's, with status
158
158
  // 200) would be served from there until it expired, and each load would fail.
159
159
  // Removing it after a load failed (forgetResponseAfterError:) could still be
160
- // in progress when the next load started.
160
+ // in progress when the next load started. A page with an inline <svg> isn't
161
+ // an SVG image.
161
162
  @interface FFFWebDownloaderOperation : SDWebImageDownloaderOperation
162
163
  @end
163
164
 
164
165
  @implementation FFFWebDownloaderOperation
165
166
 
167
+ // Whether data is an SVG document: its first element (after an XML
168
+ // declaration, comments or a doctype) is <svg>, as on Android
169
+ // (FastImageSvg.looksLikeSvg). SDWebImage takes data that starts with '<' and
170
+ // has an <svg> element anywhere for SVG, such as a page with an icon.
171
+ static BOOL FFFHasPrefixAt(NSString *text, NSUInteger i, NSString *prefix)
172
+ {
173
+ return text.length - i >= prefix.length && [text compare:prefix options:0 range:NSMakeRange(i, prefix.length)] == NSOrderedSame;
174
+ }
175
+
176
+ // The index after the end marker from i, or NSNotFound if it isn't there.
177
+ static NSUInteger FFFSkipPast(NSString *text, NSUInteger i, NSString *end)
178
+ {
179
+ NSRange found = [text rangeOfString:end options:0 range:NSMakeRange(i, text.length - i)];
180
+ return found.location == NSNotFound ? NSNotFound : NSMaxRange(found);
181
+ }
182
+
183
+ static BOOL FFFLooksLikeSVG(NSData *data)
184
+ {
185
+ // The markup is ASCII: Latin-1 reads any bytes (a UTF-8 character cut off
186
+ // at the end too).
187
+ NSData *head = [data subdataWithRange:NSMakeRange(0, MIN(data.length, (NSUInteger)1024))];
188
+ NSString *text = [[[NSString alloc] initWithData:head encoding:NSISOLatin1StringEncoding] lowercaseString];
189
+ NSCharacterSet *space = NSCharacterSet.whitespaceAndNewlineCharacterSet;
190
+ // A UTF-8 byte order mark, as Latin-1.
191
+ NSUInteger i = FFFHasPrefixAt(text, 0, @"\u00EF\u00BB\u00BF") ? 3 : 0;
192
+ while (i < text.length) {
193
+ if ([space characterIsMember:[text characterAtIndex:i]]) {
194
+ i++;
195
+ } else if (FFFHasPrefixAt(text, i, @"<?")) {
196
+ i = FFFSkipPast(text, i, @"?>");
197
+ } else if (FFFHasPrefixAt(text, i, @"<!--")) {
198
+ i = FFFSkipPast(text, i, @"-->");
199
+ } else if (FFFHasPrefixAt(text, i, @"<!")) {
200
+ // A doctype, whose internal subset ([...]) can have '>'s.
201
+ NSRange rest = NSMakeRange(i, text.length - i);
202
+ NSUInteger subset = [text rangeOfString:@"[" options:0 range:rest].location;
203
+ NSUInteger close = [text rangeOfString:@">" options:0 range:rest].location;
204
+ if (subset != NSNotFound && subset < close) {
205
+ i = FFFSkipPast(text, subset, @"]");
206
+ }
207
+ if (i != NSNotFound) {
208
+ i = FFFSkipPast(text, i, @">");
209
+ }
210
+ } else {
211
+ return FFFHasPrefixAt(text, i, @"<svg");
212
+ }
213
+ if (i == NSNotFound) {
214
+ return NO;
215
+ }
216
+ }
217
+ return NO;
218
+ }
219
+
166
220
  - (void)URLSession:(NSURLSession *)session
167
221
  dataTask:(NSURLSessionDataTask *)dataTask
168
222
  willCacheResponse:(NSCachedURLResponse *)proposedResponse
169
223
  completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler
170
224
  {
171
- if ([NSData sd_imageFormatForImageData:proposedResponse.data] == SDImageFormatUndefined) {
225
+ SDImageFormat format = [NSData sd_imageFormatForImageData:proposedResponse.data];
226
+ if (format == SDImageFormatUndefined || (format == SDImageFormatSVG && !FFFLooksLikeSVG(proposedResponse.data))) {
172
227
  completionHandler(nil);
173
228
  return;
174
229
  }
@@ -263,7 +263,12 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
263
263
  [self blurAgainIfResized];
264
264
  [self tileAgainIfResized];
265
265
  if (self.waitsForSize) {
266
- if ([self hasSize]) {
266
+ // An image in the memory cache shows in this frame. Others start
267
+ // loading from didSetProps's block, after layout, in the order the
268
+ // views got their props: UIKit lays views out in its own order (in a
269
+ // grid, the last one first), and downloads start in the order they're
270
+ // asked for, so a screen's first images would load last.
271
+ if ([self hasSize] && [self loadsDuringLayout]) {
267
272
  [self reloadImage];
268
273
  }
269
274
  } else if (![self switchSourceIfResized]) {
@@ -770,9 +775,10 @@ NSString *FFFErrorMessage(NSError *error)
770
775
  // With downsample on, the image is decoded for the view's size, and
771
776
  // with several sources one is picked for it, so a view that hasn't
772
777
  // been laid out yet loads once it has. Props and layout are applied
773
- // in the same update, so that's before the next frame (in
774
- // layoutSubviews). A view that still has no size then (e.g. one sized
775
- // from onLoad) loads at full size, or the largest source.
778
+ // in the same update, so that's here, just after it, or before the
779
+ // next frame (layoutSubviews) for an image in the memory cache. A
780
+ // view that still has no size then (e.g. one sized from onLoad) loads
781
+ // at full size, or the largest source.
776
782
  if (([self downsamples] || [self picksSource]) && ![self hasSize]) {
777
783
  if (!self.waitsForSize) {
778
784
  self.waitsForSize = YES;
@@ -960,31 +966,59 @@ NSString *FFFErrorMessage(NSError *error)
960
966
  // Headers, and the size to decode at (see FFFDownsampledImage), which it
961
967
  // records as decodedBox.
962
968
  - (SDWebImageContext*) loadContext {
963
- SDWebImageMutableContext* context = [NSMutableDictionary dictionary];
964
- context[SDWebImageContextDownloadRequestModifier] = _source.requestModifier;
965
- context[SDWebImageContextImageLoader] = _source.imageLoader;
966
- context[SDWebImageContextCacheKeyFilter] = _source.cacheKeyFilter;
967
969
  CGSize box = [self decodeBox];
968
970
  self.decodedBox = box;
969
971
  self.decodedCover = [self decodeCovers];
972
+ return [self contextForSource: _source box: box cover: self.decodedCover];
973
+ }
974
+
975
+ // Whether the view loads as it's laid out, so its image shows in this frame:
976
+ // one in the memory cache, where SDWebImage finds it as the load starts, or
977
+ // one that isn't downloaded (no source, which shows defaultSource, or a data
978
+ // uri). Also when that can't be told (an app's own image cache, or an
979
+ // SDWebImage without cacheKeyForURL:context:), as before.
980
+ - (BOOL) loadsDuringLayout {
981
+ FFFastImageSource* source = [self picksSource] ? [self sourceForSize] : _source;
982
+ if (!source.url || [source.url.scheme isEqualToString: @"data"]) {
983
+ return YES;
984
+ }
985
+ if (!source.memoryCache) {
986
+ return NO;
987
+ }
988
+ SDWebImageManager* manager = [SDWebImageManager sharedManager];
989
+ if (![manager.imageCache isKindOfClass: [SDImageCache class]] || ![manager respondsToSelector: @selector(cacheKeyForURL:context:)]) {
990
+ return YES;
991
+ }
992
+ CGSize box = [self decodeBox];
993
+ SDWebImageContext* context = [self contextForSource: source box: box cover: [self decodeCovers]];
994
+ NSURL* url = CGSizeEqualToSize(box, CGSizeZero) ? source.url : [FFFDownsampledImage loadURLForURL: source.url];
995
+ NSString* key = [manager cacheKeyForURL: url context: context];
996
+ return [(SDImageCache*) manager.imageCache imageFromMemoryCacheForKey: key] != nil;
997
+ }
998
+
999
+ - (SDWebImageContext*) contextForSource: (FFFastImageSource*)source box: (CGSize)box cover: (BOOL)cover {
1000
+ SDWebImageMutableContext* context = [NSMutableDictionary dictionary];
1001
+ context[SDWebImageContextDownloadRequestModifier] = source.requestModifier;
1002
+ context[SDWebImageContextImageLoader] = source.imageLoader;
1003
+ context[SDWebImageContextCacheKeyFilter] = source.cacheKeyFilter;
970
1004
  if (CGSizeEqualToSize(box, CGSizeZero)) {
971
1005
  context[SDWebImageContextAnimatedImageClass] = [SDAnimatedImage class];
972
- if (!_source.memoryCache) {
1006
+ if (!source.memoryCache) {
973
1007
  // Only on disk, also when it comes from there.
974
1008
  context[SDWebImageContextStoreCacheType] = @(SDImageCacheTypeDisk);
975
1009
  }
976
1010
  return context;
977
1011
  }
978
- NSString* key = _source.cacheKeyFilter ? _source.cacheKey : _source.url.absoluteString;
979
- [FFFDownsampledImage addToContext: context forKey: key box: box cover: self.decodedCover];
980
- if ([_source isPhotoLibrary]) {
1012
+ NSString* key = source.cacheKeyFilter ? source.cacheKey : source.url.absoluteString;
1013
+ [FFFDownsampledImage addToContext: context forKey: key box: box cover: cover];
1014
+ if ([source isPhotoLibrary]) {
981
1015
  // Photos makes the photo at the size asked for, with no file
982
1016
  // downloaded: SDWebImage would keep this smaller copy on disk as the
983
1017
  // photo's original, and a larger view would get it from there.
984
1018
  context[SDWebImageContextOriginalStoreCacheType] = @(SDImageCacheTypeNone);
985
1019
  context[SDWebImageContextOriginalQueryCacheType] = @(SDImageCacheTypeNone);
986
1020
  }
987
- if (!_source.memoryCache) {
1021
+ if (!source.memoryCache) {
988
1022
  // The smaller copy is only kept in memory, so it isn't kept (the
989
1023
  // downloaded file is still on disk).
990
1024
  context[SDWebImageContextStoreCacheType] = @(SDImageCacheTypeNone);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-fast-image",
3
- "version": "8.27.1",
3
+ "version": "8.27.3",
4
4
  "description": "FastImage, performant React Native image component.",
5
5
  "keywords": [
6
6
  "cache",