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
@@ -19,22 +19,35 @@ final class KeyframeGroup<T> {
19
19
 
20
20
  // MARK: Lifecycle
21
21
 
22
- init(keyframes: ContiguousArray<Keyframe<T>>) {
22
+ init(
23
+ keyframes: ContiguousArray<Keyframe<T>>,
24
+ unsupportedAfterEffectsExpression: String? = nil)
25
+ {
23
26
  self.keyframes = keyframes
27
+ self.unsupportedAfterEffectsExpression = unsupportedAfterEffectsExpression
24
28
  }
25
29
 
26
- init(_ value: T) {
30
+ init(
31
+ _ value: T,
32
+ unsupportedAfterEffectsExpression: String? = nil)
33
+ {
27
34
  keyframes = [Keyframe(value)]
35
+ self.unsupportedAfterEffectsExpression = unsupportedAfterEffectsExpression
28
36
  }
29
37
 
30
38
  // MARK: Internal
31
39
 
32
40
  enum KeyframeWrapperKey: String, CodingKey {
33
41
  case keyframeData = "k"
42
+ case unsupportedAfterEffectsExpression = "x"
34
43
  }
35
44
 
36
45
  let keyframes: ContiguousArray<Keyframe<T>>
37
46
 
47
+ /// lottie-ios doesn't support After Effects expressions, but we parse them so we can log diagnostics.
48
+ /// More info: https://helpx.adobe.com/after-effects/using/expression-basics.html
49
+ let unsupportedAfterEffectsExpression: String?
50
+
38
51
  }
39
52
 
40
53
  // MARK: Decodable
@@ -42,10 +55,13 @@ final class KeyframeGroup<T> {
42
55
  extension KeyframeGroup: Decodable where T: Decodable {
43
56
  convenience init(from decoder: Decoder) throws {
44
57
  let container = try decoder.container(keyedBy: KeyframeWrapperKey.self)
58
+ let unsupportedAfterEffectsExpression = try? container.decode(String.self, forKey: .unsupportedAfterEffectsExpression)
45
59
 
46
60
  if let keyframeData: T = try? container.decode(T.self, forKey: .keyframeData) {
47
61
  /// Try to decode raw value; No keyframe data.
48
- self.init(keyframes: [Keyframe<T>(keyframeData)])
62
+ self.init(
63
+ keyframes: [Keyframe<T>(keyframeData)],
64
+ unsupportedAfterEffectsExpression: unsupportedAfterEffectsExpression)
49
65
  } else {
50
66
  // Decode and array of keyframes.
51
67
  //
@@ -89,7 +105,9 @@ extension KeyframeGroup: Decodable where T: Decodable {
89
105
  spatialOutTangent: keyframeData.spatialOutTangent))
90
106
  previousKeyframeData = keyframeData
91
107
  }
92
- self.init(keyframes: keyframes)
108
+ self.init(
109
+ keyframes: keyframes,
110
+ unsupportedAfterEffectsExpression: unsupportedAfterEffectsExpression)
93
111
  }
94
112
  }
95
113
  }
@@ -129,6 +147,7 @@ extension KeyframeGroup: Encodable where T: Encodable {
129
147
  extension KeyframeGroup: DictionaryInitializable where T: AnyInitializable {
130
148
  convenience init(dictionary: [String: Any]) throws {
131
149
  var keyframes = ContiguousArray<Keyframe<T>>()
150
+ let unsupportedAfterEffectsExpression = dictionary[KeyframeWrapperKey.unsupportedAfterEffectsExpression.rawValue] as? String
132
151
  if
133
152
  let rawValue = dictionary[KeyframeWrapperKey.keyframeData.rawValue],
134
153
  let value = try? T(value: rawValue)
@@ -162,7 +181,9 @@ extension KeyframeGroup: DictionaryInitializable where T: AnyInitializable {
162
181
  }
163
182
  }
164
183
 
165
- self.init(keyframes: keyframes)
184
+ self.init(
185
+ keyframes: keyframes,
186
+ unsupportedAfterEffectsExpression: unsupportedAfterEffectsExpression)
166
187
  }
167
188
  }
168
189
 
@@ -199,9 +220,11 @@ extension Keyframe {
199
220
  extension KeyframeGroup {
200
221
  /// Maps the values of each individual keyframe in this group
201
222
  func map<NewValue>(_ transformation: (T) throws -> NewValue) rethrows -> KeyframeGroup<NewValue> {
202
- KeyframeGroup<NewValue>(keyframes: ContiguousArray(try keyframes.map { keyframe in
203
- keyframe.withValue(try transformation(keyframe.value))
204
- }))
223
+ KeyframeGroup<NewValue>(
224
+ keyframes: ContiguousArray(try keyframes.map { keyframe in
225
+ keyframe.withValue(try transformation(keyframe.value))
226
+ }),
227
+ unsupportedAfterEffectsExpression: unsupportedAfterEffectsExpression)
205
228
  }
206
229
  }
207
230
 
@@ -55,14 +55,25 @@ final class Transform: Codable, DictionaryInitializable {
55
55
  KeyframeGroup(LottieVector3D(x: Double(100), y: 100, z: 100))
56
56
 
57
57
  // Rotation
58
- if let rotationZ = try container.decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .rotationZ) {
59
- rotation = rotationZ
58
+ if let rotation = try container.decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .rotationX) {
59
+ rotationX = rotation
60
60
  } else {
61
- rotation = try container
62
- .decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .rotation) ?? KeyframeGroup(LottieVector1D(0))
61
+ rotationX = KeyframeGroup(LottieVector1D(0))
62
+ }
63
+
64
+ if let rotation = try container.decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .rotationY) {
65
+ rotationY = rotation
66
+ } else {
67
+ rotationY = KeyframeGroup(LottieVector1D(0))
63
68
  }
