lottie-ios 4.1.2 → 4.2.0

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.
Files changed (96) hide show
  1. package/.github/workflows/main.yml +27 -9
  2. package/Lottie.xcodeproj/project.pbxproj +158 -70
  3. package/Lottie.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +2 -2
  4. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/IDEFindNavigatorScopes.plist +5 -0
  5. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  6. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +258 -0
  7. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Expressions.xcexplist +13 -2
  8. package/Package.swift +2 -1
  9. package/README.md +3 -3
  10. package/Rakefile +8 -4
  11. package/Sources/Private/CoreAnimation/Animations/CALayer+addAnimation.swift +16 -2
  12. package/Sources/Private/CoreAnimation/Animations/CombinedShapeAnimation.swift +1 -1
  13. package/Sources/Private/CoreAnimation/Animations/CustomPathAnimation.swift +1 -1
  14. package/Sources/Private/CoreAnimation/Animations/EllipseAnimation.swift +1 -1
  15. package/Sources/Private/CoreAnimation/Animations/GradientAnimations.swift +6 -6
  16. package/Sources/Private/CoreAnimation/Animations/LayerProperty.swift +76 -7
  17. package/Sources/Private/CoreAnimation/Animations/OpacityAnimation.swift +1 -1
  18. package/Sources/Private/CoreAnimation/Animations/RectangleAnimation.swift +1 -1
  19. package/Sources/Private/CoreAnimation/Animations/ShapeAnimation.swift +66 -102
  20. package/Sources/Private/CoreAnimation/Animations/StarAnimation.swift +2 -2
  21. package/Sources/Private/CoreAnimation/Animations/StrokeAnimation.swift +3 -3
  22. package/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift +66 -17
  23. package/Sources/Private/CoreAnimation/CoreAnimationLayer.swift +55 -32
  24. package/Sources/Private/CoreAnimation/Extensions/Keyframes+combined.swift +16 -12
  25. package/Sources/Private/CoreAnimation/Layers/AnimationLayer.swift +3 -3
  26. package/Sources/Private/CoreAnimation/Layers/BaseCompositionLayer.swift +24 -11
  27. package/Sources/Private/CoreAnimation/Layers/ImageLayer.swift +2 -2
  28. package/Sources/Private/CoreAnimation/Layers/PreCompLayer.swift +1 -1
  29. package/Sources/Private/CoreAnimation/Layers/RepeaterLayer.swift +13 -2
  30. package/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift +9 -1
  31. package/Sources/Private/CoreAnimation/ValueProviderStore.swift +22 -11
  32. package/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.swift +1 -1
  33. package/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift +13 -2
  34. package/Sources/Private/MainThread/LayerContainers/Utility/LayerTransformNode.swift +16 -7
  35. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift +1 -1
  36. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift +2 -2
  37. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/RectNode.swift +1 -1
  38. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/StarNode.swift +2 -2
  39. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderContainers/GroupNode.swift +20 -8
  40. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift +1 -1
  41. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift +1 -1
  42. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift +1 -1
  43. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +1 -1
  44. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift +28 -9
  45. package/Sources/Private/Model/Assets/ImageAsset.swift +4 -3
  46. package/Sources/Private/Model/DotLottie/DotLottieAnimation.swift +2 -8
  47. package/Sources/Private/Model/DotLottie/DotLottieManifest.swift +3 -14
  48. package/Sources/Private/Model/DotLottie/DotLottieUtils.swift +11 -1
  49. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+BackingConfiguration.swift +147 -0
  50. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+Helpers.swift +351 -0
  51. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+MemoryFile.swift +183 -0
  52. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+Progress.swift +66 -0
  53. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+Reading.swift +144 -0
  54. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+ReadingDeprecated.swift +49 -0
  55. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+Writing.swift +385 -0
  56. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+WritingDeprecated.swift +91 -0
  57. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+ZIP64.swift +170 -0
  58. package/Sources/Private/Model/DotLottie/{Zip/ZipArchive.swift → ZipFoundation/Archive.swift} +150 -227
  59. package/Sources/Private/Model/DotLottie/ZipFoundation/Data+Compression.swift +403 -0
  60. package/Sources/Private/Model/DotLottie/ZipFoundation/Data+CompressionDeprecated.swift +44 -0
  61. package/Sources/Private/Model/DotLottie/{Zip → ZipFoundation}/Data+Serialization.swift +62 -0
  62. package/Sources/Private/Model/DotLottie/{Zip/ZipEntry+Serialization.swift → ZipFoundation/Entry+Serialization.swift} +7 -7
  63. package/Sources/Private/Model/DotLottie/{Zip/ZipEntry+ZIP64.swift → ZipFoundation/Entry+ZIP64.swift} +13 -19
  64. package/Sources/Private/Model/DotLottie/{Zip/ZipEntry.swift → ZipFoundation/Entry.swift} +141 -10
  65. package/Sources/Private/Model/DotLottie/ZipFoundation/FileManager+ZIP.swift +368 -0
  66. package/Sources/Private/Model/DotLottie/ZipFoundation/README.md +24 -0
  67. package/Sources/Private/Model/DotLottie/ZipFoundation/URL+ZIP.swift +32 -0
  68. package/Sources/Private/Model/Extensions/Bundle.swift +5 -14
  69. package/Sources/Private/Model/Keyframes/KeyframeGroup.swift +31 -8
  70. package/Sources/Private/Model/Objects/Transform.swift +58 -17
  71. package/Sources/Private/Model/ShapeItems/Repeater.swift +41 -7
  72. package/Sources/Private/Model/ShapeItems/ShapeTransform.swift +61 -7
  73. package/Sources/Private/Model/Text/TextAnimator.swift +37 -5
  74. package/Sources/Private/RootAnimationLayer.swift +3 -1
  75. package/Sources/Private/Utility/Extensions/AnimationKeypathExtension.swift +12 -4
  76. package/Sources/Private/Utility/Extensions/DataExtension.swift +14 -4
  77. package/Sources/Private/Utility/Primitives/BezierPathRoundExtension.swift +11 -0
  78. package/Sources/Private/Utility/Primitives/ColorExtension.swift +10 -13
  79. package/Sources/Private/Utility/Primitives/VectorsExtensions.swift +28 -6
  80. package/Sources/Public/Animation/LottieAnimationHelpers.swift +12 -10
  81. package/Sources/Public/Animation/LottieAnimationView.swift +213 -186
  82. package/Sources/Public/DotLottie/DotLottieFile.swift +11 -34
  83. package/Sources/Public/DotLottie/DotLottieFileHelpers.swift +101 -74
  84. package/Sources/Public/iOS/Compatibility/CompatibleAnimationView.swift +90 -0
  85. package/Sources/Public/iOS/LottieAnimationViewBase.swift +1 -1
  86. package/Sources/Public/macOS/LottieAnimationViewBase.macOS.swift +1 -1
  87. package/lottie-ios.podspec +1 -1
  88. package/package.json +1 -1
  89. package/LottieAnimation/LottieAnimation.xcodeproj/project.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  90. package/LottieAnimation/LottieAnimation.xcodeproj/project.xcworkspace/xcuserdata/valentinperignon.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  91. package/LottieAnimation/LottieAnimation.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
  92. package/LottieAnimation/LottieAnimation.xcodeproj/xcuserdata/valentinperignon.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +0 -6
  93. package/LottieAnimation/LottieAnimation.xcodeproj/xcuserdata/valentinperignon.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
  94. package/Sources/Private/Model/DotLottie/Zip/Data+Compression.swift +0 -134
  95. package/Sources/Private/Model/DotLottie/Zip/FileManager+ZIP.swift +0 -130
  96. package/Sources/Private/Utility/Interpolatable/KeyframeGroup+Extensions.swift +0 -59
