Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,161 @@
//
// LayerContainer.swift
// lottie-swift
//
// Created by Brandon Withrow on 1/22/19.
//
import Foundation
import QuartzCore
// MARK: - CompositionLayer
/// The base class for a child layer of CompositionContainer
class CompositionLayer: CALayer, KeypathSearchable {
// MARK: Lifecycle
init(layer: LayerModel, size: CGSize) {
transformNode = LayerTransformNode(transform: layer.transform)
if let masks = layer.masks {
maskLayer = MaskContainerLayer(masks: masks)
} else {
maskLayer = nil
}
matteType = layer.matte
inFrame = layer.inFrame.cgFloat
outFrame = layer.outFrame.cgFloat
timeStretch = layer.timeStretch.cgFloat
startFrame = layer.startTime.cgFloat
keypathName = layer.name
childKeypaths = [transformNode.transformProperties]
super.init()
anchorPoint = .zero
actions = [
"opacity" : NSNull(),
"transform" : NSNull(),
"bounds" : NSNull(),
"anchorPoint" : NSNull(),
"sublayerTransform" : NSNull(),
]
contentsLayer.anchorPoint = .zero
contentsLayer.bounds = CGRect(origin: .zero, size: size)
contentsLayer.actions = [
"opacity" : NSNull(),
"transform" : NSNull(),
"bounds" : NSNull(),
"anchorPoint" : NSNull(),
"sublayerTransform" : NSNull(),
"hidden" : NSNull(),
]
compositingFilter = layer.blendMode.filterName
addSublayer(contentsLayer)
if let maskLayer = maskLayer {
contentsLayer.mask = maskLayer
}
name = layer.name
}
override init(layer: Any) {
/// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init
guard let layer = layer as? CompositionLayer else {
fatalError("Wrong Layer Class")
}
transformNode = layer.transformNode
matteType = layer.matteType
inFrame = layer.inFrame
outFrame = layer.outFrame
timeStretch = layer.timeStretch
startFrame = layer.startFrame
keypathName = layer.keypathName
childKeypaths = [transformNode.transformProperties]
maskLayer = nil
super.init(layer: layer)
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Internal
weak var layerDelegate: CompositionLayerDelegate?
let transformNode: LayerTransformNode
let contentsLayer = CALayer()
let maskLayer: MaskContainerLayer?
let matteType: MatteType?
let inFrame: CGFloat
let outFrame: CGFloat
let startFrame: CGFloat
let timeStretch: CGFloat
// MARK: Keypath Searchable
let keypathName: String
final var childKeypaths: [KeypathSearchable]
var renderScale: CGFloat = 1 {
didSet {
updateRenderScale()
}
}
var matteLayer: CompositionLayer? {
didSet {
if let matte = matteLayer {
if let type = matteType, type == .invert {
mask = InvertedMatteLayer(inputMatte: matte)
} else {
mask = matte
}
} else {
mask = nil
}
}
}
var keypathProperties: [String: AnyNodeProperty] {
[:]
}
var keypathLayer: CALayer? {
contentsLayer
}
final func displayWithFrame(frame: CGFloat, forceUpdates: Bool) {
transformNode.updateTree(frame, forceUpdates: forceUpdates)
let layerVisible = frame.isInRangeOrEqual(inFrame, outFrame)
/// Only update contents if current time is within the layers time bounds.
if layerVisible {
displayContentsWithFrame(frame: frame, forceUpdates: forceUpdates)
maskLayer?.updateWithFrame(frame: frame, forceUpdates: forceUpdates)
}
contentsLayer.transform = transformNode.globalTransform
contentsLayer.opacity = transformNode.opacity
contentsLayer.isHidden = !layerVisible
layerDelegate?.frameUpdated(frame: frame)
}
func displayContentsWithFrame(frame _: CGFloat, forceUpdates _: Bool) {
/// To be overridden by subclass
}
func updateRenderScale() {
contentsScale = renderScale
}
}
// MARK: - CompositionLayerDelegate
protocol CompositionLayerDelegate: AnyObject {
func frameUpdated(frame: CGFloat)
}
@@ -0,0 +1,50 @@
//
// ImageCompositionLayer.swift
// lottie-swift
//
// Created by Brandon Withrow on 1/25/19.
//
import CoreGraphics
import Foundation
import QuartzCore
final class ImageCompositionLayer: CompositionLayer {
// MARK: Lifecycle
init(imageLayer: ImageLayerModel, size: CGSize) {
imageReferenceID = imageLayer.referenceID
super.init(layer: imageLayer, size: size)
contentsLayer.masksToBounds = true
contentsLayer.contentsGravity = CALayerContentsGravity.resize
}
override init(layer: Any) {
/// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init
guard let layer = layer as? ImageCompositionLayer else {
fatalError("init(layer:) Wrong Layer Class")
}
imageReferenceID = layer.imageReferenceID
image = nil
super.init(layer: layer)
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Internal
let imageReferenceID: String
var image: CGImage? = nil {
didSet {
if let image = image {
contentsLayer.contents = image
} else {
contentsLayer.contents = nil
}
}
}
}
@@ -0,0 +1,191 @@
//
// MaskContainerLayer.swift
// lottie-swift
//
// Created by Brandon Withrow on 1/25/19.
//
import Foundation
import QuartzCore
extension MaskMode {
var usableMode: MaskMode {
switch self {
case .add:
return .add
case .subtract:
return .subtract
case .intersect:
return .intersect
case .lighten:
return .add
case .darken:
return .darken
case .difference:
return .intersect
case .none:
return .none
}
}
}
// MARK: - MaskContainerLayer
final class MaskContainerLayer: CALayer {
// MARK: Lifecycle
init(masks: [Mask]) {
super.init()
anchorPoint = .zero
var containerLayer = CALayer()
var firstObject = true
for mask in masks {
let maskLayer = MaskLayer(mask: mask)
maskLayers.append(maskLayer)
if mask.mode.usableMode == .none {
continue
} else if mask.mode.usableMode == .add || firstObject {
firstObject = false
containerLayer.addSublayer(maskLayer)
} else {
containerLayer.mask = maskLayer
let newContainer = CALayer()
newContainer.addSublayer(containerLayer)
containerLayer = newContainer
}
}
addSublayer(containerLayer)
}
override init(layer: Any) {
/// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init
guard let layer = layer as? MaskContainerLayer else {
fatalError("init(layer:) Wrong Layer Class")
}
super.init(layer: layer)
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Internal
func updateWithFrame(frame: CGFloat, forceUpdates: Bool) {
maskLayers.forEach({ $0.updateWithFrame(frame: frame, forceUpdates: forceUpdates) })
}
// MARK: Fileprivate
fileprivate var maskLayers: [MaskLayer] = []
}
extension CGRect {
static var veryLargeRect: CGRect {
CGRect(
x: -100_000_000,
y: -100_000_000,
width: 200_000_000,
height: 200_000_000)
}
}
// MARK: - MaskLayer
private class MaskLayer: CALayer {
// MARK: Lifecycle
init(mask: Mask) {
properties = MaskNodeProperties(mask: mask)
super.init()
addSublayer(maskLayer)
anchorPoint = .zero
maskLayer.fillColor = mask.mode == .add
? CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [1, 0, 0, 1])
: CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0, 1, 0, 1])
maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
actions = [
"opacity" : NSNull(),
]
}
override init(layer: Any) {
properties = nil
super.init(layer: layer)
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Internal
let properties: MaskNodeProperties?
let maskLayer = CAShapeLayer()
func updateWithFrame(frame: CGFloat, forceUpdates: Bool) {
guard let properties = properties else { return }
if properties.opacity.needsUpdate(frame: frame) || forceUpdates {
properties.opacity.update(frame: frame)
opacity = Float(properties.opacity.value.cgFloatValue)
}
if properties.shape.needsUpdate(frame: frame) || forceUpdates {
properties.shape.update(frame: frame)
properties.expansion.update(frame: frame)
let shapePath = properties.shape.value.cgPath()
var path = shapePath
if
properties.mode.usableMode == .subtract && !properties.inverted ||
(properties.mode.usableMode == .add && properties.inverted)
{
/// Add a bounds rect to invert the mask
let newPath = CGMutablePath()
newPath.addRect(CGRect.veryLargeRect)
newPath.addPath(shapePath)
path = newPath
}
maskLayer.path = path
}
}
}
// MARK: - MaskNodeProperties
private class MaskNodeProperties: NodePropertyMap {
// MARK: Lifecycle
init(mask: Mask) {
mode = mask.mode
inverted = mask.inverted
opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.opacity.keyframes))
shape = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.shape.keyframes))
expansion = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.expansion.keyframes))
propertyMap = [
"Opacity" : opacity,
"Shape" : shape,
"Expansion" : expansion,
]
properties = Array(propertyMap.values)
}
// MARK: Internal
var propertyMap: [String: AnyNodeProperty]
var properties: [AnyNodeProperty]
let mode: MaskMode
let inverted: Bool
let opacity: NodeProperty<Vector1D>
let shape: NodeProperty<BezierPath>
let expansion: NodeProperty<Vector1D>
}
@@ -0,0 +1,28 @@
//
// NullCompositionLayer.swift
// lottie-swift
//
// Created by Brandon Withrow on 1/25/19.
//
import Foundation
final class NullCompositionLayer: CompositionLayer {
init(layer: LayerModel) {
super.init(layer: layer, size: .zero)
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init(layer: Any) {
/// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init
guard let layer = layer as? NullCompositionLayer else {
fatalError("init(layer:) Wrong Layer Class")
}
super.init(layer: layer)
}
}
@@ -0,0 +1,121 @@
//
// PreCompositionLayer.swift
// lottie-swift
//
// Created by Brandon Withrow on 1/25/19.
//
import Foundation
import QuartzCore
final class PreCompositionLayer: CompositionLayer {
// MARK: Lifecycle
init(
precomp: PreCompLayerModel,
asset: PrecompAsset,
layerImageProvider: LayerImageProvider,
textProvider: AnimationTextProvider,
fontProvider: AnimationFontProvider,
assetLibrary: AssetLibrary?,
frameRate: CGFloat)
{
animationLayers = []
if let keyframes = precomp.timeRemapping?.keyframes {
remappingNode = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframes))
} else {
remappingNode = nil
}
self.frameRate = frameRate
super.init(layer: precomp, size: CGSize(width: precomp.width, height: precomp.height))
bounds = CGRect(origin: .zero, size: CGSize(width: precomp.width, height: precomp.height))
contentsLayer.masksToBounds = true
contentsLayer.bounds = bounds
let layers = asset.layers.initializeCompositionLayers(
assetLibrary: assetLibrary,
layerImageProvider: layerImageProvider,
textProvider: textProvider,
fontProvider: fontProvider,
frameRate: frameRate)
var imageLayers = [ImageCompositionLayer]()
var mattedLayer: CompositionLayer? = nil
for layer in layers.reversed() {
layer.bounds = bounds
animationLayers.append(layer)
if let imageLayer = layer as? ImageCompositionLayer {
imageLayers.append(imageLayer)
}
if let matte = mattedLayer {
/// The previous layer requires this layer to be its matte
matte.matteLayer = layer
mattedLayer = nil
continue
}
if
let matte = layer.matteType,
matte == .add || matte == .invert
{
/// We have a layer that requires a matte.
mattedLayer = layer
}
contentsLayer.addSublayer(layer)
}
childKeypaths.append(contentsOf: layers)
layerImageProvider.addImageLayers(imageLayers)
}
override init(layer: Any) {
/// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init
guard let layer = layer as? PreCompositionLayer else {
fatalError("init(layer:) Wrong Layer Class")
}
frameRate = layer.frameRate
remappingNode = nil
animationLayers = []
super.init(layer: layer)
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Internal
let frameRate: CGFloat
let remappingNode: NodeProperty<Vector1D>?
override var keypathProperties: [String: AnyNodeProperty] {
guard let remappingNode = remappingNode else {
return super.keypathProperties
}
return ["Time Remap" : remappingNode]
}
override func displayContentsWithFrame(frame: CGFloat, forceUpdates: Bool) {
let localFrame: CGFloat
if let remappingNode = remappingNode {
remappingNode.update(frame: frame)
localFrame = remappingNode.value.cgFloatValue * frameRate
} else {
localFrame = (frame - startFrame) / timeStretch
}
animationLayers.forEach( { $0.displayWithFrame(frame: localFrame, forceUpdates: forceUpdates) })
}
override func updateRenderScale() {
super.updateRenderScale()
animationLayers.forEach( { $0.renderScale = renderScale } )
}
// MARK: Fileprivate
fileprivate var animationLayers: [CompositionLayer]
}
@@ -0,0 +1,58 @@
//
// ShapeLayerContainer.swift
// lottie-swift
//
// Created by Brandon Withrow on 1/22/19.
//
import CoreGraphics
import Foundation
/// A CompositionLayer responsible for initializing and rendering shapes
final class ShapeCompositionLayer: CompositionLayer {
// MARK: Lifecycle
init(shapeLayer: ShapeLayerModel) {
let results = shapeLayer.items.initializeNodeTree()
let renderContainer = ShapeContainerLayer()
self.renderContainer = renderContainer
rootNode = results.rootNode
super.init(layer: shapeLayer, size: .zero)
contentsLayer.addSublayer(renderContainer)
for container in results.renderContainers {
renderContainer.insertRenderLayer(container)
}
rootNode?.updateTree(0, forceUpdates: true)
childKeypaths.append(contentsOf: results.childrenNodes)
}
override init(layer: Any) {
guard let layer = layer as? ShapeCompositionLayer else {
fatalError("init(layer:) wrong class.")
}
rootNode = nil
renderContainer = nil
super.init(layer: layer)
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Internal
let rootNode: AnimatorNode?
let renderContainer: ShapeContainerLayer?
override func displayContentsWithFrame(frame: CGFloat, forceUpdates: Bool) {
rootNode?.updateTree(frame, forceUpdates: forceUpdates)
renderContainer?.markRenderUpdates(forFrame: frame)
}
override func updateRenderScale() {
super.updateRenderScale()
renderContainer?.renderScale = renderScale
}
}
@@ -0,0 +1,57 @@
//
// SolidCompositionLayer.swift
// lottie-swift
//
// Created by Brandon Withrow on 1/25/19.
//
import Foundation
import QuartzCore
final class SolidCompositionLayer: CompositionLayer {
// MARK: Lifecycle
init(solid: SolidLayerModel) {
let components = solid.colorHex.hexColorComponents()
colorProperty =
NodeProperty(provider: SingleValueProvider(Color(
r: Double(components.red),
g: Double(components.green),
b: Double(components.blue),
a: 1)))
super.init(layer: solid, size: .zero)
solidShape.path = CGPath(rect: CGRect(x: 0, y: 0, width: solid.width, height: solid.height), transform: nil)
contentsLayer.addSublayer(solidShape)
}
override init(layer: Any) {
/// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init
guard let layer = layer as? SolidCompositionLayer else {
fatalError("init(layer:) Wrong Layer Class")
}
colorProperty = layer.colorProperty
super.init(layer: layer)
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Internal
let colorProperty: NodeProperty<Color>?
let solidShape = CAShapeLayer()
override var keypathProperties: [String: AnyNodeProperty] {
guard let colorProperty = colorProperty else { return super.keypathProperties }
return [PropertyName.color.rawValue : colorProperty]
}
override func displayContentsWithFrame(frame: CGFloat, forceUpdates _: Bool) {
guard let colorProperty = colorProperty else { return }
colorProperty.update(frame: frame)
solidShape.fillColor = colorProperty.value.cgColorValue
}
}
@@ -0,0 +1,149 @@
//
// TextCompositionLayer.swift
// lottie-swift
//
// Created by Brandon Withrow on 1/25/19.
//
import CoreGraphics
import CoreText
import Foundation
import QuartzCore
/// Needed for NSMutableParagraphStyle...
#if os(OSX)
import AppKit
#else
import UIKit
#endif
extension TextJustification {
var textAlignment: NSTextAlignment {
switch self {
case .left:
return .left
case .right:
return .right
case .center:
return .center
}
}
var caTextAlignement: CATextLayerAlignmentMode {
switch self {
case .left:
return .left
case .right:
return .right
case .center:
return .center
}
}
}
// MARK: - TextCompositionLayer
final class TextCompositionLayer: CompositionLayer {
// MARK: Lifecycle
init(textLayer: TextLayerModel, textProvider: AnimationTextProvider, fontProvider: AnimationFontProvider) {
var rootNode: TextAnimatorNode?
for animator in textLayer.animators {
rootNode = TextAnimatorNode(parentNode: rootNode, textAnimator: animator)
}
self.rootNode = rootNode
textDocument = KeyframeInterpolator(keyframes: textLayer.text.keyframes)
self.textProvider = textProvider
self.fontProvider = fontProvider
super.init(layer: textLayer, size: .zero)
contentsLayer.addSublayer(self.textLayer)
self.textLayer.masksToBounds = false
self.textLayer.isGeometryFlipped = true
if let rootNode = rootNode {
childKeypaths.append(rootNode)
}
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init(layer: Any) {
/// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init
guard let layer = layer as? TextCompositionLayer else {
fatalError("init(layer:) Wrong Layer Class")
}
rootNode = nil
textDocument = nil
textProvider = DefaultTextProvider()
fontProvider = DefaultFontProvider()
super.init(layer: layer)
}
// MARK: Internal
let rootNode: TextAnimatorNode?
let textDocument: KeyframeInterpolator<TextDocument>?
let textLayer = CoreTextRenderLayer()
var textProvider: AnimationTextProvider
var fontProvider: AnimationFontProvider
override func displayContentsWithFrame(frame: CGFloat, forceUpdates: Bool) {
guard let textDocument = textDocument else { return }
textLayer.contentsScale = renderScale
let documentUpdate = textDocument.hasUpdate(frame: frame)
let animatorUpdate = rootNode?.updateContents(frame, forceLocalUpdate: forceUpdates) ?? false
guard documentUpdate == true || animatorUpdate == true else { return }
rootNode?.rebuildOutputs(frame: frame)
// Get Text Attributes
let text = textDocument.value(frame: frame) as! TextDocument
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
textLayer.text = textString
textLayer.font = ctFont
textLayer.alignment = text.justification.textAlignment
textLayer.lineHeight = CGFloat(text.lineHeight)
textLayer.tracking = tracking
if let fillColor = rootNode?.textOutputNode.fillColor {
textLayer.fillColor = fillColor
} else if let fillColor = text.fillColorData?.cgColorValue {
textLayer.fillColor = fillColor
} else {
textLayer.fillColor = nil
}
textLayer.preferredSize = text.textFrameSize?.sizeValue
textLayer.strokeOnTop = text.strokeOverFill ?? false
textLayer.strokeWidth = strokeWidth
textLayer.strokeColor = strokeColor
textLayer.sizeToFit()
textLayer.opacity = Float(rootNode?.textOutputNode.opacity ?? 1)
textLayer.transform = CATransform3DIdentity
textLayer.position = text.textFramePosition?.pointValue ?? CGPoint.zero
textLayer.transform = matrix
}
override func updateRenderScale() {
super.updateRenderScale()
textLayer.contentsScale = renderScale
}
}