64
- rotationZ = nil
65
69
 
70
+ if let rotation = try container.decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .rotationZ) {
71
+ rotationZ = rotation
72
+ } else {
73
+ rotationZ = try container
74
+ .decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .rotation) ?? KeyframeGroup(LottieVector1D(0))
75
+ }
76
+ rotation = nil
66
77
  // Opacity
67
78
  opacity = try container
68
79
  .decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .opacity) ?? KeyframeGroup(LottieVector1D(100))
@@ -114,20 +125,40 @@ final class Transform: Codable, DictionaryInitializable {
114
125
  } else {
115
126
  scale = KeyframeGroup(LottieVector3D(x: Double(100), y: 100, z: 100))
116
127
  }
128
+
117
129
  if
118
- let rotationDictionary = dictionary[CodingKeys.rotationZ.rawValue] as? [String: Any],
130
+ let rotationDictionary = dictionary[CodingKeys.rotationX.rawValue] as? [String: Any],
119
131
  let rotation = try? KeyframeGroup<LottieVector1D>(dictionary: rotationDictionary)
120
132
  {
121
- self.rotation = rotation
122
- } else if
123
- let rotationDictionary = dictionary[CodingKeys.rotation.rawValue] as? [String: Any],
133
+ rotationX = rotation
134
+ } else {
135
+ rotationX = KeyframeGroup(LottieVector1D(0))
136
+ }
137
+
138
+ if
139
+ let rotationDictionary = dictionary[CodingKeys.rotationY.rawValue] as? [String: Any],
124
140
  let rotation = try? KeyframeGroup<LottieVector1D>(dictionary: rotationDictionary)
125
141
  {
126
- self.rotation = rotation
142
+ rotationY = rotation
127
143
  } else {
128
- rotation = KeyframeGroup(LottieVector1D(0))
144
+ rotationY = KeyframeGroup(LottieVector1D(0))
129
145
  }
