lottie-ios 3.2.3 → 3.4.2

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 (434) hide show
  1. package/.github/actions/setup/action.yml +32 -0
  2. package/.github/issue_template.md +6 -23
  3. package/.github/workflows/main.yml +98 -0
  4. package/.github/workflows/stale_issues.yml +17 -0
  5. package/.spi.yml +6 -0
  6. package/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +7 -0
  7. package/.swiftpm/xcode/package.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  8. package/.swiftpm/xcode/package.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  9. package/.swiftpm/xcode/xcuserdata/{brandonwithrow.xcuserdatad → cal.xcuserdatad}/xcschemes/xcschememanagement.plist +25 -15
  10. package/.swiftpm/xcode/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
  11. package/Gemfile +4 -0
  12. package/Gemfile.lock +102 -0
  13. package/Lottie.xcodeproj/project.pbxproj +2011 -1972
  14. package/Lottie.xcodeproj/project.xcworkspace/contents.xcworkspacedata +1 -1
  15. package/Lottie.xcodeproj/project.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  16. package/Lottie.xcodeproj/xcshareddata/xcschemes/{Lottie_iOS.xcscheme → Lottie (iOS).xcscheme } +15 -18
  17. package/Lottie.xcodeproj/xcshareddata/xcschemes/{Lottie_tvOS.xcscheme → Lottie (macOS).xcscheme } +5 -18
  18. package/Lottie.xcodeproj/xcshareddata/xcschemes/{Lottie_macOS.xcscheme → Lottie (tvOS).xcscheme } +5 -18
  19. package/Lottie.xcodeproj/xcuserdata/cal.xcuserdatad/xcschemes/xcschememanagement.plist +37 -0
  20. package/Lottie.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +24 -0
  21. package/Lottie.xcworkspace/contents.xcworkspacedata +10 -0
  22. package/Lottie.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  23. package/Lottie.xcworkspace/xcshareddata/swiftpm/Package.resolved +34 -0
  24. package/Lottie.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  25. package/{Lottie.xcodeproj/xcuserdata/brandonwithrow.xcuserdatad → Lottie.xcworkspace/xcuserdata/cal.xcuserdatad}/xcdebugger/Breakpoints_v2.xcbkptlist +2 -2
  26. package/Lottie.xcworkspace/xcuserdata/cal.xcuserdatad/xcdebugger/Expressions.xcexplist +153 -0
  27. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  28. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +22 -0
  29. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Expressions.xcexplist +138 -0
  30. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +30 -0
  31. package/Package.swift +5 -15
  32. package/README.md +40 -60
  33. package/Rakefile +160 -0
  34. package/Sources/Private/CoreAnimation/Animations/CAAnimation+TimingConfiguration.swift +81 -0
  35. package/Sources/Private/CoreAnimation/Animations/CALayer+addAnimation.swift +447 -0
  36. package/Sources/Private/CoreAnimation/Animations/CombinedShapeAnimation.swift +81 -0
  37. package/Sources/Private/CoreAnimation/Animations/CustomPathAnimation.swift +41 -0
  38. package/Sources/Private/CoreAnimation/Animations/EllipseAnimation.swift +55 -0
  39. package/Sources/Private/CoreAnimation/Animations/GradientAnimations.swift +210 -0
  40. package/Sources/Private/CoreAnimation/Animations/LayerProperty.swift +229 -0
  41. package/Sources/Private/CoreAnimation/Animations/OpacityAnimation.swift +52 -0
  42. package/Sources/Private/CoreAnimation/Animations/RectangleAnimation.swift +58 -0
  43. package/Sources/Private/CoreAnimation/Animations/ShapeAnimation.swift +257 -0
  44. package/Sources/Private/CoreAnimation/Animations/StarAnimation.swift +124 -0
  45. package/Sources/Private/CoreAnimation/Animations/StrokeAnimation.swift +73 -0
  46. package/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift +205 -0
  47. package/Sources/Private/CoreAnimation/Animations/VisibilityAnimation.swift +37 -0
  48. package/Sources/Private/CoreAnimation/CompatibilityTracker.swift +130 -0
  49. package/Sources/Private/CoreAnimation/CoreAnimationLayer.swift +497 -0
  50. package/Sources/Private/CoreAnimation/Extensions/CALayer+fillBounds.swift +35 -0
  51. package/Sources/Private/CoreAnimation/Extensions/KeyframeGroup+exactlyOneKeyframe.swift +37 -0
  52. package/Sources/Private/CoreAnimation/Extensions/Keyframes+combinedIfPossible.swift +154 -0
  53. package/Sources/Private/CoreAnimation/Layers/AnimationLayer.swift +83 -0
  54. package/Sources/Private/CoreAnimation/Layers/BaseAnimationLayer.swift +33 -0
  55. package/Sources/Private/CoreAnimation/Layers/BaseCompositionLayer.swift +88 -0
  56. package/Sources/Private/CoreAnimation/Layers/CALayer+setupLayerHierarchy.swift +167 -0
  57. package/Sources/Private/CoreAnimation/Layers/GradientRenderLayer.swift +94 -0
  58. package/Sources/Private/CoreAnimation/Layers/ImageLayer.swift +79 -0
  59. package/Sources/Private/CoreAnimation/Layers/LayerModel+makeAnimationLayer.swift +64 -0
  60. package/Sources/Private/CoreAnimation/Layers/MaskCompositionLayer.swift +138 -0
  61. package/Sources/Private/CoreAnimation/Layers/PreCompLayer.swift +140 -0
  62. package/Sources/Private/CoreAnimation/Layers/RepeaterLayer.swift +85 -0
  63. package/Sources/Private/CoreAnimation/Layers/ShapeItemLayer.swift +315 -0
  64. package/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift +390 -0
  65. package/Sources/Private/CoreAnimation/Layers/SolidLayer.swift +47 -0
  66. package/Sources/Private/CoreAnimation/Layers/TextLayer.swift +91 -0
  67. package/Sources/Private/CoreAnimation/Layers/TransformLayer.swift +11 -0
  68. package/Sources/Private/CoreAnimation/ValueProviderStore.swift +139 -0
  69. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/CompositionLayer.swift +94 -86
  70. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/ImageCompositionLayer.swift +24 -21
  71. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/MaskContainerLayer.swift +75 -54
  72. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/NullCompositionLayer.swift +5 -5
  73. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/PreCompositionLayer.swift +59 -43
  74. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/ShapeCompositionLayer.swift +22 -20
  75. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/SolidCompositionLayer.swift +26 -17
  76. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/TextCompositionLayer.swift +42 -36
  77. package/{lottie-swift/src/Private/LayerContainers/AnimationContainer.swift → Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift} +200 -140
  78. package/Sources/Private/MainThread/LayerContainers/Utility/CachedImageProvider.swift +47 -0
  79. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/Utility/CompositionLayersInitializer.swift +33 -24
  80. package/{lottie-swift/src/Private/LayerContainers/Utility/TextLayer.swift → Sources/Private/MainThread/LayerContainers/Utility/CoreTextRenderLayer.swift} +149 -106
  81. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/Utility/InvertedMatteLayer.swift +26 -24
  82. package/Sources/Private/MainThread/LayerContainers/Utility/LayerFontProvider.swift +41 -0
  83. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/Utility/LayerImageProvider.swift +20 -16
  84. package/Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.swift +40 -0
  85. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/Utility/LayerTransformNode.swift +82 -67
  86. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Extensions/ItemsExtension.swift +9 -4
  87. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/NodeProperty.swift +29 -21
  88. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift +16 -10
  89. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.swift +6 -6
  90. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.swift +5 -5
  91. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift +10 -8
  92. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/ValueContainer.swift +25 -21
  93. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/ValueProviders/GroupInterpolator.swift +23 -17
  94. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.swift +22 -17
  95. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift +110 -79
  96. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/GroupOutputNode.swift +22 -16
  97. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.swift +19 -15
  98. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/PathOutputNode.swift +22 -20
  99. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/Renderables/FillRenderer.swift +22 -23
  100. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift +247 -0
  101. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientStrokeRenderer.swift +30 -24
  102. package/{lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift → Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/LegacyGradientFillRenderer.swift} +93 -66
  103. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/Renderables/StrokeRenderer.swift +23 -19
  104. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift +139 -0
  105. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift +170 -0
  106. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/PathNodes/RectNode.swift +95 -58
  107. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/PathNodes/ShapeNode.swift +42 -29
  108. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/PathNodes/StarNode.swift +108 -69
  109. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderContainers/GroupNode.swift +155 -0
  110. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift +51 -37
  111. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift +57 -42
  112. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift +66 -58
  113. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +169 -0
  114. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift +138 -119
  115. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Protocols/AnimatorNode.swift +80 -79
  116. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Protocols/PathNode.swift +5 -3
  117. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Protocols/RenderNode.swift +22 -17
  118. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift +27 -24
  119. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/RenderLayers/ShapeRenderLayer.swift +34 -25
  120. package/Sources/Private/Model/Animation.swift +160 -0
  121. package/Sources/Private/Model/Assets/Asset.swift +43 -0
  122. package/{lottie-swift/src → Sources}/Private/Model/Assets/AssetLibrary.swift +40 -13
  123. package/Sources/Private/Model/Assets/ImageAsset.swift +112 -0
  124. package/{lottie-swift/src → Sources}/Private/Model/Assets/PrecompAsset.swift +20 -10
  125. package/Sources/Private/Model/DictionaryInitializable.swift +67 -0
  126. package/Sources/Private/Model/Extensions/Bundle.swift +34 -0
  127. package/{lottie-swift/src → Sources}/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift +8 -4
  128. package/Sources/Private/Model/Keyframes/KeyframeData.swift +113 -0
  129. package/Sources/Private/Model/Keyframes/KeyframeGroup.swift +222 -0
  130. package/{lottie-swift/src → Sources}/Private/Model/Layers/ImageLayerModel.swift +21 -11
  131. package/Sources/Private/Model/Layers/LayerModel.swift +228 -0
  132. package/{lottie-swift/src → Sources}/Private/Model/Layers/PreCompLayerModel.swift +39 -22
  133. package/{lottie-swift/src → Sources}/Private/Model/Layers/ShapeLayerModel.swift +23 -12
  134. package/{lottie-swift/src → Sources}/Private/Model/Layers/SolidLayerModel.swift +31 -19
  135. package/{lottie-swift/src → Sources}/Private/Model/Layers/TextLayerModel.swift +33 -19
  136. package/Sources/Private/Model/Objects/DashPattern.swift +44 -0
  137. package/Sources/Private/Model/Objects/Marker.swift +33 -0
  138. package/Sources/Private/Model/Objects/Mask.swift +80 -0
  139. package/Sources/Private/Model/Objects/Transform.swift +179 -0
  140. package/Sources/Private/Model/ShapeItems/Ellipse.swift +74 -0
  141. package/Sources/Private/Model/ShapeItems/Fill.swift +74 -0
  142. package/Sources/Private/Model/ShapeItems/GradientFill.swift +137 -0
  143. package/Sources/Private/Model/ShapeItems/GradientStroke.swift +185 -0
  144. package/Sources/Private/Model/ShapeItems/Group.swift +48 -0
  145. package/{lottie-swift/src → Sources}/Private/Model/ShapeItems/Merge.swift +29 -12
  146. package/Sources/Private/Model/ShapeItems/Rectangle.swift +72 -0
  147. package/Sources/Private/Model/ShapeItems/Repeater.swift +135 -0
  148. package/Sources/Private/Model/ShapeItems/Shape.swift +56 -0
  149. package/Sources/Private/Model/ShapeItems/ShapeItem.swift +163 -0
  150. package/Sources/Private/Model/ShapeItems/ShapeTransform.swift +135 -0
  151. package/Sources/Private/Model/ShapeItems/Star.swift +131 -0
  152. package/Sources/Private/Model/ShapeItems/Stroke.swift +101 -0
  153. package/Sources/Private/Model/ShapeItems/Trim.swift +77 -0
  154. package/Sources/Private/Model/Text/Font.swift +61 -0
  155. package/{lottie-swift/src → Sources}/Private/Model/Text/Glyph.swift +63 -39
  156. package/Sources/Private/Model/Text/TextAnimator.swift +164 -0
  157. package/Sources/Private/Model/Text/TextDocument.swift +123 -0
  158. package/Sources/Private/RootAnimationLayer.swift +51 -0
  159. package/{lottie-swift/src → Sources}/Private/Utility/Debugging/AnimatorNodeDebugging.swift +7 -7
  160. package/{lottie-swift/src → Sources}/Private/Utility/Debugging/LayerDebugging.swift +72 -48
  161. package/Sources/Private/Utility/Debugging/TestHelpers.swift +10 -0
  162. package/{lottie-swift/src → Sources}/Private/Utility/Extensions/AnimationKeypathExtension.swift +69 -59
  163. package/Sources/Private/Utility/Extensions/BlendMode+Filter.swift +31 -0
  164. package/Sources/Private/Utility/Extensions/CGColor+RGB.swift +22 -0
  165. package/{lottie-swift/src → Sources}/Private/Utility/Extensions/CGFloatExtensions.swift +70 -67
  166. package/Sources/Private/Utility/Extensions/DataExtension.swift +27 -0
  167. package/Sources/Private/Utility/Extensions/MathKit.swift +450 -0
  168. package/Sources/Private/Utility/Extensions/StringExtensions.swift +38 -0
  169. package/{lottie-swift/src → Sources}/Private/Utility/Helpers/AnimationContext.swift +42 -16
  170. package/Sources/Private/Utility/Interpolatable/InterpolatableExtensions.swift +134 -0
  171. package/{lottie-swift/src → Sources}/Private/Utility/Interpolatable/KeyframeExtensions.swift +13 -10
  172. package/Sources/Private/Utility/Interpolatable/KeyframeGroup+Extensions.swift +59 -0
  173. package/{lottie-swift/src/Private/NodeRenderSystem/NodeProperties/ValueProviders → Sources/Private/Utility/Interpolatable}/KeyframeInterpolator.swift +125 -108
  174. package/{lottie-swift/src → Sources}/Private/Utility/Primitives/BezierPath.swift +229 -143
  175. package/Sources/Private/Utility/Primitives/CGPointExtension.swift +35 -0
  176. package/{lottie-swift/src → Sources}/Private/Utility/Primitives/ColorExtension.swift +46 -14
  177. package/{lottie-swift/src → Sources}/Private/Utility/Primitives/CompoundBezierPath.swift +58 -49
  178. package/Sources/Private/Utility/Primitives/CurveVertex.swift +184 -0
  179. package/Sources/Private/Utility/Primitives/PathElement.swift +75 -0
  180. package/Sources/Private/Utility/Primitives/UnitBezier.swift +115 -0
  181. package/Sources/Private/Utility/Primitives/VectorsExtensions.swift +341 -0
  182. package/Sources/Public/Animation/AnimationPublic.swift +266 -0
  183. package/Sources/Public/Animation/AnimationView.swift +1335 -0
  184. package/Sources/Public/Animation/AnimationViewInitializers.swift +109 -0
  185. package/Sources/Public/AnimationCache/AnimationCacheProvider.swift +22 -0
  186. package/{lottie-swift/src → Sources}/Public/AnimationCache/LRUAnimationCache.swift +22 -18
  187. package/Sources/Public/DynamicProperties/AnimationKeypath.swift +49 -0
  188. package/Sources/Public/DynamicProperties/AnyValueProvider.swift +132 -0
  189. package/{lottie-swift/src → Sources}/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift +54 -36
  190. package/{lottie-swift/src → Sources}/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift +40 -36
  191. package/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +123 -0
  192. package/{lottie-swift/src → Sources}/Public/DynamicProperties/ValueProviders/PointValueProvider.swift +40 -35
  193. package/{lottie-swift/src → Sources}/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift +40 -35
  194. package/{lottie-swift/src → Sources}/Public/FontProvider/AnimationFontProvider.swift +15 -8
  195. package/Sources/Public/ImageProvider/AnimationImageProvider.swift +21 -0
  196. package/Sources/Public/Keyframes/Interpolatable.swift +253 -0
  197. package/Sources/Public/Keyframes/Keyframe.swift +92 -0
  198. package/Sources/Public/Logging/LottieLogger.swift +137 -0
  199. package/Sources/Public/LottieConfiguration.swift +143 -0
  200. package/{lottie-swift/src → Sources}/Public/Primitives/AnimationTime.swift +1 -1
  201. package/{lottie-swift/src → Sources}/Public/Primitives/Color.swift +10 -6
  202. package/{lottie-swift/src → Sources}/Public/Primitives/Vectors.swift +13 -12
  203. package/Sources/Public/TextProvider/AnimationTextProvider.swift +53 -0
  204. package/{lottie-swift/src → Sources}/Public/iOS/AnimatedButton.swift +42 -26
  205. package/{lottie-swift/src → Sources}/Public/iOS/AnimatedControl.swift +100 -91
  206. package/{lottie-swift/src → Sources}/Public/iOS/AnimatedSwitch.swift +128 -94
  207. package/{lottie-swift/src → Sources}/Public/iOS/AnimationSubview.swift +3 -3
  208. package/Sources/Public/iOS/AnimationViewBase.swift +78 -0
  209. package/{lottie-swift/src → Sources}/Public/iOS/BundleImageProvider.swift +36 -34
  210. package/{lottie-swift/src → Sources}/Public/iOS/Compatibility/CompatibleAnimationKeypath.swift +4 -0
  211. package/{lottie-swift/src → Sources}/Public/iOS/Compatibility/CompatibleAnimationView.swift +38 -29
  212. package/{lottie-swift/src → Sources}/Public/iOS/FilepathImageProvider.swift +24 -21
  213. package/{lottie-swift/src → Sources}/Public/iOS/UIColorExtension.swift +4 -4
  214. package/{lottie-swift/src/Public/MacOS/AnimationSubview.swift → Sources/Public/macOS/AnimationSubview.macOS.swift} +4 -5
  215. package/{lottie-swift/src/Public/MacOS/LottieView.swift → Sources/Public/macOS/AnimationViewBase.macOS.swift} +58 -50
  216. package/{lottie-swift/src/Public/MacOS/BundleImageProvider.swift → Sources/Public/macOS/BundleImageProvider.macOS.swift} +31 -32
  217. package/Sources/Public/macOS/FilepathImageProvider.macOS.swift +67 -0
  218. package/Tests/AnimationKeypathTests.swift +103 -0
  219. package/Tests/AutomaticEngineTests.swift +57 -0
  220. package/Tests/BundleTests.swift +25 -0
  221. package/Tests/DataURLTests.swift +64 -0
  222. package/Tests/ParsingTests.swift +43 -0
  223. package/Tests/PerformanceTests.swift +214 -0
  224. package/Tests/Samples/9squares_AlBoardman.json +1 -0
  225. package/Tests/Samples/Boat_Loader.json +1 -0
  226. package/Tests/Samples/HamburgerArrow.json +1 -0
  227. package/Tests/Samples/IconTransitions.json +1 -0
  228. package/Tests/Samples/Images/dog.png +0 -0
  229. package/Tests/Samples/Issues/issue_1125.json +1 -0
  230. package/Tests/Samples/Issues/issue_1260.json +1 -0
  231. package/Tests/Samples/Issues/issue_1403.json +1 -0
  232. package/Tests/Samples/Issues/issue_1407.json +1 -0
  233. package/Tests/Samples/Issues/issue_1460.json +1 -0
  234. package/Tests/Samples/Issues/issue_1488.json +1 -0
  235. package/Tests/Samples/Issues/issue_1505.json +1 -0
  236. package/Tests/Samples/Issues/issue_1541.json +1 -0
  237. package/Tests/Samples/Issues/issue_1557.json +1 -0
  238. package/Tests/Samples/Issues/issue_1603.json +1 -0
  239. package/Tests/Samples/Issues/issue_1628.json +1 -0
  240. package/Tests/Samples/Issues/issue_1636.json +1 -0
  241. package/Tests/Samples/Issues/issue_1643.json +1 -0
  242. package/Tests/Samples/Issues/issue_1655.json +1 -0
  243. package/Tests/Samples/Issues/issue_1664.json +1 -0
  244. package/Tests/Samples/Issues/issue_1683.json +1 -0
  245. package/Tests/Samples/Issues/issue_1687.json +1 -0
  246. package/Tests/Samples/Issues/issue_1711.json +1 -0
  247. package/Tests/Samples/Issues/issue_1717.json +1 -0
  248. package/Tests/Samples/Issues/issue_769.json +1 -0
  249. package/Tests/Samples/Issues/issue_885.json +1 -0
  250. package/Tests/Samples/Issues/issue_965.json +1 -0
  251. package/Tests/Samples/Issues/pr_1536.json +1 -0
  252. package/Tests/Samples/Issues/pr_1563.json +8439 -0
  253. package/Tests/Samples/Issues/pr_1592.json +5527 -0
  254. package/Tests/Samples/Issues/pr_1599.json +738 -0
  255. package/Tests/Samples/Issues/pr_1604_1.json +1 -0
  256. package/Tests/Samples/Issues/pr_1604_2.json +1 -0
  257. package/Tests/Samples/Issues/pr_1632_1.json +1 -0
  258. package/Tests/Samples/Issues/pr_1632_2.json +1 -0
  259. package/Tests/Samples/Issues/pr_1686.json +513 -0
  260. package/Tests/Samples/Issues/pr_1698.json +1 -0
  261. package/Tests/Samples/Issues/pr_1699.json +1 -0
  262. package/Tests/Samples/LottieFiles/LICENSE.md +14 -0
  263. package/Tests/Samples/LottieFiles/bounce_strokes.json +1 -0
  264. package/Tests/Samples/LottieFiles/cactus.json +1 -0
  265. package/Tests/Samples/LottieFiles/dog_car_ride.json +1 -0
  266. package/Tests/Samples/LottieFiles/draft_icon.json +1 -0
  267. package/Tests/Samples/LottieFiles/fireworks.json +1 -0
  268. package/Tests/Samples/LottieFiles/gradient_1.json +1 -0
  269. package/Tests/Samples/LottieFiles/gradient_2.json +1 -0
  270. package/Tests/Samples/LottieFiles/gradient_pill.json +1 -0
  271. package/Tests/Samples/LottieFiles/gradient_shapes.json +1 -0
  272. package/Tests/Samples/LottieFiles/gradient_square.json +1 -0
  273. package/Tests/Samples/LottieFiles/growth.json +1 -0
  274. package/Tests/Samples/LottieFiles/infinity_loader.json +1 -0
  275. package/Tests/Samples/LottieFiles/loading_dots_1.json +1 -0
  276. package/Tests/Samples/LottieFiles/loading_dots_2.json +1 -0
  277. package/Tests/Samples/LottieFiles/loading_dots_3.json +1 -0
  278. package/Tests/Samples/LottieFiles/loading_gradient_strokes.json +1 -0
  279. package/Tests/Samples/LottieFiles/settings_slider.json +1 -0
  280. package/Tests/Samples/LottieFiles/shop.json +1 -0
  281. package/Tests/Samples/LottieFiles/step_loader.json +1 -0
  282. package/Tests/Samples/LottieLogo1.json +1 -0
  283. package/Tests/Samples/LottieLogo1_masked.json +1 -0
  284. package/Tests/Samples/LottieLogo2.json +1 -0
  285. package/Tests/Samples/MotionCorpse_Jrcanest.json +1 -0
  286. package/Tests/Samples/Nonanimating/BasicLayers.json +1 -0
  287. package/Tests/Samples/Nonanimating/DisableNodesTest.json +1 -0
  288. package/Tests/Samples/Nonanimating/FirstText.json +1 -0
  289. package/Tests/Samples/Nonanimating/GeometryTransformTest.json +1 -0
  290. package/Tests/Samples/Nonanimating/Text_AnimatedProperties.json +1 -0
  291. package/Tests/Samples/Nonanimating/Text_Glyph.json +1 -0
  292. package/Tests/Samples/Nonanimating/Text_NoAnimation.json +1 -0
  293. package/Tests/Samples/Nonanimating/Text_NoGlyph.json +1 -0
  294. package/Tests/Samples/Nonanimating/Zoom.json +1 -0
  295. package/Tests/Samples/Nonanimating/_dog.json +1 -0
  296. package/Tests/Samples/Nonanimating/base64Test.json +1 -0
  297. package/Tests/Samples/Nonanimating/blend_mode_test.json +1 -0
  298. package/Tests/Samples/Nonanimating/keypathTest.json +1 -0
  299. package/Tests/Samples/Nonanimating/verifyLineHeight.json +1 -0
  300. package/Tests/Samples/PinJump.json +1 -0
  301. package/Tests/Samples/Switch.json +1 -0
  302. package/Tests/Samples/Switch_States.json +1 -0
  303. package/Tests/Samples/TwitterHeart.json +1 -0
  304. package/Tests/Samples/TwitterHeartButton.json +1 -0
  305. package/Tests/Samples/TypeFace/A.json +1 -0
  306. package/Tests/Samples/TypeFace/Apostrophe.json +1 -0
  307. package/Tests/Samples/TypeFace/B.json +1 -0
  308. package/Tests/Samples/TypeFace/BlinkingCursor.json +1 -0
  309. package/Tests/Samples/TypeFace/C.json +1 -0
  310. package/Tests/Samples/TypeFace/Colon.json +1 -0
  311. package/Tests/Samples/TypeFace/Comma.json +1 -0
  312. package/Tests/Samples/TypeFace/D.json +1 -0
  313. package/Tests/Samples/TypeFace/E.json +1 -0
  314. package/Tests/Samples/TypeFace/F.json +1 -0
  315. package/Tests/Samples/TypeFace/G.json +1 -0
  316. package/Tests/Samples/TypeFace/H.json +1 -0
  317. package/Tests/Samples/TypeFace/I.json +1 -0
  318. package/Tests/Samples/TypeFace/J.json +1 -0
  319. package/Tests/Samples/TypeFace/K.json +1 -0
  320. package/Tests/Samples/TypeFace/L.json +1 -0
  321. package/Tests/Samples/TypeFace/M.json +1 -0
  322. package/Tests/Samples/TypeFace/N.json +1 -0
  323. package/Tests/Samples/TypeFace/O.json +1 -0
  324. package/Tests/Samples/TypeFace/P.json +1 -0
  325. package/Tests/Samples/TypeFace/Q.json +1 -0
  326. package/Tests/Samples/TypeFace/R.json +1 -0
  327. package/Tests/Samples/TypeFace/S.json +1 -0
  328. package/Tests/Samples/TypeFace/T.json +1 -0
  329. package/Tests/Samples/TypeFace/U.json +1 -0
  330. package/Tests/Samples/TypeFace/V.json +1 -0
  331. package/Tests/Samples/TypeFace/W.json +1 -0
  332. package/Tests/Samples/TypeFace/X.json +1 -0
  333. package/Tests/Samples/TypeFace/Y.json +1 -0
  334. package/Tests/Samples/TypeFace/Z.json +1 -0
  335. package/Tests/Samples/Watermelon.json +1 -0
  336. package/Tests/Samples/setValueTest.json +1 -0
  337. package/Tests/Samples/timeremap.json +1 -0
  338. package/Tests/Samples/vcTransition1.json +1 -0
  339. package/Tests/Samples/vcTransition2.json +1 -0
  340. package/Tests/SnapshotConfiguration.swift +158 -0
  341. package/Tests/SnapshotTests.swift +265 -0
  342. package/Tests/Utils/Bundle+Module.swift +30 -0
  343. package/Tests/Utils/HardcodedFontProvider.swift +19 -0
  344. package/Tests/Utils/HardcodedImageProvider.swift +23 -0
  345. package/Tests/Utils/Snapshotting+presentationLayer.swift +47 -0
  346. package/Tests/ValueProvidersTests.swift +27 -0
  347. package/lottie-ios.podspec +12 -12
  348. package/package.json +1 -1
  349. package/script/test-carthage/Cartfile +1 -0
  350. package/script/test-carthage/Cartfile.resolved +1 -0
  351. package/script/test-carthage/CarthageTest/AppDelegate.swift +26 -0
  352. package/script/test-carthage/CarthageTest/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
  353. package/script/test-carthage/CarthageTest/Assets.xcassets/AppIcon.appiconset/Contents.json +98 -0
  354. package/script/test-carthage/CarthageTest/Assets.xcassets/Contents.json +6 -0
  355. package/script/test-carthage/CarthageTest/Base.lproj/LaunchScreen.storyboard +25 -0
  356. package/script/test-carthage/CarthageTest/Base.lproj/Main.storyboard +24 -0
  357. package/script/test-carthage/CarthageTest/Info.plist +66 -0
  358. package/script/test-carthage/CarthageTest/SceneDelegate.swift +10 -0
  359. package/script/test-carthage/CarthageTest/ViewController.swift +15 -0
  360. package/script/test-carthage/CarthageTest-macOS/AppDelegate.swift +7 -0
  361. package/script/test-carthage/CarthageTest-macOS/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
  362. package/script/test-carthage/CarthageTest-macOS/Assets.xcassets/AppIcon.appiconset/Contents.json +58 -0
  363. package/script/test-carthage/CarthageTest-macOS/Assets.xcassets/Contents.json +6 -0
  364. package/script/test-carthage/CarthageTest-macOS/Base.lproj/Main.storyboard +717 -0
  365. package/script/test-carthage/CarthageTest-macOS/CarthageTest_macOS.entitlements +10 -0
  366. package/script/test-carthage/CarthageTest-macOS/ViewController.swift +15 -0
  367. package/script/test-carthage/CarthageTest.xcodeproj/project.pbxproj +532 -0
  368. package/script/test-carthage/CarthageTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  369. package/script/test-carthage/CarthageTest.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  370. package/script/test-carthage/CarthageTest.xcodeproj/project.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  371. package/script/test-carthage/CarthageTest.xcodeproj/project.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  372. package/script/test-carthage/CarthageTest.xcodeproj/xcuserdata/cal.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
  373. package/script/test-carthage/CarthageTest.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +19 -0
  374. package/script/test-carthage/Mintfile +1 -0
  375. package/script/test-spm/LottieSPM.xcworkspace/contents.xcworkspacedata +7 -0
  376. package/script/test-spm/LottieSPM.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  377. package/script/test-spm/Mintfile +1 -0
  378. package/.swiftpm/xcode/package.xcworkspace/xcuserdata/brandonwithrow.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  379. package/Lottie/Info.plist +0 -22
  380. package/Lottie.xcodeproj/project.xcworkspace/xcuserdata/brandonwithrow.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  381. package/Lottie.xcodeproj/xcuserdata/brandonwithrow.xcuserdatad/xcschemes/LottieLibraryMacOS.xcscheme +0 -80
  382. package/Lottie.xcodeproj/xcuserdata/brandonwithrow.xcuserdatad/xcschemes/xcschememanagement.plist +0 -57
  383. package/lottie-swift/Assets/.gitkeep +0 -0
  384. package/lottie-swift/src/Private/LayerContainers/Utility/LayerFontProvider.swift +0 -37
  385. package/lottie-swift/src/Private/LayerContainers/Utility/LayerTextProvider.swift +0 -36
  386. package/lottie-swift/src/Private/Model/Animation.swift +0 -107
  387. package/lottie-swift/src/Private/Model/Assets/Asset.swift +0 -27
  388. package/lottie-swift/src/Private/Model/Assets/ImageAsset.swift +0 -48
  389. package/lottie-swift/src/Private/Model/Keyframes/Keyframe.swift +0 -128
  390. package/lottie-swift/src/Private/Model/Keyframes/KeyframeGroup.swift +0 -108
  391. package/lottie-swift/src/Private/Model/Layers/LayerModel.swift +0 -150
  392. package/lottie-swift/src/Private/Model/Objects/DashPattern.swift +0 -24
  393. package/lottie-swift/src/Private/Model/Objects/Marker.swift +0 -23
  394. package/lottie-swift/src/Private/Model/Objects/Mask.swift +0 -48
  395. package/lottie-swift/src/Private/Model/Objects/Transform.swift +0 -105
  396. package/lottie-swift/src/Private/Model/ShapeItems/Ellipse.swift +0 -50
  397. package/lottie-swift/src/Private/Model/ShapeItems/FillI.swift +0 -49
  398. package/lottie-swift/src/Private/Model/ShapeItems/GradientFill.swift +0 -86
  399. package/lottie-swift/src/Private/Model/ShapeItems/GradientStroke.swift +0 -125
  400. package/lottie-swift/src/Private/Model/ShapeItems/Group.swift +0 -32
  401. package/lottie-swift/src/Private/Model/ShapeItems/Rectangle.swift +0 -50
  402. package/lottie-swift/src/Private/Model/ShapeItems/Repeater.swift +0 -80
  403. package/lottie-swift/src/Private/Model/ShapeItems/Shape.swift +0 -37
  404. package/lottie-swift/src/Private/Model/ShapeItems/ShapeItem.swift +0 -95
  405. package/lottie-swift/src/Private/Model/ShapeItems/ShapeTransform.swift +0 -68
  406. package/lottie-swift/src/Private/Model/ShapeItems/Star.swift +0 -86
  407. package/lottie-swift/src/Private/Model/ShapeItems/Stroke.swift +0 -67
  408. package/lottie-swift/src/Private/Model/ShapeItems/Trim.swift +0 -53
  409. package/lottie-swift/src/Private/Model/Text/Font.swift +0 -35
  410. package/lottie-swift/src/Private/Model/Text/TextAnimator.swift +0 -99
  411. package/lottie-swift/src/Private/Model/Text/TextDocument.swift +0 -70
  412. package/lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift +0 -109
  413. package/lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift +0 -132
  414. package/lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderContainers/GroupNode.swift +0 -141
  415. package/lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +0 -127
  416. package/lottie-swift/src/Private/Utility/Extensions/MathKit.swift +0 -539
  417. package/lottie-swift/src/Private/Utility/Extensions/StringExtensions.swift +0 -32
  418. package/lottie-swift/src/Private/Utility/Interpolatable/Interpolatable.swift +0 -18
  419. package/lottie-swift/src/Private/Utility/Interpolatable/InterpolatableExtensions.swift +0 -170
  420. package/lottie-swift/src/Private/Utility/Primitives/CurveVertex.swift +0 -177
  421. package/lottie-swift/src/Private/Utility/Primitives/PathElement.swift +0 -68
  422. package/lottie-swift/src/Private/Utility/Primitives/VectorsExtensions.swift +0 -218
  423. package/lottie-swift/src/Public/Animation/AnimationPublic.swift +0 -196
  424. package/lottie-swift/src/Public/Animation/AnimationView.swift +0 -1006
  425. package/lottie-swift/src/Public/Animation/AnimationViewInitializers.swift +0 -83
  426. package/lottie-swift/src/Public/AnimationCache/AnimationCacheProvider.swift +0 -24
  427. package/lottie-swift/src/Public/DynamicProperties/AnimationKeypath.swift +0 -46
  428. package/lottie-swift/src/Public/DynamicProperties/AnyValueProvider.swift +0 -29
  429. package/lottie-swift/src/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +0 -114
  430. package/lottie-swift/src/Public/ImageProvider/AnimationImageProvider.swift +0 -23
  431. package/lottie-swift/src/Public/MacOS/FilepathImageProvider.swift +0 -67
  432. package/lottie-swift/src/Public/TextProvider/AnimationTextProvider.swift +0 -39
  433. package/lottie-swift/src/Public/iOS/LottieView.swift +0 -62
  434. package/lottie-swift-testing.podspec +0 -32
