Skip to content

Commit

Permalink
Update text provider API to use full AnimationKeypath values (airbnb#…
Browse files Browse the repository at this point in the history
  • Loading branch information
calda authored and iago849 committed Feb 8, 2024
1 parent d9eb26f commit a0c713b
Show file tree
Hide file tree
Showing 38 changed files with 412 additions and 60 deletions.
4 changes: 4 additions & 0 deletions Lottie.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@
080DF07F2A95718200BE2D96 /* AnimationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E9C95D22822F43100677516 /* AnimationContext.swift */; };
080DF0802A95718200BE2D96 /* AnyEquatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABF033B32A7B0ABA00F8C228 /* AnyEquatable.swift */; };
080DF0812A95718200BE2D96 /* LottieAnimationSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0819D2A02A718CAE00D7DE49 /* LottieAnimationSource.swift */; };
080F5FDC2AB1075000ADC32C /* TextProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 080F5FDB2AB1075000ADC32C /* TextProviderTests.swift */; };
0819D2A12A718CAE00D7DE49 /* LottieAnimationSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0819D2A02A718CAE00D7DE49 /* LottieAnimationSource.swift */; };
0819D2A22A718CAE00D7DE49 /* LottieAnimationSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0819D2A02A718CAE00D7DE49 /* LottieAnimationSource.swift */; };
0819D2A32A718CAE00D7DE49 /* LottieAnimationSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0819D2A02A718CAE00D7DE49 /* LottieAnimationSource.swift */; };
Expand Down Expand Up @@ -1159,6 +1160,7 @@