130
- rotationZ = nil
146
+
147
+ if
148
+ let rotationDictionary = dictionary[CodingKeys.rotation.rawValue] as? [String: Any],
149
+ let rotation = try? KeyframeGroup<LottieVector1D>(dictionary: rotationDictionary)
150
+ {
151
+ rotationZ = rotation
152
+ } else if
153
+ let rotationDictionary = dictionary[CodingKeys.rotationZ.rawValue] as? [String: Any],
154
+ let rotation = try? KeyframeGroup<LottieVector1D>(dictionary: rotationDictionary)
155
+ {
156
+ rotationZ = rotation
157
+ }
158
+ else {
159
+ rotationZ = KeyframeGroup(LottieVector1D(0))
160
+ }
161
+ rotation = nil
131
162
  if
132
163
  let opacityDictionary = dictionary[CodingKeys.opacity.rawValue] as? [String: Any],
133
164
  let opacity = try? KeyframeGroup<LottieVector1D>(dictionary: opacityDictionary)
@@ -147,6 +178,8 @@ final class Transform: Codable, DictionaryInitializable {
147
178
  case positionY = "py"
148
179
  case scale = "s"
149
180
  case rotation = "r"
181
+ case rotationX = "rx"
182
+ case rotationY = "ry"
150
183
  case rotationZ = "rz"
151
184
  case opacity = "o"
152
185
  }
@@ -169,15 +202,23 @@ final class Transform: Codable, DictionaryInitializable {
169
202
  /// The positionY of the transform. This is nil if the position property is set.
170
203
  let positionY: KeyframeGroup<LottieVector1D>?
171
204
 
172
- /// The scale of the transform
205
+ /// The scale of the transform.
173
206
  let scale: KeyframeGroup<LottieVector3D>
174
207
 
175
- /// The rotation of the transform. Note: This is single dimensional rotation.
176
- let rotation: KeyframeGroup<LottieVector1D>
208
+ /// The rotation of the transform on X axis.
209
+ let rotationX: KeyframeGroup<LottieVector1D>
210
+
211
+ /// The rotation of the transform on Y axis.
212
+ let rotationY: KeyframeGroup<LottieVector1D>
213
+
214
+ /// The rotation of the transform on Z axis.
215
+ let rotationZ: KeyframeGroup<LottieVector1D>
177
216
 
178
217
  /// The opacity of the transform.
179
218
  let opacity: KeyframeGroup<LottieVector1D>
180
219
 
181
- /// Should always be nil.
182
- let rotationZ: KeyframeGroup<LottieVector1D>?
220
+ // MARK: Private
221
+
222
+ /// Here for the CodingKeys.rotation = "r". `r` and `rz` are the same.
223
+ private let rotation: KeyframeGroup<LottieVector1D>?
183
224
  }
@@ -22,8 +22,19 @@ final class Repeater: ShapeItem {
22
22
  .decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .startOpacity) ?? KeyframeGroup(LottieVector1D(100))
23
23
  endOpacity = try transformContainer
24
24
  .decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .endOpacity) ?? KeyframeGroup(LottieVector1D(100))
25
- rotation = try transformContainer
26
- .decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .rotation) ?? KeyframeGroup(LottieVector1D(0))
25
+ if let rotation = try transformContainer.decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .rotation) {
26
+ rotationZ = rotation
27
+ } else if let rotation = try transformContainer.decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .rotationZ) {
28
+ rotationZ = rotation
29
+ } else {
30
+ rotationZ = KeyframeGroup(LottieVector1D(0))
31
+ }
32
+
33
+ rotationX = try transformContainer
34
+ .decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .rotationX) ?? KeyframeGroup(LottieVector1D(0))
35
+ rotationY = try transformContainer
36
+ .decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .rotationY) ?? KeyframeGroup(LottieVector1D(0))
37
+
27
38
  position = try transformContainer
28
39
  .decodeIfPresent(KeyframeGroup<LottieVector3D>.self, forKey: .position) ??
29
40
  KeyframeGroup(LottieVector3D(x: Double(0), y: 0, z: 0))
@@ -58,10 +69,22 @@ final class Repeater: ShapeItem {
58
69
  } else {
59
70
  endOpacity = KeyframeGroup(LottieVector1D(100))
60
71
  }
