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,113 @@
//
// Keyframe.swift
// lottie-swift
//
// Created by Brandon Withrow on 1/7/19.
//
import CoreGraphics
import Foundation
// MARK: - KeyframeData
/// A generic class used to parse and remap keyframe json.
///
/// Keyframe json has a couple of different variations and formats depending on the
/// type of keyframea and also the version of the JSON. By parsing the raw data
/// we can reconfigure it into a constant format.
final class KeyframeData<T> {
// MARK: Lifecycle
init(
startValue: T?,
endValue: T?,
time: AnimationFrameTime?,
hold: Int?,
inTangent: Vector2D?,
outTangent: Vector2D?,
spatialInTangent: Vector3D?,
spatialOutTangent: Vector3D?)
{
self.startValue = startValue
self.endValue = endValue
self.time = time
self.hold = hold
self.inTangent = inTangent
self.outTangent = outTangent
self.spatialInTangent = spatialInTangent
self.spatialOutTangent = spatialOutTangent
}
// MARK: Internal
enum CodingKeys: String, CodingKey {
case startValue = "s"
case endValue = "e"
case time = "t"
case hold = "h"
case inTangent = "i"
case outTangent = "o"
case spatialInTangent = "ti"
case spatialOutTangent = "to"
}
/// The start value of the keyframe
let startValue: T?
/// The End value of the keyframe. Note: Newer versions animation json do not have this field.
let endValue: T?
/// The time in frames of the keyframe.
let time: AnimationFrameTime?
/// A hold keyframe freezes interpolation until the next keyframe that is not a hold.
let hold: Int?
/// The in tangent for the time interpolation curve.
let inTangent: Vector2D?
/// The out tangent for the time interpolation curve.
let outTangent: Vector2D?
/// The spacial in tangent of the vector.
let spatialInTangent: Vector3D?
/// The spacial out tangent of the vector.
let spatialOutTangent: Vector3D?
var isHold: Bool {
if let hold = hold {
return hold > 0
}
return false
}
}
// MARK: Encodable
extension KeyframeData: Encodable where T: Encodable { }
// MARK: Decodable
extension KeyframeData: Decodable where T: Decodable { }
// MARK: DictionaryInitializable
extension KeyframeData: DictionaryInitializable where T: AnyInitializable {
convenience init(dictionary: [String: Any]) throws {
let startValue = try? dictionary[CodingKeys.startValue.rawValue].flatMap(T.init)
let endValue = try? dictionary[CodingKeys.endValue.rawValue].flatMap(T.init)
let time: AnimationFrameTime? = try? dictionary.value(for: CodingKeys.time)
let hold: Int? = try? dictionary.value(for: CodingKeys.hold)
let inTangent: Vector2D? = try? dictionary.value(for: CodingKeys.inTangent)
let outTangent: Vector2D? = try? dictionary.value(for: CodingKeys.outTangent)
let spatialInTangent: Vector3D? = try? dictionary.value(for: CodingKeys.spatialInTangent)
let spatialOutTangent: Vector3D? = try? dictionary.value(for: CodingKeys.spatialOutTangent)
self.init(
startValue: startValue,
endValue: endValue,
time: time,
hold: hold,
inTangent: inTangent,
outTangent: outTangent,
spatialInTangent: spatialInTangent,
spatialOutTangent: spatialOutTangent)
}
}
@@ -0,0 +1,5 @@
#include "KeyframeGroup.hpp"
namespace lottie {
}
@@ -0,0 +1,150 @@
#ifndef KeyframeGroup_hpp
#define KeyframeGroup_hpp
#include "Lottie/Public/Keyframes/Keyframe.hpp"
#include "Lottie/Private/Parsing/JsonParsing.hpp"
#include <vector>
namespace lottie {
/// Used for coding/decoding a group of Keyframes by type.
///
/// Keyframe data is wrapped in a dictionary { "k" : KeyframeData }.
/// The keyframe data can either be an array of keyframes or, if no animation is present, the raw value.
/// This helper object is needed to properly decode the json.
template<typename T>
class KeyframeGroup {
public:
KeyframeGroup(std::vector<Keyframe<T>> &&keyframes_) :
keyframes(std::move(keyframes_)),
isSingle(false) {
}
KeyframeGroup(T const &value_) :
keyframes({ Keyframe<T>(value_, std::nullopt, std::nullopt) }),
isSingle(false) {
}
KeyframeGroup(json11::Json::object const &json) noexcept(false) {
isAnimated = getOptionalInt(json, "a");
expression = getOptionalAny(json, "x");
expressionIndex = getOptionalInt(json, "ix");
_extraL = getOptionalInt(json, "l");
auto containerData = getAny(json, "k");
try {
LottieParsingException::Guard expectedException;
T keyframeData = T(containerData);
keyframes.push_back(Keyframe<T>(keyframeData, std::nullopt, std::nullopt));
isSingle = true;
} catch(...) {
// Decode and array of keyframes.
//
// Body Movin and Lottie deal with keyframes in different ways.
//
// A keyframe object in Body movin defines a span of time with a START
// and an END, from the current keyframe time to the next keyframe time.
//
// A keyframe object in Lottie defines a singular point in time/space.
// This point has an in-tangent and an out-tangent.
//
// To properly decode this we must iterate through keyframes while holding
// reference to the previous keyframe.
if (!containerData.is_array()) {
throw LottieParsingException();
}
std::optional<KeyframeData<T>> previousKeyframeData;
for (const auto &containerItem : containerData.array_items()) {
// Ensure that Time and Value are present.
auto keyframeData = KeyframeData<T>(containerItem);
rawKeyframeData.push_back(keyframeData);
std::optional<T> value;
if (keyframeData.startValue.has_value()) {
value = keyframeData.startValue;
} else if (previousKeyframeData.has_value()) {
value = previousKeyframeData->endValue;
}
if (!value.has_value()) {
throw LottieParsingException();
}
if (!keyframeData.time.has_value()) {
throw LottieParsingException();
}
std::optional<Vector2D> inTangent;
std::optional<Vector3D> spatialInTangent;
if (previousKeyframeData.has_value()) {
inTangent = previousKeyframeData->inTangent;
spatialInTangent = previousKeyframeData->spatialInTangent;
}
keyframes.emplace_back(
value.value(),
keyframeData.time.value(),
keyframeData.isHold(),
inTangent,
keyframeData.outTangent,
spatialInTangent,
keyframeData.spatialOutTangent
);
previousKeyframeData = keyframeData;
}
isSingle = false;
}
}
json11::Json::object toJson() const {
json11::Json::object result;
assert(!keyframes.empty());
if (keyframes.size() == 1 && isSingle) {
result.insert(std::make_pair("k", keyframes[0].value.toJson()));
} else {
json11::Json::array containerData;
for (const auto &keyframe : rawKeyframeData) {
containerData.push_back(keyframe.toJson());
}
result.insert(std::make_pair("k", containerData));
}
if (isAnimated.has_value()) {
result.insert(std::make_pair("a", isAnimated.value()));
}
if (expression.has_value()) {
result.insert(std::make_pair("x", expression.value()));
}
if (expressionIndex.has_value()) {
result.insert(std::make_pair("ix", expressionIndex.value()));
}
if (_extraL.has_value()) {
result.insert(std::make_pair("l", _extraL.value()));
}
return result;
}
public:
std::vector<Keyframe<T>> keyframes;
std::optional<int> isAnimated;
std::optional<json11::Json> expression;
std::optional<int> expressionIndex;
std::vector<KeyframeData<T>> rawKeyframeData;
bool isSingle = false;
std::optional<int> _extraL;
};
}
#endif /* KeyframeGroup_hpp */
@@ -0,0 +1,197 @@
//
// KeyframeGroup.swift
// lottie-swift
//
// Created by Brandon Withrow on 1/14/19.
//
import Foundation
// MARK: - KeyframeGroup
/// Used for coding/decoding a group of Keyframes by type.
///
/// Keyframe data is wrapped in a dictionary { "k" : KeyframeData }.
/// The keyframe data can either be an array of keyframes or, if no animation is present, the raw value.
/// This helper object is needed to properly decode the json.
final class KeyframeGroup<T> {
// MARK: Lifecycle
init(keyframes: ContiguousArray<Keyframe<T>>) {
self.keyframes = keyframes
}
init(_ value: T) {
keyframes = [Keyframe(value)]
}
// MARK: Internal
enum KeyframeWrapperKey: String, CodingKey {
case keyframeData = "k"
}
let keyframes: ContiguousArray<Keyframe<T>>
}
// MARK: Decodable
extension KeyframeGroup: Decodable where T: Decodable {
convenience init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: KeyframeWrapperKey.self)
if let keyframeData: T = try? container.decode(T.self, forKey: .keyframeData) {
/// Try to decode raw value; No keyframe data.
self.init(keyframes: [Keyframe<T>(keyframeData)])
} else {
// Decode and array of keyframes.
//
// Body Movin and Lottie deal with keyframes in different ways.
//
// A keyframe object in Body movin defines a span of time with a START
// and an END, from the current keyframe time to the next keyframe time.
//
// A keyframe object in Lottie defines a singular point in time/space.
// This point has an in-tangent and an out-tangent.
//
// To properly decode this we must iterate through keyframes while holding
// reference to the previous keyframe.
var keyframesContainer = try container.nestedUnkeyedContainer(forKey: .keyframeData)
var keyframes = ContiguousArray<Keyframe<T>>()
var previousKeyframeData: KeyframeData<T>?
while !keyframesContainer.isAtEnd {
// Ensure that Time and Value are present.
let keyframeData = try keyframesContainer.decode(KeyframeData<T>.self)
guard
let value: T = keyframeData.startValue ?? previousKeyframeData?.endValue,
let time = keyframeData.time else
{
/// Missing keyframe data. JSON must be corrupt.
throw DecodingError.dataCorruptedError(
forKey: KeyframeWrapperKey.keyframeData,
in: container,
debugDescription: "Missing keyframe data.")
}
keyframes.append(Keyframe<T>(
value: value,
time: AnimationFrameTime(time),
isHold: keyframeData.isHold,
inTangent: previousKeyframeData?.inTangent,
outTangent: keyframeData.outTangent,
spatialInTangent: previousKeyframeData?.spatialInTangent,
spatialOutTangent: keyframeData.spatialOutTangent))
previousKeyframeData = keyframeData
}
self.init(keyframes: keyframes)
}
}
}
// MARK: Encodable
extension KeyframeGroup: Encodable where T: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: KeyframeWrapperKey.self)
if keyframes.count == 1 {
let keyframe = keyframes[0]
try container.encode(keyframe.value, forKey: .keyframeData)
} else {
var keyframeContainer = container.nestedUnkeyedContainer(forKey: .keyframeData)
for i in 1..<keyframes.endIndex {
let keyframe = keyframes[i - 1]
let nextKeyframe = keyframes[i]
let keyframeData = KeyframeData<T>(
startValue: keyframe.value,
endValue: nextKeyframe.value,
time: keyframe.time,
hold: keyframe.isHold ? 1 : nil,
inTangent: nextKeyframe.inTangent,
outTangent: keyframe.outTangent,
spatialInTangent: nil,
spatialOutTangent: nil)
try keyframeContainer.encode(keyframeData)
}
}
}
}
// MARK: DictionaryInitializable
extension KeyframeGroup: DictionaryInitializable where T: AnyInitializable {
convenience init(dictionary: [String: Any]) throws {
var keyframes = ContiguousArray<Keyframe<T>>()
if
let rawValue = dictionary[KeyframeWrapperKey.keyframeData.rawValue],
let value = try? T(value: rawValue)
{
keyframes = [Keyframe<T>(value)]
} else {
var frameDictionaries: [[String: Any]]
if let singleFrameDictionary = dictionary[KeyframeWrapperKey.keyframeData.rawValue] as? [String: Any] {
frameDictionaries = [singleFrameDictionary]
} else {
frameDictionaries = try dictionary.value(for: KeyframeWrapperKey.keyframeData)
}
var previousKeyframeData: KeyframeData<T>?
for frameDictionary in frameDictionaries {
let data = try KeyframeData<T>(dictionary: frameDictionary)
guard
let value: T = data.startValue ?? previousKeyframeData?.endValue,
let time = data.time else
{
throw InitializableError.invalidInput
}
keyframes.append(Keyframe<T>(
value: value,
time: time,
isHold: data.isHold,
inTangent: previousKeyframeData?.inTangent,
outTangent: data.outTangent,
spatialInTangent: previousKeyframeData?.spatialInTangent,
spatialOutTangent: data.spatialOutTangent))
previousKeyframeData = data
}
}
self.init(keyframes: keyframes)
}
}
// MARK: Equatable
extension KeyframeGroup: Equatable where T: Equatable {
static func == (_ lhs: KeyframeGroup<T>, _ rhs: KeyframeGroup<T>) -> Bool {
lhs.keyframes == rhs.keyframes
}
}
// MARK: Hashable
extension KeyframeGroup: Hashable where T: Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(keyframes)
}
}
extension Keyframe {
/// Creates a copy of this `Keyframe` with the same timing data, but a different value
func withValue<Value>(_ newValue: Value) -> Keyframe<Value> {
Keyframe<Value>(
value: newValue,
time: time,
isHold: isHold,
inTangent: inTangent,
outTangent: outTangent,
spatialInTangent: spatialInTangent,
spatialOutTangent: spatialOutTangent)
}
}