/* Begin PBXFileReference section */
080DEF622A95707B00BE2D96 /* Lottie.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Lottie.framework; sourceTree = BUILT_PRODUCTS_DIR; };
080F5FDB2AB1075000ADC32C /* TextProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextProviderTests.swift; sourceTree = "<group>"; };
0819D2A02A718CAE00D7DE49 /* LottieAnimationSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LottieAnimationSource.swift; sourceTree = "<group>"; };
0820D5922A8ACD67007D705C /* LottieButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LottieButton.swift; sourceTree = "<group>"; };
0820D5962A8ACDD7007D705C /* AnimatedButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimatedButton.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1752,6 +1754,7 @@
D453D8AE28FF9BC600D3F49C /* AnimationCacheProviderTests.swift */,
08CB2680291ED2B700B4F071 /* AnimationViewTests.swift */,
2E70F79E295BB6D30089A0EF /* CompatibleAnimationViewTests.swift */,
080F5FDB2AB1075000ADC32C /* TextProviderTests.swift */,
);
path = Tests;
sourceTree = "<group>";
Expand Down Expand Up @@ -3233,6 +3236,7 @@
36E57EAC28AF7ADF00B7EFDA /* HardcodedTextProvider.swift in Sources */,
2E72128527BB32DB0027BC56 /* PerformanceTests.swift in Sources */,
6DB3BDC328245AA2002A276D /* ParsingTests.swift in Sources */,
080F5FDC2AB1075000ADC32C /* TextProviderTests.swift in Sources */,
6DB3BDB628243FA5002A276D /* ValueProvidersTests.swift in Sources */,
2E72128327BB329C0027BC56 /* AnimationKeypathTests.swift in Sources */,
2E044E272820536800FA773B /* AutomaticEngineTests.swift in Sources */,
Expand Down
8 changes: 4 additions & 4 deletions Sources/Private/CoreAnimation/CoreAnimationLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ final class CoreAnimationLayer: BaseAnimationLayer {
init(
animation: LottieAnimation,
imageProvider: AnimationImageProvider,
textProvider: AnimationTextProvider,
textProvider: AnimationKeypathTextProvider,
fontProvider: AnimationFontProvider,
maskAnimationToBounds: Bool,
compatibilityTrackerMode: CompatibilityTracker.Mode,
Expand Down Expand Up @@ -108,9 +108,9 @@ final class CoreAnimationLayer: BaseAnimationLayer {
didSet { reloadImages() }
}

/// The `AnimationTextProvider` that `TextLayer`'s use to retrieve texts,
/// The `AnimationKeypathTextProvider` that `TextLayer`'s use to retrieve texts,
/// that they should use to render their text context
var textProvider: AnimationTextProvider {
var textProvider: AnimationKeypathTextProvider {
didSet {
// We need to rebuild the current animation after updating the text provider,
// since this is used in `TextLayer.setupAnimations(context:)`
Expand Down Expand Up @@ -449,7 +449,7 @@ extension CoreAnimationLayer: RootAnimationLayer {
}

func forceDisplayUpdate() {
// Unimplemented / unused
display()
}

func logHierarchyKeypaths() {
Expand Down
4 changes: 2 additions & 2 deletions Sources/Private/CoreAnimation/Layers/AnimationLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ struct LayerAnimationContext {
/// The AnimationKeypath represented by the current layer
var currentKeypath: AnimationKeypath

/// The `AnimationTextProvider`
var textProvider: AnimationTextProvider
/// The `AnimationKeypathTextProvider`
var textProvider: AnimationKeypathTextProvider

/// Records the given animation keypath so it can be logged or collected into a list
/// - Used for `CoreAnimationLayer.logHierarchyKeypaths()` and `allHierarchyKeypaths()`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import QuartzCore
struct LayerContext {
let animation: LottieAnimation
let imageProvider: AnimationImageProvider
let textProvider: AnimationTextProvider
let textProvider: AnimationKeypathTextProvider
let fontProvider: AnimationFontProvider
let compatibilityTracker: CompatibilityTracker
var layerName: String
Expand Down
16 changes: 13 additions & 3 deletions Sources/Private/CoreAnimation/Layers/TextLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,19 @@ final class TextLayer: BaseCompositionLayer {
context: textAnimationContext,
description: "text layer text")

renderLayer.text = context.textProvider.textFor(
keypathName: textAnimationContext.currentKeypath.fullPath,
sourceText: sourceText.text)
// Prior to Lottie 4.3.0 the Core Animation rendering engine always just used `LegacyAnimationTextProvider`
// but incorrectly called it with the full keypath string, unlike the Main Thread rendering engine
// which only used the last component of the keypath. Starting in Lottie 4.3.0 we use `AnimationKeypathTextProvider`
// instead if implemented.
if let keypathTextValue = context.textProvider.text(for: textAnimationContext.currentKeypath, sourceText: sourceText.text) {
renderLayer.text = keypathTextValue
} else if let legacyTextProvider = context.textProvider as? LegacyAnimationTextProvider {
renderLayer.text = legacyTextProvider.textFor(
keypathName: textAnimationContext.currentKeypath.fullPath,
sourceText: sourceText.text)
} else {
renderLayer.text = sourceText.text
}

renderLayer.sizeToFit()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ final class PreCompositionLayer: CompositionLayer {
precomp: PreCompLayerModel,
asset: PrecompAsset,
layerImageProvider: LayerImageProvider,
textProvider: AnimationTextProvider,
layerTextProvider: LayerTextProvider,
textProvider: AnimationKeypathTextProvider,
fontProvider: AnimationFontProvider,
assetLibrary: AssetLibrary?,
frameRate: CGFloat)
frameRate: CGFloat,
rootAnimationLayer: MainThreadAnimationLayer?)
{
animationLayers = []
if let keyframes = precomp.timeRemapping?.keyframes {
Expand All @@ -36,11 +38,14 @@ final class PreCompositionLayer: CompositionLayer {
let layers = asset.layers.initializeCompositionLayers(
assetLibrary: assetLibrary,
layerImageProvider: layerImageProvider,
layerTextProvider: layerTextProvider,
textProvider: textProvider,
fontProvider: fontProvider,
frameRate: frameRate)
frameRate: frameRate,
rootAnimationLayer: rootAnimationLayer)

var imageLayers = [ImageCompositionLayer]()
var textLayers = [TextCompositionLayer]()

var mattedLayer: CompositionLayer? = nil

Expand All @@ -50,6 +55,9 @@ final class PreCompositionLayer: CompositionLayer {
if let imageLayer = layer as? ImageCompositionLayer {
imageLayers.append(imageLayer)
}
if let textLayer = layer as? TextCompositionLayer {
textLayers.append(textLayer)
}
if let matte = mattedLayer {
/// The previous layer requires this layer to be its matte
matte.matteLayer = layer
Expand All @@ -69,6 +77,7 @@ final class PreCompositionLayer: CompositionLayer {
childKeypaths.append(contentsOf: layers)

layerImageProvider.addImageLayers(imageLayers)
layerTextProvider.addTextLayers(textLayers)
}

override init(layer: Any) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@ final class TextCompositionLayer: CompositionLayer {

// MARK: Lifecycle

init(textLayer: TextLayerModel, textProvider: AnimationTextProvider, fontProvider: AnimationFontProvider) {
init(
textLayer: TextLayerModel,
textProvider: AnimationKeypathTextProvider,
fontProvider: AnimationFontProvider,
rootAnimationLayer: MainThreadAnimationLayer?)
{
var rootNode: TextAnimatorNode?
for animator in textLayer.animators {
rootNode = TextAnimatorNode(parentNode: rootNode, textAnimator: animator)
Expand All @@ -57,6 +62,7 @@ final class TextCompositionLayer: CompositionLayer {

self.textProvider = textProvider
self.fontProvider = fontProvider
self.rootAnimationLayer = rootAnimationLayer

super.init(layer: textLayer, size: .zero)
contentsLayer.addSublayer(self.textLayer)
Expand Down Expand Up @@ -92,8 +98,18 @@ final class TextCompositionLayer: CompositionLayer {
let textDocument: KeyframeInterpolator<TextDocument>?

let textLayer = CoreTextRenderLayer()
var textProvider: AnimationTextProvider
var textProvider: AnimationKeypathTextProvider
var fontProvider: AnimationFontProvider
weak var rootAnimationLayer: MainThreadAnimationLayer?

lazy var fullAnimationKeypath: AnimationKeypath = {
// Individual layers don't know their full keypaths, so we have to delegate
// to the `MainThreadAnimationLayer` to search the layer hierarchy and find
// the full keypath (which includes this layer's parent layers)
rootAnimationLayer?.keypath(for: self)
// If that failed for some reason, just use the last path component (which we do have here)
?? AnimationKeypath(keypath: keypathName)
}()

override func displayContentsWithFrame(frame: CGFloat, forceUpdates: Bool) {
guard let textDocument = textDocument else { return }
Expand All @@ -108,11 +124,23 @@ final class TextCompositionLayer: CompositionLayer {

// Get Text Attributes
let text = textDocument.value(frame: frame) as! TextDocument

// Prior to Lottie 4.3.0 the Main Thread rendering engine always just used `LegacyAnimationTextProvider`
// and called it with the `keypathName` (only the last path component of the full keypath).
// Starting in Lottie 4.3.0 we use `AnimationKeypathTextProvider` instead if implemented.
let textString: String
if let keypathTextValue = textProvider.text(for: fullAnimationKeypath, sourceText: text.text) {
textString = keypathTextValue
} else if let legacyTextProvider = textProvider as? LegacyAnimationTextProvider {
textString = legacyTextProvider.textFor(keypathName: keypathName, sourceText: text.text)
} else {
textString = text.text
}

let strokeColor = rootNode?.textOutputNode.strokeColor ?? text.strokeColorData?.cgColorValue
let strokeWidth = rootNode?.textOutputNode.strokeWidth ?? CGFloat(text.strokeWidth ?? 0)
let tracking = (CGFloat(text.fontSize) * (rootNode?.textOutputNode.tracking ?? CGFloat(text.tracking))) / 1000.0
let matrix = rootNode?.textOutputNode.xform ?? CATransform3DIdentity
let textString = textProvider.textFor(keypathName: keypathName, sourceText: text.text)
let ctFont = fontProvider.fontFor(family: text.fontFamily, size: CGFloat(text.fontSize))

// Set all of the text layer options
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ final class MainThreadAnimationLayer: CALayer, RootAnimationLayer {
init(
animation: LottieAnimation,
imageProvider: AnimationImageProvider,
textProvider: AnimationTextProvider,
textProvider: AnimationKeypathTextProvider,
fontProvider: AnimationFontProvider,
maskAnimationToBounds: Bool,
logger: LottieLogger)
Expand All @@ -37,9 +37,11 @@ final class MainThreadAnimationLayer: CALayer, RootAnimationLayer {
let layers = animation.layers.initializeCompositionLayers(
assetLibrary: animation.assetLibrary,
layerImageProvider: layerImageProvider,
layerTextProvider: layerTextProvider,
textProvider: textProvider,
fontProvider: fontProvider,
frameRate: CGFloat(animation.framerate))
frameRate: CGFloat(animation.framerate),
rootAnimationLayer: self)

var imageLayers = [ImageCompositionLayer]()
var textLayers = [TextCompositionLayer]()
Expand Down Expand Up @@ -191,7 +193,7 @@ final class MainThreadAnimationLayer: CALayer, RootAnimationLayer {
}
}

var textProvider: AnimationTextProvider {
var textProvider: AnimationKeypathTextProvider {
get { layerTextProvider.textProvider }
set { layerTextProvider.textProvider = newValue }
}
Expand Down Expand Up @@ -272,6 +274,15 @@ final class MainThreadAnimationLayer: CALayer, RootAnimationLayer {
return nil
}

func keypath(for layerToFind: CALayer) -> AnimationKeypath? {
for layer in animationLayers {
if let foundKeypath = layer.keypath(for: layerToFind) {
return foundKeypath
}
}
return nil
}

func animatorNodes(for keypath: AnimationKeypath) -> [AnimatorNode]? {
var results = [AnimatorNode]()
for layer in animationLayers {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ extension Array where Element == LayerModel {
func initializeCompositionLayers(
assetLibrary: AssetLibrary?,
layerImageProvider: LayerImageProvider,
textProvider: AnimationTextProvider,
layerTextProvider: LayerTextProvider,
textProvider: AnimationKeypathTextProvider,
fontProvider: AnimationFontProvider,
frameRate: CGFloat) -> [CompositionLayer]
frameRate: CGFloat,
rootAnimationLayer: MainThreadAnimationLayer?)
-> [CompositionLayer]
{
var compositionLayers = [CompositionLayer]()
var layerMap = [Int : CompositionLayer]()
Expand Down Expand Up @@ -45,10 +48,12 @@ extension Array where Element == LayerModel {
precomp: precompLayer,
asset: precompAsset,
layerImageProvider: layerImageProvider,
layerTextProvider: layerTextProvider,
textProvider: textProvider,
fontProvider: fontProvider,
assetLibrary: assetLibrary,
frameRate: frameRate)
frameRate: frameRate,
rootAnimationLayer: rootAnimationLayer)
compositionLayers.append(precompContainer)
layerMap[layer.index] = precompContainer
} else if
Expand All @@ -62,7 +67,11 @@ extension Array where Element == LayerModel {
compositionLayers.append(imageContainer)
layerMap[layer.index] = imageContainer
} else if let textLayer = layer as? TextLayerModel {
let textContainer = TextCompositionLayer(textLayer: textLayer, textProvider: textProvider, fontProvider: fontProvider)
let textContainer = TextCompositionLayer(
textLayer: textLayer,
textProvider: textProvider,
fontProvider: fontProvider,
rootAnimationLayer: rootAnimationLayer)
compositionLayers.append(textContainer)
layerMap[layer.index] = textContainer
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ final class LayerTextProvider {

// MARK: Lifecycle

init(textProvider: AnimationTextProvider) {
init(textProvider: AnimationKeypathTextProvider) {
self.textProvider = textProvider
textLayers = []
reloadTexts()
Expand All @@ -22,7 +22,7 @@ final class LayerTextProvider {

private(set) var textLayers: [TextCompositionLayer]

var textProvider: AnimationTextProvider {
var textProvider: AnimationKeypathTextProvider {
didSet {
reloadTexts()
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Private/RootAnimationLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ protocol RootAnimationLayer: CALayer {

var _animationLayers: [CALayer] { get }
var imageProvider: AnimationImageProvider { get set }
var textProvider: AnimationTextProvider { get set }
var textProvider: AnimationKeypathTextProvider { get set }
var fontProvider: AnimationFontProvider { get set }

/// The `CAAnimation` key corresponding to the primary animation.
Expand Down
30 changes: 30 additions & 0 deletions Sources/Private/Utility/Extensions/AnimationKeypathExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ extension KeypathSearchable {
return nil
}

/// Searches this layer's keypaths to find the keypath for the given layer
func keypath(for layer: CALayer) -> AnimationKeypath? {
let allKeypaths = layerKeypaths()
return allKeypaths[layer]
}

/// Computes the list of animation keypaths that descend from this layer
func allKeypaths(for keyPath: AnimationKeypath? = nil) -> [String] {
var allKeypaths: [String] = []
Expand All @@ -138,6 +144,30 @@ extension KeypathSearchable {

return allKeypaths
}

/// Computes the list of animation keypaths that descend from this layer
func layerKeypaths(for keyPath: AnimationKeypath? = nil) -> [CALayer: AnimationKeypath] {
var allKeypaths: [CALayer: AnimationKeypath] = [:]

let newKeypath: AnimationKeypath
if let previousKeypath = keyPath {
newKeypath = previousKeypath.appendingKey(keypathName)
} else {
newKeypath = AnimationKeypath(keys: [keypathName])
}

if let layer = self as? CALayer {
allKeypaths[layer] = newKeypath
}

for child in childKeypaths {
for (layer, keypath) in child.layerKeypaths(for: newKeypath) {
allKeypaths[layer] = keypath
}
}

return allKeypaths
}
}

extension AnimationKeypath {
Expand Down
Loading

0 comments on commit a0c713b

Please sign in to comment.