72
+ if let rotationDictionary = transformDictionary[TransformKeys.rotationX.rawValue] as? [String: Any] {
73
+ rotationX = try KeyframeGroup<LottieVector1D>(dictionary: rotationDictionary)
74
+ } else {
75
+ rotationX = KeyframeGroup(LottieVector1D(0))
76
+ }
77
+ if let rotationDictionary = transformDictionary[TransformKeys.rotationY.rawValue] as? [String: Any] {
78
+ rotationY = try KeyframeGroup<LottieVector1D>(dictionary: rotationDictionary)
79
+ } else {
80
+ rotationY = KeyframeGroup(LottieVector1D(0))
81
+ }
61
82
  if let rotationDictionary = transformDictionary[TransformKeys.rotation.rawValue] as? [String: Any] {
62
- rotation = try KeyframeGroup<LottieVector1D>(dictionary: rotationDictionary)
83
+ rotationZ = try KeyframeGroup<LottieVector1D>(dictionary: rotationDictionary)
84
+ } else if let rotationDictionary = transformDictionary[TransformKeys.rotationZ.rawValue] as? [String: Any] {
85
+ rotationZ = try KeyframeGroup<LottieVector1D>(dictionary: rotationDictionary)
63
86
  } else {
64
- rotation = KeyframeGroup(LottieVector1D(0))
87
+ rotationZ = KeyframeGroup(LottieVector1D(0))
65
88
  }
66
89
  if let positionDictionary = transformDictionary[TransformKeys.position.rawValue] as? [String: Any] {
67
90
  position = try KeyframeGroup<LottieVector3D>(dictionary: positionDictionary)
@@ -95,8 +118,14 @@ final class Repeater: ShapeItem {
95
118
  /// End opacity
96
119
  let endOpacity: KeyframeGroup<LottieVector1D>
97
120
 
98
- /// The rotation
99
- let rotation: KeyframeGroup<LottieVector1D>
121
+ /// The rotation on X axis
122
+ let rotationX: KeyframeGroup<LottieVector1D>
123
+
124
+ /// The rotation on Y axis
125
+ let rotationY: KeyframeGroup<LottieVector1D>
126
+
127
+ /// The rotation on Z axis
128
+ let rotationZ: KeyframeGroup<LottieVector1D>
100
129
 
101
130
  /// Anchor Point
102
131
  let anchorPoint: KeyframeGroup<LottieVector3D>
@@ -115,7 +144,9 @@ final class Repeater: ShapeItem {
115
144
  var transformContainer = container.nestedContainer(keyedBy: TransformKeys.self, forKey: .transform)
116
145
  try transformContainer.encode(startOpacity, forKey: .startOpacity)
117
146
  try transformContainer.encode(endOpacity, forKey: .endOpacity)
118
- try transformContainer.encode(rotation, forKey: .rotation)
147
+ try transformContainer.encode(rotationX, forKey: .rotationX)
148
+ try transformContainer.encode(rotationY, forKey: .rotationY)
149
+ try transformContainer.encode(rotationZ, forKey: .rotationZ)
119
150
  try transformContainer.encode(position, forKey: .position)
120
151
  try transformContainer.encode(anchorPoint, forKey: .anchorPoint)
121
152
  try transformContainer.encode(scale, forKey: .scale)
@@ -131,6 +162,9 @@ final class Repeater: ShapeItem {
131
162
 
132
163
  private enum TransformKeys: String, CodingKey {
133
164
  case rotation = "r"
165
+ case rotationX = "rx"
166
+ case rotationY = "ry"
167
+ case rotationZ = "rz"
134
168
  case startOpacity = "so"
135
169
  case endOpacity = "eo"
136
170
  case anchorPoint = "a"
@@ -22,8 +22,25 @@ final class ShapeTransform: ShapeItem {
22
22
  scale = try container
23
23
  .decodeIfPresent(KeyframeGroup<LottieVector3D>.self, forKey: .scale) ??
24
24
  KeyframeGroup(LottieVector3D(x: Double(100), y: 100, z: 100))
25
- rotation = try container
26
- .decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .rotation) ?? KeyframeGroup(LottieVector1D(0))
25
+
26
+ rotationX = try container
27
+ .decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .rotationX) ?? KeyframeGroup(LottieVector1D(0))
28
+ rotationY = try container
29
+ .decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .rotationY) ?? KeyframeGroup(LottieVector1D(0))
30
+ if
31
+ let rotation = try container
32
+ .decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .rotation)
33
+ {
34
+ rotationZ = rotation
35
+ } else if
36
+ let rotation = try container
37
+ .decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .rotationZ)
38
+ {
39
+ rotationZ = rotation
40
+ } else {
41
+ rotationZ = KeyframeGroup(LottieVector1D(0))
42
+ }
43
+
27
44
  opacity = try container
28
45
  .decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .opacity) ?? KeyframeGroup(LottieVector1D(100))
