Skip to content

Commit

Permalink
Add TextProvider support to Core Animation rendering engine (airbnb…
Browse files Browse the repository at this point in the history
…#1723)

Co-authored-by: Cal Stephens <[email protected]>
  • Loading branch information
2 people authored and Igor Moroz committed May 22, 2024
1 parent f4b4b9d commit f3d4073
Show file tree
Hide file tree
Showing 30 changed files with 91 additions and 10 deletions.
4 changes: 4 additions & 0 deletions Lottie.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,7 @@
2EAF5B0427A0798700E00531 /* AnimationFontProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EAF59F227A0798700E00531 /* AnimationFontProvider.swift */; };
2EAF5B0527A0798700E00531 /* AnimationFontProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EAF59F227A0798700E00531 /* AnimationFontProvider.swift */; };
2EAF5B0627A0798700E00531 /* AnimationFontProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EAF59F227A0798700E00531 /* AnimationFontProvider.swift */; };
36E57EAC28AF7ADF00B7EFDA /* HardcodedTextProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E57EAB28AF7ADF00B7EFDA /* HardcodedTextProvider.swift */; };
6D0E635F28246BD0007C5DB6 /* Difference in Frameworks */ = {isa = PBXBuildFile; productRef = 6D0E635E28246BD0007C5DB6 /* Difference */; };
6D99D6432823790700E5205B /* LegacyGradientFillRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D99D6422823790700E5205B /* LegacyGradientFillRenderer.swift */; };
6D99D6442823790700E5205B /* LegacyGradientFillRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D99D6422823790700E5205B /* LegacyGradientFillRenderer.swift */; };
Expand Down Expand Up @@ -783,6 +784,7 @@
2EAF59EF27A0798700E00531 /* GradientValueProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GradientValueProvider.swift; sourceTree = "<group>"; };
2EAF59F027A0798700E00531 /* PointValueProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PointValueProvider.swift; sourceTree = "<group>"; };
2EAF59F227A0798700E00531 /* AnimationFontProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationFontProvider.swift; sourceTree = "<group>"; };
36E57EAB28AF7ADF00B7EFDA /* HardcodedTextProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HardcodedTextProvider.swift; sourceTree = "<group>"; };
6D99D6422823790700E5205B /* LegacyGradientFillRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyGradientFillRenderer.swift; sourceTree = "<group>"; };
6DB3BDB528243FA5002A276D /* ValueProvidersTests.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = ValueProvidersTests.swift; sourceTree = "<group>"; tabWidth = 2; };
6DB3BDB7282454A6002A276D /* DictionaryInitializable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DictionaryInitializable.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -876,6 +878,7 @@
2E8040BF27A07343006E74CB /* Snapshotting+presentationLayer.swift */,
2EAF59A627A076BC00E00531 /* Bundle+Module.swift */,
2E09FA0527B6CEB600BA84E5 /* HardcodedFontProvider.swift */,
36E57EAB28AF7ADF00B7EFDA /* HardcodedTextProvider.swift */,
);
path = Utils;
sourceTree = "<group>";
Expand Down Expand Up @@ -1876,6 +1879,7 @@
6DEF696E2824A76C007D640F /* BundleTests.swift in Sources */,
2EAF59A727A076BC00E00531 /* Bundle+Module.swift in Sources */,
2E8044AE27A07347006E74CB /* Snapshotting+presentationLayer.swift in Sources */,
36E57EAC28AF7ADF00B7EFDA /* HardcodedTextProvider.swift in Sources */,
2E72128527BB32DB0027BC56 /* PerformanceTests.swift in Sources */,
6DB3BDC328245AA2002A276D /* ParsingTests.swift in Sources */,
6DB3BDB628243FA5002A276D /* ValueProvidersTests.swift in Sources */,
Expand Down
24 changes: 15 additions & 9 deletions Sources/Private/CoreAnimation/CoreAnimationLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ final class CoreAnimationLayer: BaseAnimationLayer {
init(
animation: Animation,
imageProvider: AnimationImageProvider,
textProvider: AnimationTextProvider,
fontProvider: AnimationFontProvider,
compatibilityTrackerMode: CompatibilityTracker.Mode,
logger: LottieLogger)
throws
{
self.animation = animation
self.imageProvider = imageProvider
self.textProvider = textProvider
self.fontProvider = fontProvider
self.logger = logger
compatibilityTracker = CompatibilityTracker(mode: compatibilityTrackerMode, logger: logger)
Expand All @@ -44,6 +46,7 @@ final class CoreAnimationLayer: BaseAnimationLayer {
animation = typedLayer.animation
currentAnimationConfiguration = typedLayer.currentAnimationConfiguration
imageProvider = typedLayer.imageProvider
textProvider = typedLayer.textProvider
fontProvider = typedLayer.fontProvider
didSetUpAnimation = typedLayer.didSetUpAnimation
compatibilityTracker = typedLayer.compatibilityTracker
Expand Down Expand Up @@ -92,6 +95,16 @@ final class CoreAnimationLayer: BaseAnimationLayer {
didSet { reloadImages() }
}

/// The `AnimationTextProvider` that `TextLayer`'s use to retrieve texts,
/// that they should use to render their text context
var textProvider: AnimationTextProvider {
didSet {
// We need to rebuild the current animation after updating the text provider,
// since this is used in `TextLayer.setupAnimations(context:)`
rebuildCurrentAnimation()
}
}

/// The `FontProvider` that `TextLayer`s use to retrieve the `CTFont`
/// that they should use to render their text content
var fontProvider: AnimationFontProvider {
Expand Down Expand Up @@ -203,6 +216,7 @@ final class CoreAnimationLayer: BaseAnimationLayer {
LayerContext(
animation: animation,
imageProvider: imageProvider,
textProvider: textProvider,
fontProvider: fontProvider,
compatibilityTracker: compatibilityTracker,
layerName: "root layer")
Expand Down Expand Up @@ -234,6 +248,7 @@ final class CoreAnimationLayer: BaseAnimationLayer {
compatibilityTracker: compatibilityTracker,
logger: logger,
currentKeypath: AnimationKeypath(keys: []),
textProvider: textProvider,
logHierarchyKeypaths: configuration.logHierarchyKeypaths)

// Perform a layout pass if necessary so all of the sublayers
Expand Down Expand Up @@ -383,15 +398,6 @@ extension CoreAnimationLayer: RootAnimationLayer {
(sublayers ?? []).filter { $0 is AnimationLayer }
}

var textProvider: AnimationTextProvider {
get { DictionaryTextProvider([:]) }
set {
logger.assertionFailure("""
The Core Animation rendering engine currently doesn't support `textProvider`s")
""")
}
}

func reloadImages() {
// When the image provider changes, we have to update all `ImageLayer`s
// so they can query the most up-to-date image from the new image provider.
Expand Down
3 changes: 3 additions & 0 deletions Sources/Private/CoreAnimation/Layers/AnimationLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ struct LayerAnimationContext {
/// The AnimationKeypath represented by the current layer
var currentKeypath: AnimationKeypath

/// The `AnimationTextProvider`
var textProvider: AnimationTextProvider

/// Whether or not to log `AnimationKeypath`s for all of the animation's layers
/// - Used for `CoreAnimationLayer.logHierarchyKeypaths()`
var logHierarchyKeypaths: Bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import QuartzCore
struct LayerContext {
let animation: Animation
let imageProvider: AnimationImageProvider
let textProvider: AnimationTextProvider
let fontProvider: AnimationFontProvider
let compatibilityTracker: CompatibilityTracker
var layerName: String
Expand Down
16 changes: 15 additions & 1 deletion Sources/Private/CoreAnimation/Layers/TextLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@ final class TextLayer: BaseCompositionLayer {

// MARK: Internal

override func setupAnimations(context: LayerAnimationContext) throws {
try super.setupAnimations(context: context)
let textAnimationContext = context.addingKeypathComponent(textLayerModel.name)

let sourceText = try textLayerModel.text.exactlyOneKeyframe(
context: textAnimationContext,
description: "text layer text")

renderLayer.text = context.textProvider.textFor(
keypathName: textAnimationContext.currentKeypath.fullPath,
sourceText: sourceText.text)

renderLayer.sizeToFit()
}

func configureRenderLayer(with context: LayerContext) throws {
// We can't use `CATextLayer`, because it doesn't support enough features we use.
// Instead, we use the same `CoreTextRenderLayer` (with a custom `draw` implementation)
Expand All @@ -54,7 +69,6 @@ final class TextLayer: BaseCompositionLayer {
""")
}

renderLayer.text = text.text
renderLayer.font = context.fontProvider.fontFor(family: text.fontFamily, size: CGFloat(text.fontSize))

renderLayer.alignment = text.justification.textAlignment
Expand Down
2 changes: 2 additions & 0 deletions Sources/Public/Animation/AnimationView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,7 @@ final public class AnimationView: AnimationViewBase {
let coreAnimationLayer = try CoreAnimationLayer(
animation: animation,
imageProvider: imageProvider.cachedImageProvider,
textProvider: textProvider,
fontProvider: fontProvider,
compatibilityTrackerMode: .track,
logger: logger)
Expand Down Expand Up @@ -1030,6 +1031,7 @@ final public class AnimationView: AnimationViewBase {
let coreAnimationLayer = try CoreAnimationLayer(
animation: animation,
imageProvider: imageProvider.cachedImageProvider,
textProvider: textProvider,
fontProvider: fontProvider,
compatibilityTrackerMode: .abort,
logger: logger)
Expand Down
1 change: 1 addition & 0 deletions Tests/AutomaticEngineTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ final class AutomaticEngineTests: XCTestCase {
let animationLayer = try XCTUnwrap(CoreAnimationLayer(
animation: animation,
imageProvider: BundleImageProvider(bundle: Bundle.main, searchPath: nil),
textProvider: DefaultTextProvider(),
fontProvider: DefaultFontProvider(),
compatibilityTrackerMode: .track,
logger: .shared))
Expand Down
1 change: 1 addition & 0 deletions Tests/Samples/Issues/issue_1722.json

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions Tests/SnapshotConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ struct SnapshotConfiguration {
/// A custom `AnimationImageProvider` to use when rendering this animation
var customImageProvider: AnimationImageProvider?

/// A custom `AnimationTextProvider` to use when rendering this animation
var customTextProvider: AnimationTextProvider?

/// A custom `AnimationFontProvider` to use when rendering this animation
var customFontProvider: AnimationFontProvider?

Expand Down Expand Up @@ -70,6 +73,9 @@ extension SnapshotConfiguration {
// Test cases for `AnimatedImageProvider`
"Nonanimating/_dog": .customImageProvider(HardcodedImageProvider(imageName: "Samples/Images/dog.png")),

// Test cases for `AnimatedTextProvider`
"Issues/issue_1722": .customTextProvider(HardcodedTextProvider(text: "Bounce-bounce")),

// Test cases for `AnimationFontProvider`
"Nonanimating/Text_Glyph": .customFontProvider(HardcodedFontProvider(font: UIFont(name: "Chalkduster", size: 36)!)),

Expand Down Expand Up @@ -128,6 +134,15 @@ extension SnapshotConfiguration {
return configuration
}

static func customTextProvider(
_ customTextProvider: AnimationTextProvider)
-> SnapshotConfiguration
{
var configuration = SnapshotConfiguration.default
configuration.customTextProvider = customTextProvider
return configuration
}

/// A `SnapshotConfiguration` value using the given custom value providers
static func customFontProvider(
_ customFontProvider: AnimationFontProvider)
Expand Down
4 changes: 4 additions & 0 deletions Tests/SnapshotTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,10 @@ extension SnapshotConfiguration {
animationView.imageProvider = customImageProvider
}

if let customTextProvider = snapshotConfiguration.customTextProvider {
animationView.textProvider = customTextProvider
}

if let customFontProvider = snapshotConfiguration.customFontProvider {
animationView.fontProvider = customFontProvider
}
Expand Down
27 changes: 27 additions & 0 deletions Tests/Utils/HardcodedTextProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Created by Igor Katselenbogen on 08/19/22.
// Copyright © 2022 Airbnb Inc. All rights reserved.

import Lottie

// MARK: - HardcodedTextProvider

/// An `AnimationTextProvider` that always returns a specific hardcoded text
class HardcodedTextProvider: AnimationTextProvider {

// MARK: Lifecycle

init(text: String) {
self.text = text
}

// MARK: Internal

func textFor(keypathName _: String, sourceText _: String) -> String {
text
}

// MARK: Private

private let text: String

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Supports Core Animation engine
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
Does not support Core Animation engine. Encountered compatibility issues:
[TEXT] The Core Animation rendering engine does not support animating multiple keyframes for text layer text values (due to limitations of Core Animation `CAKeyframeAnimation`s).
[TEXT] The Core Animation rendering engine does not support animating multiple keyframes for text layer text values (due to limitations of Core Animation `CAKeyframeAnimation`s).
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
Does not support Core Animation engine. Encountered compatibility issues:
[TEXT] The Core Animation rendering engine does not support animating multiple keyframes for text layer text values (due to limitations of Core Animation `CAKeyframeAnimation`s).
[TEXT] The Core Animation rendering engine does not support animating multiple keyframes for text layer text values (due to limitations of Core Animation `CAKeyframeAnimation`s).
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit f3d4073

Please sign in to comment.