Skip to content

Commit

Permalink
Add TextProvider support to Core Animation rendering engine (#1723)
Browse files Browse the repository at this point in the history
Co-authored-by: Cal Stephens <[email protected]>
  • Loading branch information
lostinstaches and calda authored Aug 19, 2022
1 parent 13ecbee commit a1ba21f
Show file tree
Hide file tree
Showing 11 changed files with 88 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

}

0 comments on commit a1ba21f

Please sign in to comment.