29
46
  skew = try container.decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .skew) ?? KeyframeGroup(LottieVector1D(0))
@@ -57,14 +74,39 @@ final class ShapeTransform: ShapeItem {
57
74
  } else {
58
75
  scale = KeyframeGroup(LottieVector3D(x: Double(100), y: 100, z: 100))
59
76
  }
77
+
78
+ if
79
+ let rotationDictionary = dictionary[CodingKeys.rotationX.rawValue] as? [String: Any],
80
+ let rotation = try? KeyframeGroup<LottieVector1D>(dictionary: rotationDictionary)
81
+ {
82
+ rotationX = rotation
83
+ } else {
84
+ rotationX = KeyframeGroup(LottieVector1D(0))
85
+ }
86
+
87
+ if
88
+ let rotationDictionary = dictionary[CodingKeys.rotationY.rawValue] as? [String: Any],
89
+ let rotation = try? KeyframeGroup<LottieVector1D>(dictionary: rotationDictionary)
90
+ {
91
+ rotationY = rotation
92
+ } else {
93
+ rotationY = KeyframeGroup(LottieVector1D(0))
94
+ }
95
+
60
96
  if
61
97
  let rotationDictionary = dictionary[CodingKeys.rotation.rawValue] as? [String: Any],
62
98
  let rotation = try? KeyframeGroup<LottieVector1D>(dictionary: rotationDictionary)
63
99
  {
64
- self.rotation = rotation
100
+ rotationZ = rotation
101
+ } else if
102
+ let rotationDictionary = dictionary[CodingKeys.rotationZ.rawValue] as? [String: Any],
103
+ let rotation = try? KeyframeGroup<LottieVector1D>(dictionary: rotationDictionary)
104
+ {
105
+ rotationZ = rotation
65
106
  } else {
66
- rotation = KeyframeGroup(LottieVector1D(0))
107
+ rotationZ = KeyframeGroup(LottieVector1D(0))
67
108
  }
109
+
68
110
  if
69
111
  let opacityDictionary = dictionary[CodingKeys.opacity.rawValue] as? [String: Any],
70
112
  let opacity = try? KeyframeGroup<LottieVector1D>(dictionary: opacityDictionary)
@@ -89,6 +131,7 @@ final class ShapeTransform: ShapeItem {
89
131
  } else {
90
132
  skewAxis = KeyframeGroup(LottieVector1D(0))
91
133
  }
134
+
92
135
  try super.init(dictionary: dictionary)
93
136
  }
94
137
 