@@ -11,13 +11,13 @@
11
11
  import CoreFoundation
12
12
  import Foundation
13
13
 
14
- // MARK: - ZipEntry
14
+ // MARK: - Entry
15
15
 
16
16
  /// A value that represents a file, a directory or a symbolic link within a ZIP `Archive`.
17
17
  ///
18
18
  /// You can retrieve instances of `Entry` from an `Archive` via subscripting or iteration.
19
19
  /// Entries are identified by their `path`.
20
- struct ZipEntry: Equatable {
20
+ struct Entry: Equatable {
21
21
 
22
22
  // MARK: Lifecycle
23
23
 
@@ -37,6 +37,34 @@ struct ZipEntry: Equatable {
37
37
 
38
38
  // MARK: Internal
39
39
 
40
+ /// The type of an `Entry` in a ZIP `Archive`.
41
+ enum EntryType: Int {
42
+ /// Indicates a regular file.
43
+ case file
44
+ /// Indicates a directory.
45
+ case directory
46
+ /// Indicates a symbolic link.
47
+ case symlink
48
+
49
+ init(mode: mode_t) {
50
+ switch mode & S_IFMT {
51
+ case S_IFDIR:
52
+ self = .directory
53
+ case S_IFLNK:
54
+ self = .symlink
55
+ default:
56
+ self = .file
57
+ }
58
+ }
59
+ }
60
+
61
+ enum OSType: UInt {
62
+ case msdos = 0
63
+ case unix = 3
64
+ case osx = 19
65
+ case unused = 20
66
+ }
67
+
40
68
  struct LocalFileHeader: DataSerializable {
41
69
  let localFileHeaderSignature = UInt32(localFileHeaderStructSignature)
42
70
  let versionNeededToExtract: UInt16
@@ -106,10 +134,6 @@ struct ZipEntry: Equatable {
106
134
  }
107
135
  }
108
136
 
109
- static let localFileHeaderStructSignature = 0x04034b50
110
- static let dataDescriptorStructSignature = 0x08074b50
111
- static let centralDirectoryStructSignature = 0x02014b50
112
-
113
137
  let centralDirectoryStructure: CentralDirectoryStructure
114
138
  let localFileHeader: LocalFileHeader
115
139
  let dataDescriptor: DefaultDataDescriptor?
@@ -135,16 +159,42 @@ struct ZipEntry: Equatable {
135
159
  /// The `CRC32` checksum of the receiver.
136
160
  ///
137
161
  /// - Note: Always returns `0` for entries of type `EntryType.directory`.
138
- var checksum: UInt32 {
162
+ var checksum: CRC32 {
139
163
  if centralDirectoryStructure.usesDataDescriptor {
140
164
  return zip64DataDescriptor?.crc32 ?? dataDescriptor?.crc32 ?? 0
141
165
  }
142
166
  return centralDirectoryStructure.crc32
143
167
  }
144
168
 
169
+ /// The `EntryType` of the receiver.
170
+ var type: EntryType {
171
+ // OS Type is stored in the upper byte of versionMadeBy
172
+ let osTypeRaw = centralDirectoryStructure.versionMadeBy >> 8
173
+ let osType = OSType(rawValue: UInt(osTypeRaw)) ?? .unused
174
+ var isDirectory = path.hasSuffix("/")
175
+ switch osType {
176
+ case .unix, .osx:
177
+ let mode = mode_t(centralDirectoryStructure.externalFileAttributes >> 16) & S_IFMT
178
+ switch mode {
179
+ case S_IFREG:
180
+ return .file
181
+ case S_IFDIR:
182
+ return .directory
183
+ case S_IFLNK:
184
+ return .symlink
185
+ default:
186
+ return isDirectory ? .directory : .file
187
+ }
188
+ case .msdos:
189
+ isDirectory = isDirectory || ((centralDirectoryStructure.externalFileAttributes >> 4) == 0x01)
190
+ fallthrough // For all other OSes we can only guess based on the directory suffix char
191
+ default: return isDirectory ? .directory : .file
192
+ }
193
+ }
194
+
145
195
  /// Indicates whether or not the receiver is compressed.
146
196
  var isCompressed: Bool {
147
- localFileHeader.compressionMethod != 0
197
+ localFileHeader.compressionMethod != CompressionMethod.none.rawValue
148
198
  }
149
199
 
150
200
  /// The size of the receiver's compressed data.
@@ -186,7 +236,7 @@ struct ZipEntry: Equatable {
186
236
  return dataOffset
187
237
  }
188
238
 
189
- static func == (lhs: ZipEntry, rhs: ZipEntry) -> Bool {
239
+ static func == (lhs: Entry, rhs: Entry) -> Bool {
190
240
  lhs.path == rhs.path
191
241
  && lhs.localFileHeader.crc32 == rhs.localFileHeader.crc32
192
242
  && lhs.centralDirectoryStructure.effectiveRelativeOffsetOfLocalHeader
@@ -203,7 +253,88 @@ struct ZipEntry: Equatable {
203
253
 
204
254
  }
205
255
 
206
- extension ZipEntry.CentralDirectoryStructure {
256
+ extension Entry.CentralDirectoryStructure {
257
+
258
+ init(
259
+ localFileHeader: Entry.LocalFileHeader,
260
+ fileAttributes: UInt32,
261
+ relativeOffset: UInt32,
262
+ extraField: (length: UInt16, data: Data))
263
+ {
264
+ versionMadeBy = UInt16(789)
265
+ versionNeededToExtract = localFileHeader.versionNeededToExtract
266
+ generalPurposeBitFlag = localFileHeader.generalPurposeBitFlag
267
+ compressionMethod = localFileHeader.compressionMethod
268
+ lastModFileTime = localFileHeader.lastModFileTime
269
+ lastModFileDate = localFileHeader.lastModFileDate
270
+ crc32 = localFileHeader.crc32
271
+ compressedSize = localFileHeader.compressedSize
272
+ uncompressedSize = localFileHeader.uncompressedSize
273
+ fileNameLength = localFileHeader.fileNameLength
274
+ extraFieldLength = extraField.length
275
+ fileCommentLength = UInt16(0)
276
+ diskNumberStart = UInt16(0)
277
+ internalFileAttributes = UInt16(0)
278
+ externalFileAttributes = fileAttributes
279
+ relativeOffsetOfLocalHeader = relativeOffset
280
+ fileNameData = localFileHeader.fileNameData
281
+ extraFieldData = extraField.data
282
+ fileCommentData = Data()
283
+ if
284
+ let zip64ExtendedInformation = Entry.ZIP64ExtendedInformation.scanForZIP64Field(
285
+ in: extraFieldData,
286
+ fields: validFields)
287
+ {
288
+ extraFields = [zip64ExtendedInformation]
289
+ }
290
+ }
291
+
292
+ init(
293
+ centralDirectoryStructure: Entry.CentralDirectoryStructure,
294
+ zip64ExtendedInformation: Entry.ZIP64ExtendedInformation?,
295
+ relativeOffset: UInt32)
296
+ {
297
+ if let existingInfo = zip64ExtendedInformation {
298
+ extraFieldData = existingInfo.data
299
+ versionNeededToExtract = max(
300
+ centralDirectoryStructure.versionNeededToExtract,
301
+ Archive.Version.v45.rawValue)
302
+ } else {
303
+ extraFieldData = centralDirectoryStructure.extraFieldData
304
+ let existingVersion = centralDirectoryStructure.versionNeededToExtract
305
+ versionNeededToExtract = existingVersion < Archive.Version.v45.rawValue
306
+ ? centralDirectoryStructure.versionNeededToExtract
307
+ : Archive.Version.v20.rawValue
308
+ }
309
+ extraFieldLength = UInt16(extraFieldData.count)
310
+ relativeOffsetOfLocalHeader = relativeOffset
311
+ versionMadeBy = centralDirectoryStructure.versionMadeBy
312
+ generalPurposeBitFlag = centralDirectoryStructure.generalPurposeBitFlag
313
+ compressionMethod = centralDirectoryStructure.compressionMethod
314
+ lastModFileTime = centralDirectoryStructure.lastModFileTime
315
+ lastModFileDate = centralDirectoryStructure.lastModFileDate
316
+ crc32 = centralDirectoryStructure.crc32
317
+ compressedSize = centralDirectoryStructure.compressedSize
318
+ uncompressedSize = centralDirectoryStructure.uncompressedSize
319
+ fileNameLength = centralDirectoryStructure.fileNameLength
320
+ fileCommentLength = centralDirectoryStructure.fileCommentLength
321
+ diskNumberStart = centralDirectoryStructure.diskNumberStart
322
+ internalFileAttributes = centralDirectoryStructure.internalFileAttributes
323
+ externalFileAttributes = centralDirectoryStructure.externalFileAttributes
324
+ fileNameData = centralDirectoryStructure.fileNameData
325
+ fileCommentData = centralDirectoryStructure.fileCommentData
326
+ if
327
+ let zip64ExtendedInformation = Entry.ZIP64ExtendedInformation.scanForZIP64Field(
328
+ in: extraFieldData,
329
+ fields: validFields)
330
+ {
331
+ extraFields = [zip64ExtendedInformation]
332
+ }
333
+ }
334
+ }
335
+
336
+ extension Entry.CentralDirectoryStructure {
337
+
207
338
  var effectiveCompressedSize: UInt64 {
208
339
  if isZIP64, let compressedSize = zip64ExtendedInformation?.compressedSize, compressedSize > 0 {
209
340
  return compressedSize
@@ -0,0 +1,368 @@
1
+ //
2
+ // FileManager+ZIP.swift
3
+ // ZIPFoundation
4
+ //
5
+ // Copyright © 2017-2021 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors.
6
+ // Released under the MIT License.
7
+ //
8
+ // See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information.
9
+ //
10
+
11
+ import Foundation
12
+
13
+ extension FileManager {
14
+ typealias CentralDirectoryStructure = Entry.CentralDirectoryStructure
15
+
16
+ class func attributes(from entry: Entry) -> [FileAttributeKey: Any] {
17
+ let centralDirectoryStructure = entry.centralDirectoryStructure
18
+ let entryType = entry.type
19
+ let fileTime = centralDirectoryStructure.lastModFileTime
20
+ let fileDate = centralDirectoryStructure.lastModFileDate
21
+ let defaultPermissions = entryType == .directory ? defaultDirectoryPermissions : defaultFilePermissions
22
+ var attributes = [.posixPermissions: defaultPermissions] as [FileAttributeKey: Any]
23
+ // Certain keys are not yet supported in swift-corelibs
24
+ #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
25
+ attributes[.modificationDate] = Date(dateTime: (fileDate, fileTime))
26
+ #endif
27
+ let versionMadeBy = centralDirectoryStructure.versionMadeBy
28
+ guard let osType = Entry.OSType(rawValue: UInt(versionMadeBy >> 8)) else { return attributes }
29
+
30
+ let externalFileAttributes = centralDirectoryStructure.externalFileAttributes
31
+ let permissions = permissions(for: externalFileAttributes, osType: osType, entryType: entryType)
32
+ attributes[.posixPermissions] = NSNumber(value: permissions)
33
+ return attributes
34
+ }
35
+
36
+ class func permissions(
37
+ for externalFileAttributes: UInt32,
38
+ osType: Entry.OSType,
39
+ entryType: Entry.EntryType)
40
+ -> UInt16
41
+ {
42
+ switch osType {
43
+ case .unix, .osx:
44
+ let permissions = mode_t(externalFileAttributes >> 16) & ~S_IFMT
45
+ let defaultPermissions = entryType == .directory ? defaultDirectoryPermissions : defaultFilePermissions
46
+ return permissions == 0 ? defaultPermissions : UInt16(permissions)
47
+ default:
48
+ return entryType == .directory ? defaultDirectoryPermissions : defaultFilePermissions
49
+ }
50
+ }
51
+
52
+ class func externalFileAttributesForEntry(of type: Entry.EntryType, permissions: UInt16) -> UInt32 {
53
+ var typeInt: UInt16
54
+ switch type {
55
+ case .file:
56
+ typeInt = UInt16(S_IFREG)
57
+ case .directory:
58
+ typeInt = UInt16(S_IFDIR)
59
+ case .symlink:
60
+ typeInt = UInt16(S_IFLNK)
61
+ }
62
+ var externalFileAttributes = UInt32(typeInt | UInt16(permissions))
63
+ externalFileAttributes = (externalFileAttributes << 16)
64
+ return externalFileAttributes
65
+ }
66
+
67
+ class func permissionsForItem(at URL: URL) throws -> UInt16 {
68
+ let fileManager = FileManager()
69
+ let entryFileSystemRepresentation = fileManager.fileSystemRepresentation(withPath: URL.path)
70
+ var fileStat = stat()
71
+ lstat(entryFileSystemRepresentation, &fileStat)
72
+ let permissions = fileStat.st_mode
73
+ return UInt16(permissions)
74
+ }
75
+
76
+ class func fileModificationDateTimeForItem(at url: URL) throws -> Date {
77
+ let fileManager = FileManager()
78
+ guard fileManager.itemExists(at: url) else {
79
+ throw CocoaError(.fileReadNoSuchFile, userInfo: [NSFilePathErrorKey: url.path])
80
+ }
81
+ let entryFileSystemRepresentation = fileManager.fileSystemRepresentation(withPath: url.path)
82
+ var fileStat = stat()
83
+ lstat(entryFileSystemRepresentation, &fileStat)
84
+ #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
85
+ let modTimeSpec = fileStat.st_mtimespec
86
+ #else
87
+ let modTimeSpec = fileStat.st_mtim
88
+ #endif
89
+
90
+ let timeStamp = TimeInterval(modTimeSpec.tv_sec) + TimeInterval(modTimeSpec.tv_nsec) / 1000000000.0
91
+ let modDate = Date(timeIntervalSince1970: timeStamp)
92
+ return modDate
93
+ }
94
+
95
+ class func fileSizeForItem(at url: URL) throws -> Int64 {
96
+ let fileManager = FileManager()
97
+ guard fileManager.itemExists(at: url) else {
98
+ throw CocoaError(.fileReadNoSuchFile, userInfo: [NSFilePathErrorKey: url.path])
99
+ }
100
+ let entryFileSystemRepresentation = fileManager.fileSystemRepresentation(withPath: url.path)
101
+ var fileStat = stat()
102
+ lstat(entryFileSystemRepresentation, &fileStat)
103
+ guard fileStat.st_size >= 0 else {
104
+ throw CocoaError(.fileReadTooLarge, userInfo: [NSFilePathErrorKey: url.path])
105
+ }
106
+ // `st_size` is a signed int value
107
+ return Int64(fileStat.st_size)
108
+ }
109
+
110
+ class func typeForItem(at url: URL) throws -> Entry.EntryType {
111
+ let fileManager = FileManager()
112
+ guard url.isFileURL, fileManager.itemExists(at: url) else {
113
+ throw CocoaError(.fileReadNoSuchFile, userInfo: [NSFilePathErrorKey: url.path])
114
+ }
115
+ let entryFileSystemRepresentation = fileManager.fileSystemRepresentation(withPath: url.path)
116
+ var fileStat = stat()
117
+ lstat(entryFileSystemRepresentation, &fileStat)
118
+ return Entry.EntryType(mode: mode_t(fileStat.st_mode))
119
+ }
120
+
121
+ /// Zips the file or directory contents at the specified source URL to the destination URL.
122
+ ///
123
+ /// If the item at the source URL is a directory, the directory itself will be
124
+ /// represented within the ZIP `Archive`. Calling this method with a directory URL
125
+ /// `file:///path/directory/` will create an archive with a `directory/` entry at the root level.
126
+ /// You can override this behavior by passing `false` for `shouldKeepParent`. In that case, the contents
127
+ /// of the source directory will be placed at the root of the archive.
128
+ /// - Parameters:
129
+ /// - sourceURL: The file URL pointing to an existing file or directory.
130
+ /// - destinationURL: The file URL that identifies the destination of the zip operation.
131
+ /// - shouldKeepParent: Indicates that the directory name of a source item should be used as root element
132
+ /// within the archive. Default is `true`.
133
+ /// - compressionMethod: Indicates the `CompressionMethod` that should be applied.
134
+ /// By default, `zipItem` will create uncompressed archives.
135
+ /// - progress: A progress object that can be used to track or cancel the zip operation.
136
+ /// - Throws: Throws an error if the source item does not exist or the destination URL is not writable.
137
+ func zipItem(
138
+ at sourceURL: URL,
139
+ to destinationURL: URL,
140
+ shouldKeepParent: Bool = true,
141
+ compressionMethod: CompressionMethod = .none,
142
+ progress: Progress? = nil)
143
+ throws
144
+ {
145
+ let fileManager = FileManager()
146
+ guard fileManager.itemExists(at: sourceURL) else {
147
+ throw CocoaError(.fileReadNoSuchFile, userInfo: [NSFilePathErrorKey: sourceURL.path])
148
+ }
149
+ guard !fileManager.itemExists(at: destinationURL) else {
150
+ throw CocoaError(.fileWriteFileExists, userInfo: [NSFilePathErrorKey: destinationURL.path])
151
+ }
152
+ guard let archive = Archive(url: destinationURL, accessMode: .create) else {
153
+ throw Archive.ArchiveError.unwritableArchive
154
+ }
155
+ let isDirectory = try FileManager.typeForItem(at: sourceURL) == .directory
156
+ if isDirectory {
157
+ let subPaths = try subpathsOfDirectory(atPath: sourceURL.path)
158
+ var totalUnitCount = Int64(0)
159
+ if let progress = progress {
160
+ totalUnitCount = subPaths.reduce(Int64(0)) {
161
+ let itemURL = sourceURL.appendingPathComponent($1)
162
+ let itemSize = archive.totalUnitCountForAddingItem(at: itemURL)
163
+ return $0 + itemSize
164
+ }
165
+ progress.totalUnitCount = totalUnitCount
166
+ }
167
+
168
+ // If the caller wants to keep the parent directory, we use the lastPathComponent of the source URL
169
+ // as common base for all entries (similar to macOS' Archive Utility.app)
170
+ let directoryPrefix = sourceURL.lastPathComponent
171
+ for entryPath in subPaths {
172
+ let finalEntryPath = shouldKeepParent ? directoryPrefix + "/" + entryPath : entryPath
173
+ let finalBaseURL = shouldKeepParent ? sourceURL.deletingLastPathComponent() : sourceURL
174
+ if let progress = progress {
175
+ let itemURL = sourceURL.appendingPathComponent(entryPath)
176
+ let entryProgress = archive.makeProgressForAddingItem(at: itemURL)
177
+ progress.addChild(entryProgress, withPendingUnitCount: entryProgress.totalUnitCount)
178
+ try archive.addEntry(
179
+ with: finalEntryPath,
180
+ relativeTo: finalBaseURL,
181
+ compressionMethod: compressionMethod,
182
+ progress: entryProgress)
183
+ } else {
184
+ try archive.addEntry(
185
+ with: finalEntryPath,
186
+ relativeTo: finalBaseURL,
187
+ compressionMethod: compressionMethod)
188
+ }
189
+ }
190
+ } else {
191
+ progress?.totalUnitCount = archive.totalUnitCountForAddingItem(at: sourceURL)
192
+ let baseURL = sourceURL.deletingLastPathComponent()
193
+ try archive.addEntry(
194
+ with: sourceURL.lastPathComponent,
195
+ relativeTo: baseURL,
196
+ compressionMethod: compressionMethod,
197
+ progress: progress)
198
+ }
199
+ }
200
+
201
+ /// Unzips the contents at the specified source URL to the destination URL.
202
+ ///
203
+ /// - Parameters:
204
+ /// - sourceURL: The file URL pointing to an existing ZIP file.
205
+ /// - destinationURL: The file URL that identifies the destination directory of the unzip operation.
206
+ /// - skipCRC32: Optional flag to skip calculation of the CRC32 checksum to improve performance.
207
+ /// - progress: A progress object that can be used to track or cancel the unzip operation.
208
+ /// - preferredEncoding: Encoding for entry paths. Overrides the encoding specified in the archive.
209
+ /// - Throws: Throws an error if the source item does not exist or the destination URL is not writable.
210
+ func unzipItem(
211
+ at sourceURL: URL,
212
+ to destinationURL: URL,
213
+ skipCRC32: Bool = false,
214
+ progress: Progress? = nil,
215
+ preferredEncoding: String.Encoding? = nil)
216
+ throws
217
+ {
218
+ let fileManager = FileManager()
219
+ guard fileManager.itemExists(at: sourceURL) else {
220
+ throw CocoaError(.fileReadNoSuchFile, userInfo: [NSFilePathErrorKey: sourceURL.path])
221
+ }
222
+ guard let archive = Archive(url: sourceURL, accessMode: .read, preferredEncoding: preferredEncoding) else {
223
+ throw Archive.ArchiveError.unreadableArchive
224
+ }
225
+ // Defer extraction of symlinks until all files & directories have been created.
226
+ // This is necessary because we can't create links to files that haven't been created yet.
227
+ let sortedEntries = archive.sorted { left, right -> Bool in
228
+ switch (left.type, right.type) {
229
+ case (.directory, .file): return true
230
+ case (.directory, .symlink): return true
231
+ case (.file, .symlink): return true
232
+ default: return false
233
+ }
234
+ }
235
+ var totalUnitCount = Int64(0)
236
+ if let progress = progress {
237
+ totalUnitCount = sortedEntries.reduce(0) { $0 + archive.totalUnitCountForReading($1) }
238
+ progress.totalUnitCount = totalUnitCount
239
+ }
240
+
241
+ for entry in sortedEntries {
242
+ let path = preferredEncoding == nil ? entry.path : entry.path(using: preferredEncoding!)
243
+ let entryURL = destinationURL.appendingPathComponent(path)
244
+ guard entryURL.isContained(in: destinationURL) else {
245
+ throw CocoaError(
246
+ .fileReadInvalidFileName,
247
+ userInfo: [NSFilePathErrorKey: entryURL.path])
248
+ }
249
+ let crc32: CRC32
250
+ if let progress = progress {
251
+ let entryProgress = archive.makeProgressForReading(entry)
252
+ progress.addChild(entryProgress, withPendingUnitCount: entryProgress.totalUnitCount)
253
+ crc32 = try archive.extract(entry, to: entryURL, skipCRC32: skipCRC32, progress: entryProgress)
254
+ } else {
255
+ crc32 = try archive.extract(entry, to: entryURL, skipCRC32: skipCRC32)
256
+ }
257
+
258
+ func verifyChecksumIfNecessary() throws {
259
+ if skipCRC32 == false, crc32 != entry.checksum {
260
+ throw Archive.ArchiveError.invalidCRC32
261
+ }
262
+ }
263
+ try verifyChecksumIfNecessary()
264
+ }
265
+ }
266
+
267
+ // MARK: - Helpers
268
+
269
+ func itemExists(at url: URL) -> Bool {
270
+ // Use `URL.checkResourceIsReachable()` instead of `FileManager.fileExists()` here
271
+ // because we don't want implicit symlink resolution.
272
+ // As per documentation, `FileManager.fileExists()` traverses symlinks and therefore a broken symlink
273
+ // would throw a `.fileReadNoSuchFile` false positive error.
274
+ // For ZIP files it may be intended to archive "broken" symlinks because they might be
275
+ // resolvable again when extracting the archive to a different destination.
276
+ (try? url.checkResourceIsReachable()) == true
277
+ }
278
+
279
+ func createParentDirectoryStructure(for url: URL) throws {
280
+ let parentDirectoryURL = url.deletingLastPathComponent()
281
+ try createDirectory(at: parentDirectoryURL, withIntermediateDirectories: true, attributes: nil)
282
+ }
283
+
284
+ }
285
+
286
+ extension Date {
287
+
288
+ // MARK: Lifecycle
289
+
290
+ init(dateTime: (UInt16, UInt16)) {
291
+ var msdosDateTime = Int(dateTime.0)
292
+ msdosDateTime <<= 16
293
+ msdosDateTime |= Int(dateTime.1)
294
+ var unixTime = tm()
295
+ unixTime.tm_sec = Int32((msdosDateTime & 31) * 2)
296
+ unixTime.tm_min = Int32((msdosDateTime >> 5) & 63)
297
+ unixTime.tm_hour = Int32((Int(dateTime.1) >> 11) & 31)
298
+ unixTime.tm_mday = Int32((msdosDateTime >> 16) & 31)
299
+ unixTime.tm_mon = Int32((msdosDateTime >> 21) & 15)
300
+ unixTime.tm_mon -= 1 // UNIX time struct month entries are zero based.
301
+ unixTime.tm_year = Int32(1980 + (msdosDateTime >> 25))
302
+ unixTime.tm_year -= 1900 // UNIX time structs count in "years since 1900".
303
+ let time = timegm(&unixTime)
304
+ self = Date(timeIntervalSince1970: TimeInterval(time))
305
+ }
306
+
307
+ // MARK: Internal
308
+
309
+ var fileModificationDateTime: (UInt16, UInt16) {
310
+ return (self.fileModificationDate, self.fileModificationTime)
311
+ }
312
+
313
+ var fileModificationDate: UInt16 {
314
+ var time = time_t(timeIntervalSince1970)
315
+ guard let unixTime = gmtime(&time) else {
316
+ return 0
317
+ }
318
+ var year = unixTime.pointee.tm_year + 1900 // UNIX time structs count in "years since 1900".
319
+ // ZIP uses the MSDOS date format which has a valid range of 1980 - 2099.
320
+ year = year >= 1980 ? year : 1980
321
+ year = year <= 2099 ? year : 2099
322
+ let month = unixTime.pointee.tm_mon + 1 // UNIX time struct month entries are zero based.
323
+ let day = unixTime.pointee.tm_mday
324
+ return (UInt16)(day + (month * 32) + ((year - 1980) * 512))
325
+ }
326
+
327
+ var fileModificationTime: UInt16 {
328
+ var time = time_t(timeIntervalSince1970)
329
+ guard let unixTime = gmtime(&time) else {
330
+ return 0
331
+ }
332
+ let hour = unixTime.pointee.tm_hour
333
+ let minute = unixTime.pointee.tm_min
334
+ let second = unixTime.pointee.tm_sec
335
+ return (UInt16)((second / 2) + (minute * 32) + (hour * 2048))
336
+ }
337
+ }
338
+
339
+ #if swift(>=4.2)
340
+ #else
341
+
342
+ #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
343
+ #else
344
+
345
+ // The swift-corelibs-foundation version of NSError.swift was missing a convenience method to create
346
+ // error objects from error codes. (https://github.com/apple/swift-corelibs-foundation/pull/1420)
347
+ // We have to provide an implementation for non-Darwin platforms using Swift versions < 4.2.
348
+
349
+ extension CocoaError {
350
+ static func error(_ code: CocoaError.Code, userInfo: [AnyHashable: Any]? = nil, url: URL? = nil) -> Error {
351
+ var info: [String: Any] = userInfo as? [String: Any] ?? [:]
352
+ if let url = url {
353
+ info[NSURLErrorKey] = url
354
+ }
355
+ return NSError(domain: NSCocoaErrorDomain, code: code.rawValue, userInfo: info)
356
+ }
357
+ }
358
+
359
+ #endif
360
+ #endif
361
+
362
+ extension URL {
363
+ func isContained(in parentDirectoryURL: URL) -> Bool {
364
+ // Ensure this URL is contained in the passed in URL
365
+ let parentDirectoryURL = URL(fileURLWithPath: parentDirectoryURL.path, isDirectory: true).standardized
366
+ return standardized.absoluteString.hasPrefix(parentDirectoryURL.absoluteString)
367
+ }
368
+ }
@@ -0,0 +1,24 @@
1
+ ## ZipFoundation
2
+
3
+ This directory includes the source code of the ZIPFoundation library, from the following release:
4
+ https://github.com/weichsel/ZIPFoundation/releases/tag/0.9.16
5
+
6
+ Lottie is distributed via multiple package managers (SPM, Cocoapods, Carthage, and NPM),
7
+ each with different packaging and compilation requirements.
8
+
9
+ Due to limitations of these package managers, we can't depend on / import
10
+ a separate ZIPFoundation module / library. Instead, we include the source
11
+ directly within the Lottie library and compile everything as a single unit.
12
+
13
+ ### Update instructions
14
+
15
+ From time to time we may need to update to a more recent version of ZIPFoundation.
16
+ When doing this, follow these steps:
17
+
18
+ 1. Download the latest release from https://github.com/weichsel/ZIPFoundation
19
+ and replace the source code in this directory with the updated code.
20
+
21
+ 2. Update the URL at the top of this file to indicate what release is being used.
22
+
23
+ 3. Change all of the `public` symbols defined in this module to instead be `internal`
24
+ to prevent Lottie from exposing any ZIPFoundation APIs.
@@ -0,0 +1,32 @@
1
+ //
2
+ // URL+ZIP.swift
3
+ // ZIPFoundation
4
+ //
5
+ // Copyright © 2017-2021 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors.
6
+ // Released under the MIT License.
7
+ //
8
+ // See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information.
9
+ //
10
+
11
+ import Foundation
12
+
13
+ extension URL {
14
+
15
+ static func temporaryReplacementDirectoryURL(for archive: Archive) -> URL {
16
+ #if swift(>=5.0) || os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
17
+ if
18
+ archive.url.isFileURL,
19
+ let tempDir = try? FileManager().url(
20
+ for: .itemReplacementDirectory,
21
+ in: .userDomainMask,
22
+ appropriateFor: archive.url,
23
+ create: true)
24
+ {
25
+ return tempDir
26
+ }
27
+ #endif
28
+
29
+ return URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(
30
+ ProcessInfo.processInfo.globallyUniqueString)
31
+ }
32
+ }
@@ -4,36 +4,27 @@ import UIKit
4
4
  #endif
5
5
 
6
6
  extension Bundle {
7
- func getAnimationData(_ name: String, subdirectory: String? = nil) throws -> Data? {
7
+ func getAnimationData(_ name: String, subdirectory: String? = nil) throws -> Data {
8
8
  // Check for files in the bundle at the given path
9
9
  let name = name.removingJSONSuffix()
10
10
  if let url = url(forResource: name, withExtension: "json", subdirectory: subdirectory) {
11
11
  return try Data(contentsOf: url)
12
12
  }
13
13
 
14
- // Check for data assets (not available on macOS)
15
- #if os(iOS) || os(tvOS) || os(watchOS) || targetEnvironment(macCatalyst)
14
+ // Check for data assets
16
15
  let assetKey = subdirectory != nil ? "\(subdirectory ?? "")/\(name)" : name
17
- return NSDataAsset(name: assetKey, bundle: self)?.data
18
- #else
19
- return nil
20
- #endif
16
+ return try Data(assetName: assetKey, in: self)
21
17
  }
22
18
 
23
- func dotLottieData(_ name: String, subdirectory: String? = nil) throws -> Data? {
19
+ func dotLottieData(_ name: String, subdirectory: String? = nil) throws -> Data {
24
20
  // Check for files in the bundle at the given path
25
21
  let name = name.removingDotLottieSuffix()
26
22
  if let url = url(forResource: name, withExtension: "lottie", subdirectory: subdirectory) {
27
23
  return try Data(contentsOf: url)
28
24
  }
29
25
 
30
- // Check for data assets (not available on macOS)
31
- #if os(iOS) || os(tvOS) || os(watchOS) || targetEnvironment(macCatalyst)
32
26
  let assetKey = subdirectory != nil ? "\(subdirectory ?? "")/\(name)" : name
33
- return NSDataAsset(name: assetKey, bundle: self)?.data
34
- #else
35
- return nil
36
- #endif
27
+ return try Data(assetName: assetKey, in: self)
37
28
  }
38
29
  }
39
30