react-native-fast-image 8.27.2 → 8.27.4

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.
@@ -10,12 +10,16 @@
10
10
  // the image's header before it decodes, as React Native's Image does.
11
11
  @interface FFFDownsampledImage : SDAnimatedImage
12
12
 
13
- // The url to load a source's url with, when it's downsampled.
14
- + (NSURL*) loadURLForURL: (NSURL*)url;
15
-
16
13
  // Sets a load's context to decode the image for a box (in pixels) to cover
17
- // or fit in. `key` is the source's cache key (its url, or its cacheKey).
18
- + (void) addToContext: (SDWebImageMutableContext*)context forKey: (NSString*)key box: (CGSize)box cover: (BOOL)cover;
14
+ // or fit in.
15
+ + (void) addToContext: (SDWebImageMutableContext*)context box: (CGSize)box cover: (BOOL)cover;
16
+
17
+ // Sets a load's context to decode the image at full size, through this class
18
+ // when it's supported. Every FastImage load uses it, so loads of the same url
19
+ // can share a download: SDWebImage's downloader decodes each load of a
20
+ // download with the first load's image class (and each with its own size),
21
+ // and SDWebImage's own thumbnail decoding can't cover a box.
22
+ + (void) addFullSizeToContext: (SDWebImageMutableContext*)context;
19
23
 
20
24
  // The size of the full image (in points, like UIImage's size) that an image
21
25
  // loaded this way was decoded from, for onLoad, or zero for other images.
@@ -2,13 +2,12 @@
2
2
  #import <ImageIO/ImageIO.h>
3
3
  #import <SDWebImage/SDImageCoder.h>
4
4
  #import <SDWebImage/SDImageCacheDefine.h>
5
- #import <SDWebImage/SDWebImageCacheKeyFilter.h>
6
5
  #import <SDWebImage/UIImage+Metadata.h>
6
+ #import <SDWebImage/NSData+ImageContentType.h>
7
+ #import <SDWebImage/SDImageCodersManager.h>
7
8
 
8
9
  // The decode option that carries the full image's size to the view.
9
10
  static SDImageCoderOption const FFFDecodeSourceSize = @"FFFDecodeSourceSize";
10
- // Added to the url's fragment, which isn't sent to the server.
11
- static NSString* const FFFDownsampledFragment = @"fastimage-downsampled";
12
11
 
13
12
  // The image's size in pixels as it's shown (EXIF orientations 5 to 8 turn it
14
13
  // sideways), from its header, or zero if ImageIO can't read it.
@@ -83,38 +82,20 @@ static CGSize FFFPixelSize(NSData* data) {
83
82
  return supported;
84
83
  }
85
84
 