@@ -103,8 +146,14 @@ final class ShapeTransform: ShapeItem {
103
146
  /// Scale
104
147
  let scale: KeyframeGroup<LottieVector3D>
105
148
 
106
- /// Rotation
107
- let rotation: KeyframeGroup<LottieVector1D>
149
+ /// Rotation on X axis
150
+ let rotationX: KeyframeGroup<LottieVector1D>
151
+
152
+ /// Rotation on Y axis
153
+ let rotationY: KeyframeGroup<LottieVector1D>
154
+
155
+ /// Rotation on Z axis
156
+ let rotationZ: KeyframeGroup<LottieVector1D>
108
157
 
109
158
  /// opacity
110
159
  let opacity: KeyframeGroup<LottieVector1D>
@@ -130,7 +179,9 @@ final class ShapeTransform: ShapeItem {
130
179
  try container.encode(anchor, forKey: .anchor)
131
180
  try container.encode(position, forKey: .position)
132
181
  try container.encode(scale, forKey: .scale)
133
- try container.encode(rotation, forKey: .rotation)
182
+ try container.encode(rotationX, forKey: .rotationX)
183
+ try container.encode(rotationY, forKey: .rotationY)
184
+ try container.encode(rotationZ, forKey: .rotationZ)
134
185
  try container.encode(opacity, forKey: .opacity)
135
186
  try container.encode(skew, forKey: .skew)
136
187
  try container.encode(skewAxis, forKey: .skewAxis)
@@ -143,6 +194,9 @@ final class ShapeTransform: ShapeItem {
143
194
  case position = "p"
144
195
  case scale = "s"
145
196
  case rotation = "r"
197
+ case rotationX = "rx"
198
+ case rotationY = "ry"
199
+ case rotationZ = "rz"
146
200
  case opacity = "o"
147
201
  case skew = "sk"
148
202
  case skewAxis = "sa"
@@ -24,7 +24,15 @@ final class TextAnimator: Codable, DictionaryInitializable {
24
24
  scale = try animatorContainer.decodeIfPresent(KeyframeGroup<LottieVector3D>.self, forKey: .scale)
25
25
  skew = try animatorContainer.decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .skew)
26
26
  skewAxis = try animatorContainer.decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .skewAxis)
27
- rotation = try animatorContainer.decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .rotation)
27
+ rotationX = try animatorContainer.decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .rotationX)
28
+ rotationY = try animatorContainer.decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .rotationY)
29
+ if let rotation = try animatorContainer.decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .rotation) {
30
+ rotationZ = rotation
31
+ } else if let rotation = try animatorContainer.decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .rotationZ) {
32
+ rotationZ = rotation
33
+ } else {
34
+ rotationZ = nil
35
+ }
28
36
  opacity = try animatorContainer.decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .opacity)
29
37
  }
30
38
 
@@ -76,11 +84,26 @@ final class TextAnimator: Codable, DictionaryInitializable {
76
84
  } else {
77
85
  skewAxis = nil
78
86
  }
87
+ if let rotationDictionary = animatorDictionary[TextAnimatorKeys.rotationX.rawValue] as? [String: Any] {
88
+ rotationX = try? KeyframeGroup<LottieVector1D>(dictionary: rotationDictionary)
89
+ } else {
90
+ rotationX = nil
91
+ }
92
+
93
+ if let rotationDictionary = animatorDictionary[TextAnimatorKeys.rotationY.rawValue] as? [String: Any] {
94
+ rotationY = try? KeyframeGroup<LottieVector1D>(dictionary: rotationDictionary)
95
+ } else {
96
+ rotationY = nil
97
+ }
98
+
79
99
  if let rotationDictionary = animatorDictionary[TextAnimatorKeys.rotation.rawValue] as? [String: Any] {
80
- rotation = try? KeyframeGroup<LottieVector1D>(dictionary: rotationDictionary)
100
+ rotationZ = try? KeyframeGroup<LottieVector1D>(dictionary: rotationDictionary)
101
+ } else if let rotationDictionary = animatorDictionary[TextAnimatorKeys.rotationZ.rawValue] as? [String: Any] {
102
+ rotationZ = try? KeyframeGroup<LottieVector1D>(dictionary: rotationDictionary)
81
103
  } else {
82
- rotation = nil
104
+ rotationZ = nil
83
105
  }
