lottie-ios 4.4.3 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/main.yml +6 -34
- package/Lottie.xcodeproj/project.pbxproj +28 -0
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +19 -6
- package/Package.resolved +2 -2
- package/Package.swift +1 -1
- package/README.md +2 -2
- package/Rakefile +1 -1
- package/Sources/Private/CoreAnimation/Animations/CALayer+addAnimation.swift +2 -2
- package/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift +6 -6
- package/Sources/Private/CoreAnimation/CoreAnimationLayer.swift +6 -6
- package/Sources/Private/CoreAnimation/Extensions/Keyframes+combined.swift +2 -2
- package/Sources/Private/CoreAnimation/Layers/AnimationLayer.swift +7 -7
- package/Sources/Private/CoreAnimation/Layers/ShapeItemLayer.swift +12 -12
- package/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift +19 -19
- package/Sources/Private/CoreAnimation/ValueProviderStore.swift +4 -4
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/SwiftUIMeasurementContainer.swift +13 -6
- package/Sources/Private/EmbeddedLibraries/LRUCache/LRUCache.swift +3 -3
- package/Sources/Private/EmbeddedLibraries/ZipFoundation/Archive+BackingConfiguration.swift +10 -6
- package/Sources/Private/EmbeddedLibraries/ZipFoundation/Archive+Helpers.swift +4 -0
- package/Sources/Private/EmbeddedLibraries/ZipFoundation/Archive+Progress.swift +2 -2
- package/Sources/Private/EmbeddedLibraries/ZipFoundation/Archive+Reading.swift +5 -0
- package/Sources/Private/EmbeddedLibraries/ZipFoundation/Archive+Writing.swift +2 -0
- package/Sources/Private/EmbeddedLibraries/ZipFoundation/Data+Compression.swift +1 -0
- package/Sources/Private/EmbeddedLibraries/ZipFoundation/Entry+ZIP64.swift +2 -2
- package/Sources/Private/EmbeddedLibraries/ZipFoundation/Entry.swift +1 -0
- package/Sources/Private/EmbeddedLibraries/ZipFoundation/FileManager+ZIP.swift +10 -9
- package/Sources/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.swift +19 -0
- package/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.swift +7 -7
- package/Sources/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.swift +3 -0
- package/Sources/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.swift +24 -14
- package/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift +11 -10
- package/Sources/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.swift +2 -0
- package/Sources/Private/MainThread/LayerContainers/Utility/CoreTextRenderLayer.swift +102 -17
- package/Sources/Private/MainThread/LayerContainers/Utility/LayerTransformNode.swift +11 -11
- package/Sources/Private/MainThread/NodeRenderSystem/Extensions/ItemsExtension.swift +2 -0
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/LayerEffectNodes/DropShadowNode.swift +102 -0
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/LayerEffectNodes/LayerEffectNode.swift +24 -0
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/FillRenderer.swift +4 -4
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/StrokeRenderer.swift +16 -16
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift +4 -4
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +5 -5
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift +60 -1
- package/Sources/Private/Model/DotLottie/DotLottieAnimation.swift +2 -2
- package/Sources/Private/Model/Keyframes/KeyframeGroup.swift +9 -6
- package/Sources/Private/Model/LayerEffects/EffectValues/EffectValue.swift +4 -4
- package/Sources/Private/Model/LayerEffects/LayerEffect.swift +2 -2
- package/Sources/Private/Model/LayerStyles/LayerStyle.swift +2 -2
- package/Sources/Private/Model/Layers/LayerModel.swift +7 -7
- package/Sources/Private/Model/ShapeItems/ShapeItem.swift +15 -15
- package/Sources/Private/Model/Text/TextAnimator.swift +47 -1
- package/Sources/Private/Utility/Debugging/LayerDebugging.swift +6 -6
- package/Sources/Private/Utility/Extensions/AnimationKeypathExtension.swift +16 -16
- package/Sources/Private/Utility/Extensions/BlendMode+Filter.swift +16 -16
- package/Sources/Private/Utility/Extensions/CGColor+RGB.swift +18 -0
- package/Sources/Private/Utility/Extensions/CGFloatExtensions.swift +23 -23
- package/Sources/Private/Utility/Extensions/MathKit.swift +4 -11
- package/Sources/Private/Utility/Interpolatable/InterpolatableExtensions.swift +9 -3
- package/Sources/Private/Utility/LottieAnimationSource.swift +4 -4
- package/Sources/Private/Utility/Primitives/BezierPath.swift +16 -2
- package/Sources/Private/Utility/Primitives/BezierPathRoundExtension.swift +2 -2
- package/Sources/Private/Utility/Primitives/ColorExtension.swift +20 -20
- package/Sources/Private/Utility/Primitives/CompoundBezierPath.swift +9 -9
- package/Sources/Public/Animation/LottieAnimationLayer.swift +36 -31
- package/Sources/Public/Animation/LottieAnimationView.swift +15 -15
- package/Sources/Public/Animation/LottieAnimationViewInitializers.swift +4 -0
- package/Sources/Public/Animation/LottiePlaybackMode.swift +12 -12
- package/Sources/Public/Animation/LottieView.swift +34 -1
- package/Sources/Public/Configuration/ReducedMotionOption.swift +5 -5
- package/Sources/Public/Configuration/RenderingEngineOption.swift +4 -4
- package/Sources/Public/Controls/AnimatedSwitch.swift +2 -2
- package/Sources/Public/DotLottie/DotLottieFile.swift +2 -2
- package/Sources/Public/DynamicProperties/AnyValueProvider.swift +9 -9
- package/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +2 -2
- package/Sources/Public/Keyframes/Interpolatable.swift +2 -2
- package/Sources/Public/Primitives/LottieColor.swift +3 -3
- package/Sources/Public/TextProvider/AnimationTextProvider.swift +4 -4
- package/Sources/Public/iOS/Compatibility/CompatibleAnimationView.swift +13 -12
- package/Version.xcconfig +1 -1
- package/lottie-ios.podspec +2 -2
- package/package.json +1 -1
|
@@ -83,6 +83,7 @@ extension Archive {
|
|
|
83
83
|
bufferSize: bufferSize,
|
|
84
84
|
progress: progress,
|
|
85
85
|
provider: provider)
|
|
86
|
+
|
|
86
87
|
case .deflate:
|
|
87
88
|
(sizeWritten, checksum) = try writeCompressed(
|
|
88
89
|
size: uncompressedSize,
|
|
@@ -90,9 +91,11 @@ extension Archive {
|
|
|
90
91
|
progress: progress,
|
|
91
92
|
provider: provider)
|
|
92
93
|
}
|
|
94
|
+
|
|
93
95
|
case .directory:
|
|
94
96
|
_ = try provider(0, 0)
|
|
95
97
|
if let progress { progress.completedUnitCount = progress.totalUnitCount }
|
|
98
|
+
|
|
96
99
|
case .symlink:
|
|
97
100
|
let (linkSizeWritten, linkChecksum) = try writeSymbolicLink(
|
|
98
101
|
size: Int(uncompressedSize),
|
|
@@ -226,6 +229,7 @@ extension Archive {
|
|
|
226
229
|
throw ArchiveError.invalidCentralDirectoryEntryCount
|
|
227
230
|
}
|
|
228
231
|
return (sizeOfCD + UInt64(cdDataLengthChange), numberOfTotalEntries + UInt64(countChange))
|
|
232
|
+
|
|
229
233
|
case .remove:
|
|
230
234
|
return (sizeOfCD - UInt64(-cdDataLengthChange), numberOfTotalEntries - UInt64(-countChange))
|
|
231
235
|
}
|
|
@@ -32,9 +32,9 @@ extension Archive {
|
|
|
32
32
|
func totalUnitCountForReading(_ entry: Entry) -> Int64 {
|
|
33
33
|
switch entry.type {
|
|
34
34
|
case .file, .symlink:
|
|
35
|
-
|
|
35
|
+
Int64(entry.uncompressedSize)
|
|
36
36
|
case .directory:
|
|
37
|
-
|
|
37
|
+
defaultDirectoryUnitCount
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -52,6 +52,7 @@ extension Archive {
|
|
|
52
52
|
skipCRC32: skipCRC32,
|
|
53
53
|
progress: progress,
|
|
54
54
|
consumer: consumer)
|
|
55
|
+
|
|
55
56
|
case .directory:
|
|
56
57
|
let consumer = { (_: Data) in
|
|
57
58
|
try fileManager.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
|
|
@@ -62,6 +63,7 @@ extension Archive {
|
|
|
62
63
|
skipCRC32: skipCRC32,
|
|
63
64
|
progress: progress,
|
|
64
65
|
consumer: consumer)
|
|
66
|
+
|
|
65
67
|
case .symlink:
|
|
66
68
|
guard !fileManager.itemExists(at: url) else {
|
|
67
69
|
throw CocoaError(.fileWriteFileExists, userInfo: [NSFilePathErrorKey: url.path])
|
|
@@ -121,6 +123,7 @@ extension Archive {
|
|
|
121
123
|
skipCRC32: skipCRC32,
|
|
122
124
|
progress: progress,
|
|
123
125
|
with: consumer)
|
|
126
|
+
|
|
124
127
|
case .deflate: checksum = try readCompressed(
|
|
125
128
|
entry: entry,
|
|
126
129
|
bufferSize: bufferSize,
|
|
@@ -128,9 +131,11 @@ extension Archive {
|
|
|
128
131
|
progress: progress,
|
|
129
132
|
with: consumer)
|
|
130
133
|
}
|
|
134
|
+
|
|
131
135
|
case .directory:
|
|
132
136
|
try consumer(Data())
|
|
133
137
|
progress?.completedUnitCount = totalUnitCountForReading(entry)
|
|
138
|
+
|
|
134
139
|
case .symlink:
|
|
135
140
|
let localFileHeader = entry.localFileHeader
|
|
136
141
|
let size = Int(localFileHeader.compressedSize)
|
|
@@ -96,6 +96,7 @@ extension Archive {
|
|
|
96
96
|
bufferSize: bufferSize,
|
|
97
97
|
progress: progress,
|
|
98
98
|
provider: provider)
|
|
99
|
+
|
|
99
100
|
case .directory:
|
|
100
101
|
provider = { _, _ in Data() }
|
|
101
102
|
try addEntry(
|
|
@@ -108,6 +109,7 @@ extension Archive {
|
|
|
108
109
|
bufferSize: bufferSize,
|
|
109
110
|
progress: progress,
|
|
110
111
|
provider: provider)
|
|
112
|
+
|
|
111
113
|
case .symlink:
|
|
112
114
|
provider = { _, _ -> Data in
|
|
113
115
|
let linkDestination = try fileManager.destinationOfSymbolicLink(atPath: fileURL.path)
|
|
@@ -169,6 +169,7 @@ extension Data {
|
|
|
169
169
|
if operation == COMPRESSION_STREAM_DECODE, !skipCRC32 { crc32 = outputData.crc32(checksum: crc32) }
|
|
170
170
|
stream.dst_ptr = destPointer
|
|
171
171
|
stream.dst_size = bufferSize
|
|
172
|
+
|
|
172
173
|
default: throw CompressionError.corruptedData
|
|
173
174
|
}
|
|
174
175
|
} while status == COMPRESSION_STATUS_OK
|
|
@@ -185,6 +185,7 @@ struct Entry: Equatable {
|
|
|
185
185
|
default:
|
|
186
186
|
return isDirectory ? .directory : .file
|
|
187
187
|
}
|
|
188
|
+
|
|
188
189
|
case .msdos:
|
|
189
190
|
isDirectory = isDirectory || ((centralDirectoryStructure.externalFileAttributes >> 4) == 0x01)
|
|
190
191
|
fallthrough // For all other OSes we can only guess based on the directory suffix char
|
|
@@ -44,21 +44,22 @@ extension FileManager {
|
|
|
44
44
|
let permissions = mode_t(externalFileAttributes >> 16) & ~S_IFMT
|
|
45
45
|
let defaultPermissions = entryType == .directory ? defaultDirectoryPermissions : defaultFilePermissions
|
|
46
46
|
return permissions == 0 ? defaultPermissions : UInt16(permissions)
|
|
47
|
+
|
|
47
48
|
default:
|
|
48
49
|
return entryType == .directory ? defaultDirectoryPermissions : defaultFilePermissions
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
class func externalFileAttributesForEntry(of type: Entry.EntryType, permissions: UInt16) -> UInt32 {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
54
|
+
let typeInt =
|
|
55
|
+
switch type {
|
|
56
|
+
case .file:
|
|
57
|
+
UInt16(S_IFREG)
|
|
58
|
+
case .directory:
|
|
59
|
+
UInt16(S_IFDIR)
|
|
60
|
+
case .symlink:
|
|
61
|
+
UInt16(S_IFLNK)
|
|
62
|
+
}
|
|
62
63
|
var externalFileAttributes = UInt32(typeInt | UInt16(permissions))
|
|
63
64
|
externalFileAttributes = (externalFileAttributes << 16)
|
|
64
65
|
return externalFileAttributes
|
|
@@ -36,6 +36,10 @@ class CompositionLayer: CALayer, KeypathSearchable {
|
|
|
36
36
|
"bounds" : NSNull(),
|
|
37
37
|
"anchorPoint" : NSNull(),
|
|
38
38
|
"sublayerTransform" : NSNull(),
|
|
39
|
+
"shadowOpacity" : NSNull(),
|
|
40
|
+
"shadowOffset" : NSNull(),
|
|
41
|
+
"shadowColor" : NSNull(),
|
|
42
|
+
"shadowRadius" : NSNull(),
|
|
39
43
|
]
|
|
40
44
|
|
|
41
45
|
contentsLayer.anchorPoint = .zero
|
|
@@ -55,6 +59,14 @@ class CompositionLayer: CALayer, KeypathSearchable {
|
|
|
55
59
|
contentsLayer.mask = maskLayer
|
|
56
60
|
}
|
|
57
61
|
|
|
62
|
+
// There are two different drop shadow schemas, either using `DropShadowEffect` or `DropShadowStyle`.
|
|
63
|
+
// If both happen to be present, prefer the `DropShadowEffect` (which is the drop shadow schema
|
|
64
|
+
// supported on other platforms).
|
|
65
|
+
let dropShadowEffect = layer.effects.first(where: { $0 is DropShadowEffect }) as? DropShadowModel
|
|
66
|
+
let dropShadowStyle = layer.styles.first(where: { $0 is DropShadowStyle }) as? DropShadowModel
|
|
67
|
+
if let dropShadowModel = dropShadowEffect ?? dropShadowStyle {
|
|
68
|
+
layerEffectNodes.append(DropShadowNode(model: dropShadowModel))
|
|
69
|
+
}
|
|
58
70
|
name = layer.name
|
|
59
71
|
}
|
|
60
72
|
|
|
@@ -72,6 +84,7 @@ class CompositionLayer: CALayer, KeypathSearchable {
|
|
|
72
84
|
keypathName = layer.keypathName
|
|
73
85
|
childKeypaths = [transformNode.transformProperties]
|
|
74
86
|
maskLayer = nil
|
|
87
|
+
layerEffectNodes = layer.layerEffectNodes
|
|
75
88
|
super.init(layer: layer)
|
|
76
89
|
}
|
|
77
90
|
|
|
@@ -96,6 +109,8 @@ class CompositionLayer: CALayer, KeypathSearchable {
|
|
|
96
109
|
let startFrame: CGFloat
|
|
97
110
|
let timeStretch: CGFloat
|
|
98
111
|
|
|
112
|
+
var layerEffectNodes: [LayerEffectNode] = []
|
|
113
|
+
|
|
99
114
|
// MARK: Keypath Searchable
|
|
100
115
|
|
|
101
116
|
let keypathName: String
|
|
@@ -142,6 +157,10 @@ class CompositionLayer: CALayer, KeypathSearchable {
|
|
|
142
157
|
contentsLayer.opacity = transformNode.opacity
|
|
143
158
|
contentsLayer.isHidden = !layerVisible
|
|
144
159
|
layerDelegate?.frameUpdated(frame: frame)
|
|
160
|
+
|
|
161
|
+
for layerEffectNode in layerEffectNodes {
|
|
162
|
+
layerEffectNode.updateWithFrame(layer: self, frame: frame)
|
|
163
|
+
}
|
|
145
164
|
}
|
|
146
165
|
|
|
147
166
|
func displayContentsWithFrame(frame _: CGFloat, forceUpdates _: Bool) {
|
|
@@ -11,19 +11,19 @@ extension MaskMode {
|
|
|
11
11
|
var usableMode: MaskMode {
|
|
12
12
|
switch self {
|
|
13
13
|
case .add:
|
|
14
|
-
|
|
14
|
+
.add
|
|
15
15
|
case .subtract:
|
|
16
|
-
|
|
16
|
+
.subtract
|
|
17
17
|
case .intersect:
|
|
18
|
-
|
|
18
|
+
.intersect
|
|
19
19
|
case .lighten:
|
|
20
|
-
|
|
20
|
+
.add
|
|
21
21
|
case .darken:
|
|
22
|
-
|
|
22
|
+
.darken
|
|
23
23
|
case .difference:
|
|
24
|
-
|
|
24
|
+
.intersect
|
|
25
25
|
case .none:
|
|
26
|
-
|
|
26
|
+
.none
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
}
|
|
@@ -16,6 +16,7 @@ final class PreCompositionLayer: CompositionLayer {
|
|
|
16
16
|
asset: PrecompAsset,
|
|
17
17
|
layerImageProvider: LayerImageProvider,
|
|
18
18
|
layerTextProvider: LayerTextProvider,
|
|
19
|
+
layerFontProvider: LayerFontProvider,
|
|
19
20
|
textProvider: AnimationKeypathTextProvider,
|
|
20
21
|
fontProvider: AnimationFontProvider,
|
|
21
22
|
assetLibrary: AssetLibrary?,
|
|
@@ -38,6 +39,7 @@ final class PreCompositionLayer: CompositionLayer {
|
|
|
38
39
|
assetLibrary: assetLibrary,
|
|
39
40
|
layerImageProvider: layerImageProvider,
|
|
40
41
|
layerTextProvider: layerTextProvider,
|
|
42
|
+
layerFontProvider: layerFontProvider,
|
|
41
43
|
textProvider: textProvider,
|
|
42
44
|
fontProvider: fontProvider,
|
|
43
45
|
frameRate: frameRate,
|
|
@@ -77,6 +79,7 @@ final class PreCompositionLayer: CompositionLayer {
|
|
|
77
79
|
|
|
78
80
|
layerImageProvider.addImageLayers(imageLayers)
|
|
79
81
|
layerTextProvider.addTextLayers(textLayers)
|
|
82
|
+
layerFontProvider.addTextLayers(textLayers)
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
override init(layer: Any) {
|
|
@@ -16,22 +16,22 @@ extension TextJustification {
|
|
|
16
16
|
var textAlignment: NSTextAlignment {
|
|
17
17
|
switch self {
|
|
18
18
|
case .left:
|
|
19
|
-
|
|
19
|
+
.left
|
|
20
20
|
case .right:
|
|
21
|
-
|
|
21
|
+
.right
|
|
22
22
|
case .center:
|
|
23
|
-
|
|
23
|
+
.center
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
var caTextAlignement: CATextLayerAlignmentMode {
|
|
28
28
|
switch self {
|
|
29
29
|
case .left:
|
|
30
|
-
|
|
30
|
+
.left
|
|
31
31
|
case .right:
|
|
32
|
-
|
|
32
|
+
.right
|
|
33
33
|
case .center:
|
|
34
|
-
|
|
34
|
+
.center
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
}
|
|
@@ -121,20 +121,24 @@ final class TextCompositionLayer: CompositionLayer {
|
|
|
121
121
|
// Prior to Lottie 4.3.0 the Main Thread rendering engine always just used `LegacyAnimationTextProvider`
|
|
122
122
|
// and called it with the `keypathName` (only the last path component of the full keypath).
|
|
123
123
|
// Starting in Lottie 4.3.0 we use `AnimationKeypathTextProvider` instead if implemented.
|
|
124
|
-
let textString: String
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
124
|
+
let textString: String =
|
|
125
|
+
if let keypathTextValue = textProvider.text(for: fullAnimationKeypath, sourceText: text.text) {
|
|
126
|
+
keypathTextValue
|
|
127
|
+
} else if let legacyTextProvider = textProvider as? LegacyAnimationTextProvider {
|
|
128
|
+
legacyTextProvider.textFor(keypathName: keypathName, sourceText: text.text)
|
|
129
|
+
} else {
|
|
130
|
+
text.text
|
|
131
|
+
}
|
|
132
132
|
|
|
133
133
|
let strokeColor = rootNode?.textOutputNode.strokeColor ?? text.strokeColorData?.cgColorValue
|
|
134
134
|
let strokeWidth = rootNode?.textOutputNode.strokeWidth ?? CGFloat(text.strokeWidth ?? 0)
|
|
135
135
|
let tracking = (CGFloat(text.fontSize) * (rootNode?.textOutputNode.tracking ?? CGFloat(text.tracking))) / 1000.0
|
|
136
136
|
let matrix = rootNode?.textOutputNode.xform ?? CATransform3DIdentity
|
|
137
137
|
let ctFont = fontProvider.fontFor(family: text.fontFamily, size: CGFloat(text.fontSize))
|
|
138
|
+
let start = rootNode?.textOutputNode.start.flatMap { Int($0) }
|
|
139
|
+
let end = rootNode?.textOutputNode.end.flatMap { Int($0) }
|
|
140
|
+
let selectedRangeOpacity = rootNode?.textOutputNode.selectedRangeOpacity
|
|
141
|
+
let textRangeUnit = rootNode?.textAnimatorProperties.textRangeUnit
|
|
138
142
|
|
|
139
143
|
// Set all of the text layer options
|
|
140
144
|
textLayer.text = textString
|
|
@@ -143,6 +147,12 @@ final class TextCompositionLayer: CompositionLayer {
|
|
|
143
147
|
textLayer.lineHeight = CGFloat(text.lineHeight)
|
|
144
148
|
textLayer.tracking = tracking
|
|
145
149
|
|
|
150
|
+
// Configure the text animators
|
|
151
|
+
textLayer.start = start
|
|
152
|
+
textLayer.end = end
|
|
153
|
+
textLayer.textRangeUnit = textRangeUnit
|
|
154
|
+
textLayer.selectedRangeOpacity = selectedRangeOpacity
|
|
155
|
+
|
|
146
156
|
if let fillColor = rootNode?.textOutputNode.fillColor {
|
|
147
157
|
textLayer.fillColor = fillColor
|
|
148
158
|
} else if let fillColor = text.fillColorData?.cgColorValue {
|
|
@@ -37,6 +37,7 @@ final class MainThreadAnimationLayer: CALayer, RootAnimationLayer {
|
|
|
37
37
|
assetLibrary: animation.assetLibrary,
|
|
38
38
|
layerImageProvider: layerImageProvider,
|
|
39
39
|
layerTextProvider: layerTextProvider,
|
|
40
|
+
layerFontProvider: layerFontProvider,
|
|
40
41
|
textProvider: textProvider,
|
|
41
42
|
fontProvider: fontProvider,
|
|
42
43
|
frameRate: CGFloat(animation.framerate),
|
|
@@ -127,16 +128,16 @@ final class MainThreadAnimationLayer: CALayer, RootAnimationLayer {
|
|
|
127
128
|
|
|
128
129
|
public override func display() {
|
|
129
130
|
guard Thread.isMainThread else { return }
|
|
130
|
-
var newFrame: CGFloat
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
131
|
+
var newFrame: CGFloat =
|
|
132
|
+
if
|
|
133
|
+
let animationKeys = animationKeys(),
|
|
134
|
+
!animationKeys.isEmpty
|
|
135
|
+
{
|
|
136
|
+
presentation()?.currentFrame ?? currentFrame
|
|
137
|
+
} else {
|
|
138
|
+
// We ignore the presentation's frame if there's no animation in the layer.
|
|
139
|
+
currentFrame
|
|
140
|
+
}
|
|
140
141
|
if respectAnimationFrameRate {
|
|
141
142
|
newFrame = floor(newFrame)
|
|
142
143
|
}
|
package/Sources/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.swift
CHANGED
|
@@ -14,6 +14,7 @@ extension [LayerModel] {
|
|
|
14
14
|
assetLibrary: AssetLibrary?,
|
|
15
15
|
layerImageProvider: LayerImageProvider,
|
|
16
16
|
layerTextProvider: LayerTextProvider,
|
|
17
|
+
layerFontProvider: LayerFontProvider,
|
|
17
18
|
textProvider: AnimationKeypathTextProvider,
|
|
18
19
|
fontProvider: AnimationFontProvider,
|
|
19
20
|
frameRate: CGFloat,
|
|
@@ -49,6 +50,7 @@ extension [LayerModel] {
|
|
|
49
50
|
asset: precompAsset,
|
|
50
51
|
layerImageProvider: layerImageProvider,
|
|
51
52
|
layerTextProvider: layerTextProvider,
|
|
53
|
+
layerFontProvider: layerFontProvider,
|
|
52
54
|
textProvider: textProvider,
|
|
53
55
|
fontProvider: fontProvider,
|
|
54
56
|
assetLibrary: assetLibrary,
|
|
@@ -39,7 +39,7 @@ final class CoreTextRenderLayer: CALayer {
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
public var alignment
|
|
42
|
+
public var alignment = NSTextAlignment.left {
|
|
43
43
|
didSet {
|
|
44
44
|
needsContentUpdate = true
|
|
45
45
|
setNeedsLayout()
|
|
@@ -102,6 +102,40 @@ final class CoreTextRenderLayer: CALayer {
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
public var start: Int? {
|
|
106
|
+
didSet {
|
|
107
|
+
needsContentUpdate = true
|
|
108
|
+
setNeedsLayout()
|
|
109
|
+
setNeedsDisplay()
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
public var end: Int? {
|
|
114
|
+
didSet {
|
|
115
|
+
needsContentUpdate = true
|
|
116
|
+
setNeedsLayout()
|
|
117
|
+
setNeedsDisplay()
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// The type of unit to use when computing the `start` / `end` range within the text string
|
|
122
|
+
public var textRangeUnit: TextRangeUnit? {
|
|
123
|
+
didSet {
|
|
124
|
+
needsContentUpdate = true
|
|
125
|
+
setNeedsLayout()
|
|
126
|
+
setNeedsDisplay()
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/// The opacity to apply to the range between `start` and `end`
|
|
131
|
+
public var selectedRangeOpacity: CGFloat? {
|
|
132
|
+
didSet {
|
|
133
|
+
needsContentUpdate = true
|
|
134
|
+
setNeedsLayout()
|
|
135
|
+
setNeedsDisplay()
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
105
139
|
public func sizeToFit() {
|
|
106
140
|
updateTextContent()
|
|
107
141
|
bounds = drawingRect
|
|
@@ -137,19 +171,19 @@ final class CoreTextRenderLayer: CALayer {
|
|
|
137
171
|
|
|
138
172
|
let drawingPath = CGPath(rect: drawingRect, transform: nil)
|
|
139
173
|
|
|
140
|
-
let fillFrame: CTFrame?
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
174
|
+
let fillFrame: CTFrame? =
|
|
175
|
+
if let setter = fillFrameSetter {
|
|
176
|
+
CTFramesetterCreateFrame(setter, CFRangeMake(0, attributedString.length), drawingPath, nil)
|
|
177
|
+
} else {
|
|
178
|
+
nil
|
|
179
|
+
}
|
|
146
180
|
|
|
147
|
-
let strokeFrame: CTFrame?
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
181
|
+
let strokeFrame: CTFrame? =
|
|
182
|
+
if let setter = strokeFrameSetter {
|
|
183
|
+
CTFramesetterCreateFrame(setter, CFRangeMake(0, attributedString.length), drawingPath, nil)
|
|
184
|
+
} else {
|
|
185
|
+
nil
|
|
186
|
+
}
|
|
153
187
|
|
|
154
188
|
// This fixes a vertical padding issue that arises when drawing some fonts.
|
|
155
189
|
// For some reason some fonts, such as Helvetica draw with and ascender that is greater than the one reported by CTFontGetAscender.
|
|
@@ -176,14 +210,14 @@ final class CoreTextRenderLayer: CALayer {
|
|
|
176
210
|
|
|
177
211
|
// MARK: Private
|
|
178
212
|
|
|
179
|
-
private var drawingRect
|
|
180
|
-
private var drawingAnchor
|
|
213
|
+
private var drawingRect = CGRect.zero
|
|
214
|
+
private var drawingAnchor = CGPoint.zero
|
|
181
215
|
private var fillFrameSetter: CTFramesetter?
|
|
182
216
|
private var attributedString: NSAttributedString?
|
|
183
217
|
private var strokeFrameSetter: CTFramesetter?
|
|
184
218
|
private var needsContentUpdate = false
|
|
185
219
|
|
|
186
|
-
|
|
220
|
+
/// Draws Debug colors for the font alignment.
|
|
187
221
|
private func drawDebug(_ ctx: CGContext) {
|
|
188
222
|
if let font {
|
|
189
223
|
let ascent = CTFontGetAscent(font)
|
|
@@ -259,7 +293,58 @@ final class CoreTextRenderLayer: CALayer {
|
|
|
259
293
|
attributes[NSAttributedString.Key.foregroundColor] = fillColor
|
|
260
294
|
}
|
|
261
295
|
|
|
262
|
-
let attrString =
|
|
296
|
+
let attrString = NSMutableAttributedString(string: text, attributes: attributes)
|
|
297
|
+
|
|
298
|
+
// Apply the text animator within between the `start` and `end` indices
|
|
299
|
+
if let selectedRangeOpacity {
|
|
300
|
+
// The start and end of a text animator refer to the portions of the text
|
|
301
|
+
// where that animator is applies. In the schema these can be represented
|
|
302
|
+
// in absolute index value, or as percentages relative to the dynamic string length.
|
|
303
|
+
var startIndex: Int
|
|
304
|
+
var endIndex: Int
|
|
305
|
+
|
|
306
|
+
switch textRangeUnit ?? .percentage {
|
|
307
|
+
case .index:
|
|
308
|
+
startIndex = start ?? 0
|
|
309
|
+
endIndex = end ?? text.count
|
|
310
|
+
|
|
311
|
+
case .percentage:
|
|
312
|
+
let startPercentage = Double(start ?? 0) / 100
|
|
313
|
+
let endPercentage = Double(end ?? 100) / 100
|
|
314
|
+
|
|
315
|
+
startIndex = Int(round(Double(attrString.length) * startPercentage))
|
|
316
|
+
endIndex = Int(round(Double(attrString.length) * endPercentage))
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Carefully cap the indices, since passing invalid indices
|
|
320
|
+
// to `NSAttributedString` will crash the app.
|
|
321
|
+
startIndex = startIndex.clamp(0, attrString.length)
|
|
322
|
+
endIndex = endIndex.clamp(0, attrString.length)
|
|
323
|
+
|
|
324
|
+
// Make sure the end index actually comes after the start index
|
|
325
|
+
if endIndex < startIndex {
|
|
326
|
+
swap(&startIndex, &endIndex)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Apply the `selectedRangeOpacity` to the current `fillColor` if provided
|
|
330
|
+
let textRangeColor: CGColor
|
|
331
|
+
if let fillColor {
|
|
332
|
+
if let (r, g, b) = fillColor.rgb {
|
|
333
|
+
textRangeColor = .rgba(r, g, b, selectedRangeOpacity)
|
|
334
|
+
} else {
|
|
335
|
+
LottieLogger.shared.warn("Could not convert color \(fillColor) to RGB values.")
|
|
336
|
+
textRangeColor = .rgba(0, 0, 0, selectedRangeOpacity)
|
|
337
|
+
}
|
|
338
|
+
} else {
|
|
339
|
+
textRangeColor = .rgba(0, 0, 0, selectedRangeOpacity)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
attrString.addAttribute(
|
|
343
|
+
NSAttributedString.Key.foregroundColor,
|
|
344
|
+
value: textRangeColor,
|
|
345
|
+
range: NSRange(location: startIndex, length: endIndex - startIndex))
|
|
346
|
+
}
|
|
347
|
+
|
|
263
348
|
attributedString = attrString
|
|
264
349
|
|
|
265
350
|
if fillColor != nil {
|
|
@@ -119,17 +119,17 @@ class LayerTransformNode: AnimatorNode {
|
|
|
119
119
|
func rebuildOutputs(frame _: CGFloat) {
|
|
120
120
|
opacity = Float(transformProperties.opacity.value.cgFloatValue) * 0.01
|
|
121
121
|
|
|
122
|
-
let position: CGPoint
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
122
|
+
let position: CGPoint =
|
|
123
|
+
if let point = transformProperties.position?.value.pointValue {
|
|
124
|
+
point
|
|
125
|
+
} else if
|
|
126
|
+
let xPos = transformProperties.positionX?.value.cgFloatValue,
|
|
127
|
+
let yPos = transformProperties.positionY?.value.cgFloatValue
|
|
128
|
+
{
|
|
129
|
+
CGPoint(x: xPos, y: yPos)
|
|
130
|
+
} else {
|
|
131
|
+
.zero
|
|
132
|
+
}
|
|
133
133
|
|
|
134
134
|
localTransform = CATransform3D.makeTransform(
|
|
135
135
|
anchor: transformProperties.anchor.value.pointValue,
|
|
@@ -49,10 +49,12 @@ extension [ShapeItem] {
|
|
|
49
49
|
switch star.starType {
|
|
50
50
|
case .none:
|
|
51
51
|
continue
|
|
52
|
+
|
|
52
53
|
case .polygon:
|
|
53
54
|
let node = PolygonNode(parentNode: nodeTree.rootNode, star: star)
|
|
54
55
|
nodeTree.rootNode = node
|
|
55
56
|
nodeTree.childrenNodes.append(node)
|
|
57
|
+
|
|
56
58
|
case .star:
|
|
57
59
|
let node = StarNode(parentNode: nodeTree.rootNode, star: star)
|
|
58
60
|
nodeTree.rootNode = node
|