86
- + (NSURL*) loadURLForURL: (NSURL*)url {
87
- // SDWebImage's downloader shares a download between loads of the same
88
- // url, and decodes each load's image with the first load's image class.
89
- // After a load that isn't downsampled (e.g. a preload), this class
90
- // wouldn't be used, and the box would be decoded as SDWebImage does (to
91
- // fill it, stretched). With its own url, a downsampled load only shares
92
- // downloads with other downsampled ones. Not a photo library url, which
93
- // isn't downloaded, and whose loader reads everything after ph:// as the
94
- // photo's identifier.
95
- if ([url.scheme isEqualToString: @"ph"]) {
96
- return url;
85
+ + (void) addFullSizeToContext: (SDWebImageMutableContext*)context {
86
+ if (![self isSupported]) {
87
+ context[SDWebImageContextAnimatedImageClass] = [SDAnimatedImage class];
88
+ return;
97
89
  }
98
- NSURLComponents* components = [NSURLComponents componentsWithURL: url resolvingAgainstBaseURL: NO];
99
- if (!components) {
100
- return url;
101
- }
102
- components.fragment = components.fragment.length > 0
103
- ? [components.fragment stringByAppendingFormat: @"-%@", FFFDownsampledFragment]
104
- : FFFDownsampledFragment;
105
- return components.URL ?: url;
90
+ context[SDWebImageContextAnimatedImageClass] = [FFFDownsampledImage class];
91
+ context[SDWebImageContextImageDecodeOptions] = @{FFFDecodeSourceSize: [FFFSourceSize new]};
106
92
  }
107
93
 
108
- + (void) addToContext: (SDWebImageMutableContext*)context forKey: (NSString*)key box: (CGSize)box cover: (BOOL)cover {
94
+ + (void) addToContext: (SDWebImageMutableContext*)context box: (CGSize)box cover: (BOOL)cover {
109
95
  context[SDWebImageContextAnimatedImageClass] = [FFFDownsampledImage class];
110
- // Cached under the source's own key (not the url from loadURLForURL:),
111
- // so the disk cache keeps one download for every size and for loads
112
- // that aren't downsampled.
113
- context[SDWebImageContextCacheKeyFilter] = [SDWebImageCacheKeyFilter cacheKeyFilterWithBlock: ^NSString* (NSURL* _Nonnull loadURL) {
114
- return key;
115
- }];
116
- // The memory cache key is the url with these (SDWebImage's thumbnail
117
- // key), so views of other sizes don't get this image.
96
+ // The memory cache key is the source's key with these (SDWebImage's
97
+ // thumbnail key), so views of other sizes don't get this image. The disk
98
+ // cache keeps the downloaded file under the source's key, for every size.
118
99
  context[SDWebImageContextImageThumbnailPixelSize] = [NSValue valueWithCGSize: box];
119
100
  context[SDWebImageContextImagePreserveAspectRatio] = @(!cover);
120
101
  context[SDWebImageContextImageDecodeOptions] = @{FFFDecodeSourceSize: [FFFSourceSize new]};
@@ -129,6 +110,22 @@ static CGSize FFFPixelSize(NSData* data) {
129
110
  }
130
111
 
131
112
  - (instancetype) initWithData: (NSData*)data scale: (CGFloat)scale options: (SDImageCoderOptions*)options {
113
+ // An SVG isn't decoded smaller: it's a vector image, drawn at any size.
114
+ // This class can't decode it, and SDWebImage would then give it to the
115
+ // SVG coder with the box, which draws it into a bitmap that size. Decoded
116
+ // as without downsample instead (this init can return another image).
117
+ if ([NSData sd_imageFormatForImageData: data] == SDImageFormatSVG) {
118
+ NSMutableDictionary* vectorOptions = options ? [options mutableCopy] : [NSMutableDictionary dictionary];
119
+ [vectorOptions removeObjectForKey: SDImageCoderDecodeThumbnailPixelSize];
120
+ UIImage* image = [[SDImageCodersManager sharedManager] decodedImageWithData: data options: vectorOptions];
121
+ FFFSourceSize* sourceSize = options[FFFDecodeSourceSize];
122
+ if (image && [sourceSize isKindOfClass: [FFFSourceSize class]]) {
123
+ sourceSize.size = image.size;
124
+ }
125
+ self = (id) image;
126
+ return self;
127
+ }
128
+
132
129
  NSValue* boxValue = options[SDImageCoderDecodeThumbnailPixelSize];
133
130
  CGSize box = boxValue ? boxValue.CGSizeValue : CGSizeZero;
134
131
  NSNumber* preserveAspectRatio = options[SDImageCoderDecodePreserveAspectRatio];
@@ -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
  }
@@ -991,8 +991,7 @@ NSString *FFFErrorMessage(NSError *error)
991
991
  }
992
992
  CGSize box = [self decodeBox];
993
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];
994
+ NSString* key = [manager cacheKeyForURL: source.url context: context];
996
995
  return [(SDImageCache*) manager.imageCache imageFromMemoryCacheForKey: key] != nil;
997
996
  }
998
997
 
@@ -1002,15 +1001,14 @@ NSString *FFFErrorMessage(NSError *error)
1002
1001
  context[SDWebImageContextImageLoader] = source.imageLoader;
1003
1002
  context[SDWebImageContextCacheKeyFilter] = source.cacheKeyFilter;
1004
1003
  if (CGSizeEqualToSize(box, CGSizeZero)) {
1005
- context[SDWebImageContextAnimatedImageClass] = [SDAnimatedImage class];
1004
+ [FFFDownsampledImage addFullSizeToContext: context];
1006
1005
  if (!source.memoryCache) {
1007
1006
  // Only on disk, also when it comes from there.
1008
1007
  context[SDWebImageContextStoreCacheType] = @(SDImageCacheTypeDisk);
1009
1008
  }
1010
1009
  return context;
1011
1010
  }
1012
- NSString* key = source.cacheKeyFilter ? source.cacheKey : source.url.absoluteString;
1013
- [FFFDownsampledImage addToContext: context forKey: key box: box cover: cover];
1011
+ [FFFDownsampledImage addToContext: context box: box cover: cover];
1014
1012
  if ([source isPhotoLibrary]) {
1015
1013
  // Photos makes the photo at the size asked for, with no file
1016
1014
  // downloaded: SDWebImage would keep this smaller copy on disk as the
@@ -1058,7 +1056,7 @@ NSString *FFFErrorMessage(NSError *error)
1058
1056
  };
1059
1057
  }
1060
1058
  CFTimeInterval startedAt = CACurrentMediaTime();
1061
- NSURL* url = context[SDWebImageContextImageThumbnailPixelSize] ? [FFFDownsampledImage loadURLForURL: source.url] : source.url;
1059
+ NSURL* url = source.url;
1062
1060
  if (!events) {
1063
1061
  // Only this load's image: SDWebImage would clear the view (to the
1064
1062
  // placeholder) if it fails, and a cancelled load can still complete.
@@ -1,5 +1,6 @@
1
1
  #import "FFFastImageViewManager.h"
2
2
  #import "FFFastImageView.h"
3
+ #import "FFFDownsampledImage.h"
3
4
 
4
5
  #import <SDWebImage/SDImageCache.h>
5
6
  #import <SDWebImage/SDWebImageManager.h>
@@ -128,13 +129,16 @@ RCT_REMAP_VIEW_PROPERTY(tintColor, imageColor, UIColor)
128
129
  static NSMutableArray<dispatch_block_t> *FFFPendingPreloads;
129
130
  static NSUInteger FFFPreloadsInFlight;
130
131
 
131
- // A preload's context: the source's headers and cache key.
132
+ // A preload's context: the source's headers and cache key, and the image
133
+ // class views use, so a view of the same url can share its download (see
134
+ // FFFDownsampledImage).
132
135
  static SDWebImageMutableContext *FFFPreloadContext(FFFastImageSource *source)
133
136
  {
134
137
  SDWebImageMutableContext *context = [NSMutableDictionary dictionary];
135
138
  context[SDWebImageContextDownloadRequestModifier] = source.requestModifier;
136
139
  context[SDWebImageContextImageLoader] = source.imageLoader;
137
140
  context[SDWebImageContextCacheKeyFilter] = source.cacheKeyFilter;
141
+ [FFFDownsampledImage addFullSizeToContext:context];
138
142
  return context;
139
143
  }
140
144
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-fast-image",
3
- "version": "8.27.2",
3
+ "version": "8.27.4",
4
4
  "description": "FastImage, performant React Native image component.",
5
5
  "keywords": [
6
6
  "cache",