106
+
84
107
  if let opacityDictionary = animatorDictionary[TextAnimatorKeys.opacity.rawValue] as? [String: Any] {
85
108
  opacity = try KeyframeGroup<LottieVector1D>(dictionary: opacityDictionary)
86
109
  } else {
@@ -107,8 +130,14 @@ final class TextAnimator: Codable, DictionaryInitializable {
107
130
  /// Skew Axis
108
131
  let skewAxis: KeyframeGroup<LottieVector1D>?
109
132
 
110
- /// Rotation
111
- let rotation: KeyframeGroup<LottieVector1D>?
133
+ /// Rotation on X axis
134
+ let rotationX: KeyframeGroup<LottieVector1D>?
135
+
136
+ /// Rotation on Y axis
137
+ let rotationY: KeyframeGroup<LottieVector1D>?
138
+
139
+ /// Rotation on Z axis
140
+ let rotationZ: KeyframeGroup<LottieVector1D>?
112
141
 
113
142
  /// Opacity
114
143
  let opacity: KeyframeGroup<LottieVector1D>?
@@ -159,6 +188,9 @@ final class TextAnimator: Codable, DictionaryInitializable {
159
188
  case skew = "sk"
160
189
  case skewAxis = "sa"
161
190
  case rotation = "r"
191
+ case rotationX = "rx"
192
+ case rotationY = "ry"
193
+ case rotationZ = "rz"
162
194
  case opacity = "o"
163
195
  }
164
196
  }
@@ -7,6 +7,8 @@ import QuartzCore
7
7
 
8
8
  /// A root `CALayer` responsible for playing a Lottie animation
9
9
  protocol RootAnimationLayer: CALayer {
10
+ var animationView: LottieAnimationView? { get set }
11
+
10
12
  var currentFrame: AnimationFrameTime { get set }
11
13
  var renderScale: CGFloat { get set }
12
14
  var respectAnimationFrameRate: Bool { get set }
@@ -32,7 +34,7 @@ protocol RootAnimationLayer: CALayer {
32
34
  func reloadImages()
33
35
  func forceDisplayUpdate()
34
36
  func logHierarchyKeypaths()
35
-
37
+ func allHierarchyKeypaths() -> [String]
36
38
  func setValueProvider(_ valueProvider: AnyValueProvider, keypath: AnimationKeypath)
37
39
  func getValue(for keypath: AnimationKeypath, atFrame: AnimationFrameTime?) -> Any?
38
40
  func getOriginalValue(for keypath: AnimationKeypath, atFrame: AnimationFrameTime?) -> Any?
@@ -115,20 +115,28 @@ extension KeypathSearchable {
115
115
  return nil
116
116
  }
117
117
 
118
- func logKeypaths(for keyPath: AnimationKeypath?, logger: LottieLogger) {
118
+ /// Computes the list of animation keypaths that descend from this layer
119
+ func allKeypaths(for keyPath: AnimationKeypath? = nil) -> [String] {
120
+ var allKeypaths: [String] = []
121
+
119
122
  let newKeypath: AnimationKeypath
120
123
  if let previousKeypath = keyPath {
121
124
  newKeypath = previousKeypath.appendingKey(keypathName)
122
125
  } else {
123
126
  newKeypath = AnimationKeypath(keys: [keypathName])
124
127
  }
125
- logger.info(newKeypath.fullPath)
128
+
129
+ allKeypaths.append(newKeypath.fullPath)
130
+
126
131
  for key in keypathProperties.keys {
127
- logger.info(newKeypath.appendingKey(key).fullPath)
132
+ allKeypaths.append(newKeypath.appendingKey(key).fullPath)
128
133
  }
134
+
129
135
  for child in childKeypaths {
130
- child.logKeypaths(for: newKeypath, logger: logger)
136
+ allKeypaths.append(contentsOf: child.allKeypaths(for: newKeypath))
131
137
  }
138
+
139
+ return allKeypaths
132
140
  }
133
141
  }
134
142
 
@@ -14,14 +14,24 @@ import AppKit
14
14
 
15
15
  extension Data {
16
16
 
17
- static func jsonData(from assetName: String, in bundle: Bundle) -> Data? {
17
+ init(assetName: String, in bundle: Bundle) throws {
18
18
  #if canImport(UIKit)
19
- return NSDataAsset(name: assetName, bundle: bundle)?.data
19
+ if let asset = NSDataAsset(name: assetName, bundle: bundle) {
20
+ self = asset.data
21
+ return
22
+ } else {
23
+ throw DotLottieError.assetNotFound(name: assetName, bundle: bundle)
24
+ }
20
25
  #else
21
26
  if #available(macOS 10.11, *) {
22
- return NSDataAsset(name: assetName, bundle: bundle)?.data
27
+ if let asset = NSDataAsset(name: assetName, bundle: bundle) {
28
+ self = asset.data
29
+ return
30
+ } else {
31
+ throw DotLottieError.assetNotFound(name: assetName, bundle: bundle)
32
+ }
23
33
  }
24
- return nil
34
+ throw DotLottieError.loadingFromAssetNotSupported
25
35
  #endif
26
36
  }
27
37
  }
@@ -83,6 +83,8 @@ extension BezierPath {
83
83
  uniquePath.addVertex(elements[i].vertex)
84
84
  }
85
85
 
86
+ var pathHasRoundedCorner = false
87
+
86
88
  for elementIndex in 0..<uniquePath.elements.count {
87
89
  currentVertex = uniquePath.elements[elementIndex].vertex
88
90
 
@@ -96,6 +98,9 @@ extension BezierPath {
96
98
  continue
97
99
  }
98
100
 
101
+ // Track whether or not this path has at least one rounded corner
102
+ pathHasRoundedCorner = true
103
+
99
104
  // Do not round start and end if not closed
100
105
  if !newPath.closed, elementIndex == 0 || elementIndex == uniquePath.elements.count - 1 {
101
106
  newPath.addVertex(currentVertex)
@@ -142,6 +147,12 @@ extension BezierPath {
142
147
  }
143
148
  }
144
149
 
150
+ // If we didn't need to apply the corner radius to any of the corners,
151
+ // just use the original given path instead of modifying it.
152
+ if !pathHasRoundedCorner {
153
+ return self
154
+ }
155
+
145
156
  return newPath
146
157
  }
147
158
  }
@@ -38,22 +38,18 @@ extension LottieColor: Codable {
38
38
  b1 = 0
39
39
  }
40
40
 
41
- var a1: Double
42
- if !container.isAtEnd {
43
- a1 = try container.decode(Double.self)
44
- } else {
45
- a1 = 1
46
- }
47
- if r1 > 1, g1 > 1, b1 > 1, a1 > 1 {
41
+ if r1 > 1, g1 > 1, b1 > 1 {
48
42
  r1 = r1 / 255
49
43
  g1 = g1 / 255
50
44
  b1 = b1 / 255
51
- a1 = a1 / 255
52
45
  }
53
46
  r = r1
54
47
  g = g1
55
48
  b = b1
56
- a = a1
49
+
50
+ // The Lottie JSON schema supports alpha values in theory, as the fourth value in this array.
51
+ // We intentionally do not support this, though, for consistency with Lottie on other platforms.
52
+ a = 1
57
53
  }
58
54
 
59
55
  // MARK: Public
@@ -79,17 +75,18 @@ extension LottieColor: AnyInitializable {
79
75
  var r: Double = array.count > 0 ? array.removeFirst() : 0
80
76
  var g: Double = array.count > 0 ? array.removeFirst() : 0
81
77
  var b: Double = array.count > 0 ? array.removeFirst() : 0
82
- var a: Double = array.count > 0 ? array.removeFirst() : 1
83
- if r > 1, g > 1, b > 1, a > 1 {
78
+ if r > 1, g > 1, b > 1 {
84
79
  r /= 255
85
80
  g /= 255
86
81
  b /= 255
87
- a /= 255
88
82
  }
89
83
  self.r = r
90
84
  self.g = g
91
85
  self.b = b
92
- self.a = a
86
+
87
+ // The Lottie JSON schema supports alpha values in theory, as the fourth value in this array.
88
+ // We intentionally do not support this, though, for consistency with Lottie on other platforms.
89
+ a = 1
93
90
  }
94
91
 
95
92
  }