@@ -0,0 +1,257 @@
1
+ // Created by Cal Stephens on 1/7/22.
2
+ // Copyright © 2022 Airbnb Inc. All rights reserved.
3
+
4
+ import QuartzCore
5
+
6
+ extension CAShapeLayer {
7
+ /// Adds a `path` animation for the given `ShapeItem`
8
+ @nonobjc
9
+ func addAnimations(
10
+ for shape: ShapeItem,
11
+ context: LayerAnimationContext,
12
+ pathMultiplier: PathMultiplier) throws
13
+ {
14
+ switch shape {
15
+ case let customShape as Shape:
16
+ try addAnimations(for: customShape.path, context: context, pathMultiplier: pathMultiplier)
17
+
18
+ case let combinedShape as CombinedShapeItem:
19
+ try addAnimations(for: combinedShape, context: context, pathMultiplier: pathMultiplier)
20
+
21
+ case let ellipse as Ellipse:
22
+ try addAnimations(for: ellipse, context: context, pathMultiplier: pathMultiplier)
23
+
24
+ case let rectangle as Rectangle:
25
+ try addAnimations(for: rectangle, context: context, pathMultiplier: pathMultiplier)
26
+
27
+ case let star as Star:
28
+ try addAnimations(for: star, context: context, pathMultiplier: pathMultiplier)
29
+
30
+ default:
31
+ // None of the other `ShapeItem` subclasses draw a `path`
32
+ try context.logCompatibilityIssue("Unexpected shape type \(type(of: shape))")
33
+ return
34
+ }
35
+ }
36
+
37
+ /// Adds a `fillColor` animation for the given `Fill` object
38
+ @nonobjc
39
+ func addAnimations(for fill: Fill, context: LayerAnimationContext) throws {
40
+ fillRule = fill.fillRule.caFillRule
41
+
42
+ try addAnimation(
43
+ for: .fillColor,
44
+ keyframes: fill.color.keyframes,
45
+ value: \.cgColorValue,
46
+ context: context)
47
+
48
+ try addOpacityAnimation(for: fill, context: context)
49
+ }
50
+
51
+ /// Adds animations for `strokeStart` and `strokeEnd` from the given `Trim` object
52
+ @nonobjc
53
+ func addAnimations(for trim: Trim, context: LayerAnimationContext) throws -> PathMultiplier {
54
+ let (strokeStartKeyframes, strokeEndKeyframes, pathMultiplier) = try trim.caShapeLayerKeyframes(context: context)
55
+
56
+ try addAnimation(
57
+ for: .strokeStart,
58
+ keyframes: strokeStartKeyframes.keyframes,
59
+ value: { strokeStart in
60
+ // Lottie animation files express stoke trims as a numerical percentage value
61
+ // (e.g. 25%, 50%, 100%) so we divide by 100 to get the decimal values
62
+ // expected by Core Animation (e.g. 0.25, 0.5, 1.0).
63
+ CGFloat(strokeStart.cgFloatValue) / CGFloat(pathMultiplier) / 100
64
+ }, context: context)
65
+
66
+ try addAnimation(
67
+ for: .strokeEnd,
68
+ keyframes: strokeEndKeyframes.keyframes,
69
+ value: { strokeEnd in
70
+ // Lottie animation files express stoke trims as a numerical percentage value
71
+ // (e.g. 25%, 50%, 100%) so we divide by 100 to get the decimal values
72
+ // expected by Core Animation (e.g. 0.25, 0.5, 1.0).
73
+ CGFloat(strokeEnd.cgFloatValue) / CGFloat(pathMultiplier) / 100
74
+ }, context: context)
75
+
76
+ return pathMultiplier
77
+ }
78
+ }
79
+
80
+ /// The number of times that a `CGPath` needs to be duplicated in order to support the animation's `Trim` keyframes
81
+ typealias PathMultiplier = Int
82
+
83
+ extension Trim {
84
+
85
+ // MARK: Fileprivate
86
+
87
+ /// The `strokeStart` and `strokeEnd` keyframes to apply to a `CAShapeLayer`,
88
+ /// plus a `pathMultiplier` that should be applied to the layer's `path` so that
89
+ /// trim values larger than 100% can be displayed properly.
90
+ fileprivate func caShapeLayerKeyframes(context: LayerAnimationContext) throws
91
+ -> (strokeStart: KeyframeGroup<Vector1D>, strokeEnd: KeyframeGroup<Vector1D>, pathMultiplier: PathMultiplier)
92
+ {
93
+ let strokeStart: KeyframeGroup<Vector1D>
94
+ let strokeEnd: KeyframeGroup<Vector1D>
95
+
96
+ // CAShapeLayer requires strokeStart to be less than strokeEnd. This
97
+ // isn't required by the Lottie schema, so some animations may have
98
+ // strokeStart and strokeEnd flipped. If we detect this is the case,
99
+ // then swap them.
100
+ if startValueIsAlwaysGreaterThanEndValue() {
101
+ strokeStart = end
102
+ strokeEnd = start
103
+ } else {
104
+ strokeStart = start
105
+ strokeEnd = end
106
+ }
107
+
108
+ // If there are no offsets, then the stroke values can be used as-is
109
+ guard
110
+ !offset.keyframes.isEmpty,
111
+ offset.keyframes.contains(where: { $0.value.cgFloatValue != 0 })
112
+ else {
113
+ return (strokeStart, strokeEnd, 1)
114
+ }
115
+
116
+ // Otherwise, adjust the stroke values to account for the offsets
117
+ // 1. Interpolate the keyframes so they are all on a linear timing function
118
+ // 2. Merge by summing the stroke values with the offset values
119
+ let interpolatedStrokeStart = strokeStart.manuallyInterpolateKeyframes()
120
+ let interpolatedStrokeEnd = strokeEnd.manuallyInterpolateKeyframes()
121
+ let interpolatedStrokeOffset = offset.manuallyInterpolateKeyframes()
122
+
123
+ var adjustedStrokeStart = KeyframeGroup(
124
+ keyframes: try adjustKeyframesForTrimOffsets(
125
+ strokeKeyframes: interpolatedStrokeStart,
126
+ offsetKeyframes: interpolatedStrokeOffset,
127
+ context: context))
128
+
129
+ var adjustedStrokeEnd = KeyframeGroup(
130
+ keyframes: try adjustKeyframesForTrimOffsets(
131
+ strokeKeyframes: interpolatedStrokeEnd,
132
+ offsetKeyframes: interpolatedStrokeOffset,
133
+ context: context))
134
+
135
+ // If maximum stroke value is larger than 100%, then we have to create copies of the path
136
+ // so the total path length includes the maximum stroke
137
+ let startStrokes = adjustedStrokeStart.keyframes.map { $0.value.cgFloatValue }
138
+ let endStrokes = adjustedStrokeEnd.keyframes.map { $0.value.cgFloatValue }
139
+ let minimumStrokeMultiplier = Double(floor((startStrokes.min() ?? 0) / 100.0))
140
+ let maximumStrokeMultiplier = Double(ceil((endStrokes.max() ?? 100) / 100.0))
141
+
142
+ if minimumStrokeMultiplier < 0 {
143
+ // Core Animation doesn't support negative stroke offsets, so we have to
144
+ // shift all of the offset values up by the minimum
145
+ adjustedStrokeStart = adjustedStrokeStart.map { Vector1D($0.value + (abs(minimumStrokeMultiplier) * 100.0)) }
146
+ adjustedStrokeEnd = adjustedStrokeEnd.map { Vector1D($0.value + (abs(minimumStrokeMultiplier) * 100.0)) }
147
+ }
148
+
149
+ return (
150
+ strokeStart: adjustedStrokeStart,
151
+ strokeEnd: adjustedStrokeEnd,
152
+ pathMultiplier: Int(abs(maximumStrokeMultiplier) + abs(minimumStrokeMultiplier)))
153
+ }
154
+
155
+ // MARK: Private
156
+
157
+ /// Checks whether or not the value for `trim.start` is greater
158
+ /// than the value for every `trim.end` at every keyframe.
159
+ private func startValueIsAlwaysGreaterThanEndValue() -> Bool {
160
+ let keyframeTimes = Set(start.keyframes.map { $0.time } + end.keyframes.map { $0.time })
161
+
162
+ let startInterpolator = KeyframeInterpolator(keyframes: start.keyframes)
163
+ let endInterpolator = KeyframeInterpolator(keyframes: end.keyframes)
164
+
165
+ for keyframeTime in keyframeTimes {
166
+ guard
167
+ let startAtTime = startInterpolator.value(frame: keyframeTime) as? Vector1D,
168
+ let endAtTime = endInterpolator.value(frame: keyframeTime) as? Vector1D
169
+ else { continue }
170
+
171
+ if startAtTime.cgFloatValue < endAtTime.cgFloatValue {
172
+ return false
173
+ }
174
+ }
175
+
176
+ return true
177
+ }
178
+
179
+ /// Adjusted stroke keyframes to account for offset keyframes by merging them into a single keyframe collection
180
+ ///
181
+ /// Since stroke keyframes and offset keyframes can be defined on different animation curves, they must be
182
+ /// manually interpolated prior to invoking this method. Manually interpolating the keyframes will redefine both
183
+ /// keyframe groups such that they can be interpolated linearly.
184
+ ///
185
+ /// - Precondition: The keyframes must be interpolated using `KeyframeGroup.manuallyInterpolateKeyframes()`
186
+ private func adjustKeyframesForTrimOffsets(
187
+ strokeKeyframes: ContiguousArray<Keyframe<Vector1D>>,
188
+ offsetKeyframes: ContiguousArray<Keyframe<Vector1D>>,
189
+ context _: LayerAnimationContext) throws -> ContiguousArray<Keyframe<Vector1D>>
190
+ {
191
+ guard
192
+ !strokeKeyframes.isEmpty,
193
+ !offsetKeyframes.isEmpty
194
+ else {
195
+ return strokeKeyframes
196
+ }
197
+
198
+ // Map each time to its corresponding stroke/offset keyframe
199
+ var timeMap = [AnimationFrameTime: [Keyframe<Vector1D>?]]()
200
+ for stroke in strokeKeyframes {
201
+ timeMap[stroke.time] = [stroke, nil]
202
+ }
203
+ for offset in offsetKeyframes {
204
+ if var existing = timeMap[offset.time] {
205
+ existing[1] = offset
206
+ timeMap[offset.time] = existing
207
+ } else {
208
+ timeMap[offset.time] = [nil, offset]
209
+ }
210
+ }
211
+
212
+ // Each time will be mapped to a new, adjusted keyframe
213
+ var output = ContiguousArray<Keyframe<Vector1D>>()
214
+ var lastKeyframe: Keyframe<Vector1D>?
215
+ var lastOffset: Keyframe<Vector1D>?
216
+
217
+ for (time, values) in timeMap.sorted(by: { $0.0 < $1.0 }) {
218
+ // Extract keyframe/offset associated with this timestamp
219
+ let keyframe = values[0]
220
+ let offset = values[1]
221
+ lastKeyframe = keyframe ?? lastKeyframe
222
+ lastOffset = offset ?? lastOffset
223
+
224
+ guard let currentKeyframe = lastKeyframe else {
225
+ // No keyframes are output until the first keyframe occurs
226
+ continue
227
+ }
228
+
229
+ guard let currentOffset = lastOffset else {
230
+ // Scalar isHold keyframes are not output as they offset the offset keyframes
231
+ if !(strokeKeyframes.count == 1 && currentKeyframe.isHold) {
232
+ output.append(currentKeyframe)
233
+ }
234
+ continue
235
+ }
236
+
237
+ // Compute the adjusted value by converting the offset value to a stroke value
238
+ let strokeValue = currentKeyframe.value.value
239
+ let offsetValue = currentOffset.value.value
240
+ let adjustedValue = strokeValue + (offsetValue / 360 * 100)
241
+
242
+ // The tangent values are all `nil` as the keyframes should have been manually interpolated
243
+ let adjustedKeyframe = Keyframe<Vector1D>(
244
+ value: Vector1D(adjustedValue),
245
+ time: time,
246
+ isHold: currentKeyframe.isHold,
247
+ inTangent: nil,
248
+ outTangent: nil,
249
+ spatialInTangent: nil,
250
+ spatialOutTangent: nil)
251
+
252
+ output.append(adjustedKeyframe)
253
+ }
254
+
255
+ return output
256
+ }
257
+ }
@@ -0,0 +1,124 @@
1
+ // Created by Cal Stephens on 1/10/22.
2
+ // Copyright © 2022 Airbnb Inc. All rights reserved.
3
+
4
+ import QuartzCore
5
+
6
+ extension CAShapeLayer {
7
+
8
+ // MARK: Internal
9
+
10
+ /// Adds animations for the given `Rectangle` to this `CALayer`
11
+ @nonobjc
12
+ func addAnimations(
13
+ for star: Star,
14
+ context: LayerAnimationContext,
15
+ pathMultiplier: PathMultiplier)
16
+ throws
17
+ {
18
+ switch star.starType {
19
+ case .star:
20
+ try addStarAnimation(for: star, context: context, pathMultiplier: pathMultiplier)
21
+ case .polygon:
22
+ try addPolygonAnimation(for: star, context: context, pathMultiplier: pathMultiplier)
23
+ case .none:
24
+ break
25
+ }
26
+ }
27
+
28
+ // MARK: Private
29
+
30
+ @nonobjc
31
+ private func addStarAnimation(
32
+ for star: Star,
33
+ context: LayerAnimationContext,
34
+ pathMultiplier: PathMultiplier)
35
+ throws
36
+ {
37
+ try addAnimation(
38
+ for: .path,
39
+ keyframes: try star.combinedKeyframes(context: context).keyframes,
40
+ value: { keyframe in
41
+ BezierPath.star(
42
+ position: keyframe.position.pointValue,
43
+ outerRadius: keyframe.outerRadius.cgFloatValue,
44
+ innerRadius: keyframe.innerRadius.cgFloatValue,
45
+ outerRoundedness: keyframe.outerRoundness.cgFloatValue,
46
+ innerRoundedness: keyframe.innerRoundness.cgFloatValue,
47
+ numberOfPoints: keyframe.points.cgFloatValue,
48
+ rotation: keyframe.rotation.cgFloatValue,
49
+ direction: star.direction)
50
+ .cgPath()
51
+ .duplicated(times: pathMultiplier)
52
+ },
53
+ context: context)
54
+ }
55
+
56
+ @nonobjc
57
+ private func addPolygonAnimation(
58
+ for star: Star,
59
+ context: LayerAnimationContext,
60
+ pathMultiplier: PathMultiplier)
61
+ throws
62
+ {
63
+ try addAnimation(
64
+ for: .path,
65
+ keyframes: try star.combinedKeyframes(context: context).keyframes,
66
+ value: { keyframe in
67
+ BezierPath.polygon(
68
+ position: keyframe.position.pointValue,
69
+ numberOfPoints: keyframe.points.cgFloatValue,
70
+ outerRadius: keyframe.outerRadius.cgFloatValue,
71
+ outerRoundedness: keyframe.outerRoundness.cgFloatValue,
72
+ rotation: keyframe.rotation.cgFloatValue,
73
+ direction: star.direction)
74
+ .cgPath()
75
+ .duplicated(times: pathMultiplier)
76
+ },
77
+ context: context)
78
+ }
79
+ }
80
+
81
+ extension Star {
82
+ /// Data that represents how to render a star at a specific point in time
83
+ struct Keyframe {
84
+ let position: Vector3D
85
+ let outerRadius: Vector1D
86
+ let innerRadius: Vector1D
87
+ let outerRoundness: Vector1D
88
+ let innerRoundness: Vector1D
89
+ let points: Vector1D
90
+ let rotation: Vector1D
91
+ }
92
+
93
+ /// Creates a single array of animatable keyframes from the separate arrays of keyframes in this star/polygon
94
+ func combinedKeyframes(context: LayerAnimationContext) throws -> KeyframeGroup<Keyframe> {
95
+ let combinedKeyframes = Keyframes.combinedIfPossible(
96
+ position,
97
+ outerRadius,
98
+ innerRadius ?? KeyframeGroup(Vector1D(0)),
99
+ outerRoundness,
100
+ innerRoundness ?? KeyframeGroup(Vector1D(0)),
101
+ points,
102
+ rotation,
103
+ makeCombinedResult: Star.Keyframe.init)
104
+
105
+ if let combinedKeyframes = combinedKeyframes {
106
+ return combinedKeyframes
107
+ } else {
108
+ // If we weren't able to combine all of the keyframes, we have to take the timing values
109
+ // from one property and use a fixed value for the other properties.
110
+ return try position.map { positionValue in
111
+ Keyframe(
112
+ position: positionValue,
113
+ outerRadius: try outerRadius.exactlyOneKeyframe(context: context, description: "star outerRadius"),
114
+ innerRadius: try innerRadius?.exactlyOneKeyframe(context: context, description: "star innerRadius")
115
+ ?? Vector1D(0),
116
+ outerRoundness: try outerRoundness.exactlyOneKeyframe(context: context, description: "star outerRoundness"),
117
+ innerRoundness: try innerRoundness?.exactlyOneKeyframe(context: context, description: "star innerRoundness")
118
+ ?? Vector1D(0),
119
+ points: try points.exactlyOneKeyframe(context: context, description: "star points"),
120
+ rotation: try rotation.exactlyOneKeyframe(context: context, description: "star rotation"))
121
+ }
122
+ }
123
+ }
124
+ }
@@ -0,0 +1,73 @@
1
+ // Created by Cal Stephens on 2/10/22.
2
+ // Copyright © 2022 Airbnb Inc. All rights reserved.
3
+
4
+ import Foundation
5
+ import QuartzCore
6
+
7
+ // MARK: - StrokeShapeItem
8
+
9
+ /// A `ShapeItem` that represents a stroke
10
+ protocol StrokeShapeItem: OpacityAnimationModel {
11
+ var strokeColor: KeyframeGroup<Color>? { get }
12
+ var width: KeyframeGroup<Vector1D> { get }
13
+ var lineCap: LineCap { get }
14
+ var lineJoin: LineJoin { get }
15
+ var miterLimit: Double { get }
16
+ var dashPattern: [DashElement]? { get }
17
+ }
18
+
19
+ // MARK: - Stroke + StrokeShapeItem
20
+
21
+ extension Stroke: StrokeShapeItem {
22
+ var strokeColor: KeyframeGroup<Color>? { color }
23
+ }
24
+
25
+ // MARK: - GradientStroke + StrokeShapeItem
26
+
27
+ extension GradientStroke: StrokeShapeItem {
28
+ var strokeColor: KeyframeGroup<Color>? { nil }
29
+ }
30
+
31
+ // MARK: - CAShapeLayer + StrokeShapeItem
32
+
33
+ extension CAShapeLayer {
34
+ /// Adds animations for properties related to the given `Stroke` object (`strokeColor`, `lineWidth`, etc)
35
+ @nonobjc
36
+ func addStrokeAnimations(for stroke: StrokeShapeItem, context: LayerAnimationContext) throws {
37
+ lineJoin = stroke.lineJoin.caLineJoin
38
+ lineCap = stroke.lineCap.caLineCap
39
+ miterLimit = CGFloat(stroke.miterLimit)
40
+
41
+ if let strokeColor = stroke.strokeColor {
42
+ try addAnimation(
43
+ for: .strokeColor,
44
+ keyframes: strokeColor.keyframes,
45
+ value: \.cgColorValue,
46
+ context: context)
47
+ }
48
+
49
+ try addAnimation(
50
+ for: .lineWidth,
51
+ keyframes: stroke.width.keyframes,
52
+ value: \.cgFloatValue,
53
+ context: context)
54
+
55
+ try addOpacityAnimation(for: stroke, context: context)
56
+
57
+ if let (dashPattern, dashPhase) = stroke.dashPattern?.shapeLayerConfiguration {
58
+ lineDashPattern = try dashPattern.map {
59
+ try KeyframeGroup(keyframes: $0)
60
+ .exactlyOneKeyframe(context: context, description: "stroke dashPattern").cgFloatValue as NSNumber
61
+ }
62
+ if lineDashPattern?.allSatisfy({ $0.floatValue.isZero }) == true {
63
+ lineDashPattern = nil
64
+ }
65
+
66
+ try addAnimation(
67
+ for: .lineDashPhase,
68
+ keyframes: dashPhase,
69
+ value: \.cgFloatValue,
70
+ context: context)
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,205 @@
1
+ // Created by Cal Stephens on 12/17/21.
2
+ // Copyright © 2021 Airbnb Inc. All rights reserved.
3
+
4
+ import QuartzCore
5
+
6
+ // MARK: - TransformModel
7
+
8
+ /// This protocol mirrors the interface of `Transform`,
9
+ /// but it also implemented by `ShapeTransform` to allow
10
+ /// both transform types to share the same animation implementation.
11
+ protocol TransformModel {
12
+ /// The anchor point of the transform.
13
+ var anchorPoint: KeyframeGroup<Vector3D> { get }
14
+
15
+ /// The position of the transform. This is nil if the position data was split.
16
+ var _position: KeyframeGroup<Vector3D>? { get }
17
+
18
+ /// The positionX of the transform. This is nil if the position property is set.
19
+ var _positionX: KeyframeGroup<Vector1D>? { get }
20
+
21
+ /// The positionY of the transform. This is nil if the position property is set.
22
+ var _positionY: KeyframeGroup<Vector1D>? { get }
23
+
24
+ /// The scale of the transform
25
+ var scale: KeyframeGroup<Vector3D> { get }
26
+
27
+ /// The rotation of the transform. Note: This is single dimensional rotation.
28
+ var rotation: KeyframeGroup<Vector1D> { get }
29
+ }
30
+
31
+ // MARK: - Transform + TransformModel
32
+
33
+ extension Transform: TransformModel {
34
+ var _position: KeyframeGroup<Vector3D>? { position }
35
+ var _positionX: KeyframeGroup<Vector1D>? { positionX }
36
+ var _positionY: KeyframeGroup<Vector1D>? { positionY }
37
+ }
38
+
39
+ // MARK: - ShapeTransform + TransformModel
40
+
41
+ extension ShapeTransform: TransformModel {
42
+ var anchorPoint: KeyframeGroup<Vector3D> { anchor }
43
+ var _position: KeyframeGroup<Vector3D>? { position }
44
+ var _positionX: KeyframeGroup<Vector1D>? { nil }
45
+ var _positionY: KeyframeGroup<Vector1D>? { nil }
46
+ }
47
+
48
+ // MARK: - CALayer + TransformModel
49
+
50
+ extension CALayer {
51
+
52
+ // MARK: Internal
53
+
54
+ /// Adds transform-related animations from the given `TransformModel` to this layer
55
+ /// - This _doesn't_ apply `transform.opacity`, which has to be handled separately
56
+ /// since child layers don't inherit the `opacity` of their parent.
57
+ @nonobjc
58
+ func addTransformAnimations(for transformModel: TransformModel, context: LayerAnimationContext) throws {
59
+ try addPositionAnimations(from: transformModel, context: context)
60
+ try addAnchorPointAnimation(from: transformModel, context: context)
61
+ try addScaleAnimations(from: transformModel, context: context)
62
+ try addRotationAnimation(from: transformModel, context: context)
63
+ }
64
+
65
+ // MARK: Private
66
+
67
+ @nonobjc
68
+ private func addPositionAnimations(
69
+ from transformModel: TransformModel,
70
+ context: LayerAnimationContext)
71
+ throws
72
+ {
73
+ if let positionKeyframes = transformModel._position?.keyframes {
74
+ try addAnimation(
75
+ for: .position,
76
+ keyframes: positionKeyframes,
77
+ value: \.pointValue,
78
+ context: context)
79
+ } else if
80
+ let xKeyframes = transformModel._positionX?.keyframes,
81
+ let yKeyframes = transformModel._positionY?.keyframes
82
+ {
83
+ try addAnimation(
84
+ for: .positionX,
85
+ keyframes: xKeyframes,
86
+ value: \.cgFloatValue,
87
+ context: context)
88
+
89
+ try addAnimation(
90
+ for: .positionY,
91
+ keyframes: yKeyframes,
92
+ value: \.cgFloatValue,
93
+ context: context)
94
+ } else {
95
+ try context.logCompatibilityIssue("""
96
+ `Transform` values must provide either `position` or `positionX` / `positionY` keyframes
97
+ """)
98
+ }
99
+ }
100
+
101
+ @nonobjc
102
+ private func addAnchorPointAnimation(
103
+ from transformModel: TransformModel,
104
+ context: LayerAnimationContext)
105
+ throws
106
+ {
107
+ try addAnimation(
108
+ for: .anchorPoint,
109
+ keyframes: transformModel.anchorPoint.keyframes,
110
+ value: { absoluteAnchorPoint in
111
+ guard bounds.width > 0, bounds.height > 0 else {
112
+ context.logger.assertionFailure("Size must be non-zero before an animation can be played")
113
+ return .zero
114
+ }
115
+
116
+ // Lottie animation files express anchorPoint as an absolute point value,
117
+ // so we have to divide by the width/height of this layer to get the
118
+ // relative decimal values expected by Core Animation.
119
+ return CGPoint(
120
+ x: CGFloat(absoluteAnchorPoint.x) / bounds.width,
121
+ y: CGFloat(absoluteAnchorPoint.y) / bounds.height)
122
+ },
123
+ context: context)
124
+ }
125
+
126
+ @nonobjc
127
+ private func addScaleAnimations(
128
+ from transformModel: TransformModel,
129
+ context: LayerAnimationContext)
130
+ throws
131
+ {
132
+ try addAnimation(
133
+ for: .scaleX,
134
+ keyframes: transformModel.scale.keyframes,
135
+ value: { scale in
136
+ // Lottie animation files express scale as a numerical percentage value
137
+ // (e.g. 50%, 100%, 200%) so we divide by 100 to get the decimal values
138
+ // expected by Core Animation (e.g. 0.5, 1.0, 2.0).
139
+ // - Negative `scale.x` values aren't applied correctly by Core Animation.
140
+ // This appears to be because we animate `transform.scale.x` and `transform.scale.y`
141
+ // as separate `CAKeyframeAnimation`s instead of using a single animation of `transform` itself.
142
+ // https://openradar.appspot.com/FB9862872
143
+ // - To work around this, we set up a `rotationY` animation below
144
+ // to flip the view horizontally, which gives us the desired effect.
145
+ abs(CGFloat(scale.x) / 100)
146
+ },
147
+ context: context)
148
+
149
+ // When `scale.x` is negative, we have to rotate the view
150
+ // half way around the y axis to flip it horizontally.
151
+ // - We don't do this in snapshot tests because it breaks the tests
152
+ // in surprising ways that don't happen at runtime. Definitely not ideal.
153
+ if TestHelpers.snapshotTestsAreRunning {
154
+ if transformModel.scale.keyframes.contains(where: { $0.value.x < 0 }) {
155
+ context.logger.warn("""
156
+ Negative `scale.x` values are not displayed correctly in snapshot tests
157
+ """)
158
+ }
159
+ } else {
160
+ try addAnimation(
161
+ for: .rotationY,
162
+ keyframes: transformModel.scale.keyframes,
163
+ value: { scale in
164
+ if scale.x < 0 {
165
+ return .pi
166
+ } else {
167
+ return 0
168
+ }
169
+ },
170
+ context: context)
171
+ }
172
+
173
+ try addAnimation(
174
+ for: .scaleY,
175
+ keyframes: transformModel.scale.keyframes,
176
+ value: { scale in
177
+ // Lottie animation files express scale as a numerical percentage value
178
+ // (e.g. 50%, 100%, 200%) so we divide by 100 to get the decimal values
179
+ // expected by Core Animation (e.g. 0.5, 1.0, 2.0).
180
+ // - Negative `scaleY` values are correctly applied (they flip the view
181
+ // vertically), so we don't have to apply an additional rotation animation
182
+ // like we do for `scaleX`.
183
+ CGFloat(scale.y) / 100
184
+ },
185
+ context: context)
186
+ }
187
+
188
+ private func addRotationAnimation(
189
+ from transformModel: TransformModel,
190
+ context: LayerAnimationContext)
191
+ throws
192
+ {
193
+ try addAnimation(
194
+ for: .rotation,
195
+ keyframes: transformModel.rotation.keyframes,
196
+ value: { rotationDegrees in
197
+ // Lottie animation files express rotation in degrees
198
+ // (e.g. 90º, 180º, 360º) so we covert to radians to get the
199
+ // values expected by Core Animation (e.g. π/2, π, 2π)
200
+ rotationDegrees.cgFloatValue * .pi / 180
201
+ },
202
+ context: context)
203
+ }
204
+
205
+ }
@@ -0,0 +1,37 @@
1
+ // Created by Cal Stephens on 12/21/21.
2
+ // Copyright © 2021 Airbnb Inc. All rights reserved.
3
+
4
+ import QuartzCore
5
+
6
+ extension CALayer {
7
+ /// Adds an animation for the given `inTime` and `outTime` to this `CALayer`
8
+ @nonobjc
9
+ func addVisibilityAnimation(
10
+ inFrame: AnimationFrameTime,
11
+ outFrame: AnimationFrameTime,
12
+ context: LayerAnimationContext)
13
+ {
14
+ let animation = CAKeyframeAnimation(keyPath: #keyPath(isHidden))
15
+ animation.calculationMode = .discrete
16
+
17
+ animation.values = [
18
+ true, // hidden, before `inFrame`
19
+ false, // visible
20
+ true, // hidden, after `outFrame`
21
+ ]
22
+
23
+ // From the documentation of `keyTimes`:
24
+ // - If the calculationMode is set to discrete, the first value in the array
25
+ // must be 0.0 and the last value must be 1.0. The array should have one more
26
+ // entry than appears in the values array. For example, if there are two values,
27
+ // there should be three key times.
28
+ animation.keyTimes = [
29
+ NSNumber(value: 0.0),
30
+ NSNumber(value: max(Double(context.progressTime(for: inFrame)), 0)),
31
+ NSNumber(value: min(Double(context.progressTime(for: outFrame)), 1)),
32
+ NSNumber(value: 1.0),
33
+ ]
34
+
35
+ add(animation, timedWith: context)
36
+ }
37
+ }