From af82fb814ead9bb0e7337e4090809007565c45d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 31 Aug 2026 17:49:14 +0000 Subject: [PATCH 01/39] Add FlatBuffers schema and dependency-free C# FlatBuffers writer/reader Co-authored-by: jonwis <18537118+jonwis@users.noreply.github.com> --- .../FlatBufferFormatException.cs | 33 ++ source/CompDataFlatbuffer/FlatBufferTable.cs | 325 +++++++++++ source/CompDataFlatbuffer/FlatBufferWriter.cs | 474 ++++++++++++++++ source/CompDataFlatbuffer/Schema.cs | 385 +++++++++++++ source/CompDataFlatbuffer/SchemaEnums.cs | 139 +++++ source/LottieFlatbuffer/lottie_comp.fbs | 529 ++++++++++++++++++ 6 files changed, 1885 insertions(+) create mode 100644 source/CompDataFlatbuffer/FlatBufferFormatException.cs create mode 100644 source/CompDataFlatbuffer/FlatBufferTable.cs create mode 100644 source/CompDataFlatbuffer/FlatBufferWriter.cs create mode 100644 source/CompDataFlatbuffer/Schema.cs create mode 100644 source/CompDataFlatbuffer/SchemaEnums.cs create mode 100644 source/LottieFlatbuffer/lottie_comp.fbs diff --git a/source/CompDataFlatbuffer/FlatBufferFormatException.cs b/source/CompDataFlatbuffer/FlatBufferFormatException.cs new file mode 100644 index 00000000..62756e2c --- /dev/null +++ b/source/CompDataFlatbuffer/FlatBufferFormatException.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; + +namespace CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer +{ + /// + /// Thrown when a buffer is not a valid serialized composition. + /// +#if PUBLIC_CompDataFlatbuffer + public +#endif + sealed class FlatBufferFormatException : Exception + { + public FlatBufferFormatException() + { + } + + public FlatBufferFormatException(string message) + : base(message) + { + } + + public FlatBufferFormatException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/source/CompDataFlatbuffer/FlatBufferTable.cs b/source/CompDataFlatbuffer/FlatBufferTable.cs new file mode 100644 index 00000000..8338ac78 --- /dev/null +++ b/source/CompDataFlatbuffer/FlatBufferTable.cs @@ -0,0 +1,325 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Text; + +namespace CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer +{ + /// + /// A minimal reader for the FlatBuffers binary wire format. All accesses are + /// bounds checked; a malformed buffer causes a + /// rather than undefined behavior. + /// + readonly struct FlatBufferTable + { + readonly byte[] _buffer; + readonly int _position; + + internal FlatBufferTable(byte[] buffer, int position) + { + _buffer = buffer; + _position = position; + } + + internal bool IsNull => _buffer is null; + + /// + /// Gets the root table of a buffer, checking its file identifier. + /// + /// The buffer. + /// The expected 4 character file identifier. + /// The root table. + internal static FlatBufferTable GetRoot(byte[] buffer, string fileIdentifier) + { + if (buffer is null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (buffer.Length < 8) + { + throw new FlatBufferFormatException("Buffer is too small."); + } + + for (var i = 0; i < 4; i++) + { + if (buffer[4 + i] != (byte)fileIdentifier[i]) + { + throw new FlatBufferFormatException("Unrecognized file identifier."); + } + } + + var rootOffset = ReadInt32(buffer, 0); + return new FlatBufferTable(buffer, CheckedAdd(buffer, 0, rootOffset)); + } + + /// + /// Returns the absolute position of the data of the field with the given + /// id, or 0 if the field is not present. + /// + /// The id of the field. + /// The position of the field's data, or 0. + internal int FieldPosition(int field) + { + var vtable = _position - ReadInt32(_buffer, _position); + if (vtable < 0 || vtable + 4 > _buffer.Length) + { + throw new FlatBufferFormatException("Invalid vtable offset."); + } + + var vtableSize = ReadUInt16(_buffer, vtable); + var fieldIndex = 4 + (field * 2); + if (fieldIndex + 2 > vtableSize) + { + return 0; + } + + var fieldOffset = ReadUInt16(_buffer, vtable + fieldIndex); + if (fieldOffset == 0) + { + return 0; + } + + var result = _position + fieldOffset; + if (result < 0 || result >= _buffer.Length) + { + throw new FlatBufferFormatException("Field is outside the buffer."); + } + + return result; + } + + internal byte GetUInt8(int field, byte defaultValue) + { + var position = FieldPosition(field); + return position == 0 ? defaultValue : _buffer[position]; + } + + internal byte? GetOptionalUInt8(int field) + { + var position = FieldPosition(field); + return position == 0 ? (byte?)null : _buffer[position]; + } + + internal bool GetBool(int field, bool defaultValue) + => GetUInt8(field, (byte)(defaultValue ? 1 : 0)) != 0; + + internal bool? GetOptionalBool(int field) + { + var value = GetOptionalUInt8(field); + return value.HasValue ? value.Value != 0 : (bool?)null; + } + + internal ushort GetUInt16(int field, ushort defaultValue) + { + var position = FieldPosition(field); + return position == 0 ? defaultValue : ReadUInt16(_buffer, position); + } + + internal int GetInt32(int field, int defaultValue) + { + var position = FieldPosition(field); + return position == 0 ? defaultValue : ReadInt32(_buffer, position); + } + + internal int? GetOptionalInt32(int field) + { + var position = FieldPosition(field); + return position == 0 ? (int?)null : ReadInt32(_buffer, position); + } + + internal uint GetUInt32(int field, uint defaultValue) + { + var position = FieldPosition(field); + return position == 0 ? defaultValue : unchecked((uint)ReadInt32(_buffer, position)); + } + + internal long GetInt64(int field, long defaultValue) + { + var position = FieldPosition(field); + if (position == 0) + { + return defaultValue; + } + + var low = unchecked((uint)ReadInt32(_buffer, position)); + var high = ReadInt32(_buffer, position + 4); + return unchecked((long)(((ulong)(uint)high << 32) | low)); + } + + internal float GetFloat(int field, float defaultValue) + { + var position = FieldPosition(field); + return position == 0 ? defaultValue : BitConverter.Int32BitsToSingle(ReadInt32(_buffer, position)); + } + + internal float? GetOptionalFloat(int field) + { + var position = FieldPosition(field); + return position == 0 ? (float?)null : BitConverter.Int32BitsToSingle(ReadInt32(_buffer, position)); + } + + /// + /// Returns the position of an inline struct field, or 0 if not present. + /// Use the ReadStructFloat and ReadStructUInt8 methods to read its fields. + /// + /// The id of the field. + /// The position of the struct, or 0. + internal int GetStructPosition(int field) => FieldPosition(field); + + internal float ReadStructFloat(int structPosition, int offset) + => BitConverter.Int32BitsToSingle(ReadInt32(_buffer, structPosition + offset)); + + internal byte ReadStructUInt8(int structPosition, int offset) + { + if (structPosition + offset >= _buffer.Length) + { + throw new FlatBufferFormatException("Struct is outside the buffer."); + } + + return _buffer[structPosition + offset]; + } + + internal FlatBufferTable GetTable(int field) + { + var position = FieldPosition(field); + return position == 0 + ? default + : new FlatBufferTable(_buffer, CheckedAdd(_buffer, position, ReadInt32(_buffer, position))); + } + + internal string? GetString(int field) + { + var position = FieldPosition(field); + if (position == 0) + { + return null; + } + + var stringPosition = CheckedAdd(_buffer, position, ReadInt32(_buffer, position)); + var length = ReadInt32(_buffer, stringPosition); + if (length < 0 || stringPosition + 4 + length > _buffer.Length) + { + throw new FlatBufferFormatException("String is outside the buffer."); + } + + return Encoding.UTF8.GetString(_buffer, stringPosition + 4, length); + } + + /// + /// Returns the number of elements in a vector field. + /// + /// The id of the field. + /// The number of elements, or 0 if the field is not present. + internal int VectorLength(int field) + { + var position = FieldPosition(field); + if (position == 0) + { + return 0; + } + + var vector = CheckedAdd(_buffer, position, ReadInt32(_buffer, position)); + var length = ReadInt32(_buffer, vector); + if (length < 0) + { + throw new FlatBufferFormatException("Invalid vector length."); + } + + return length; + } + + internal byte GetVectorUInt8(int field, int index) + { + var position = VectorElementPosition(field, index, sizeof(byte)); + return _buffer[position]; + } + + internal float GetVectorFloat(int field, int index) + => BitConverter.Int32BitsToSingle(ReadInt32(_buffer, VectorElementPosition(field, index, sizeof(float)))); + + internal uint GetVectorUInt32(int field, int index) + => unchecked((uint)ReadInt32(_buffer, VectorElementPosition(field, index, sizeof(uint)))); + + internal FlatBufferTable GetVectorTable(int field, int index) + { + var position = VectorElementPosition(field, index, sizeof(int)); + return new FlatBufferTable(_buffer, CheckedAdd(_buffer, position, ReadInt32(_buffer, position))); + } + + internal string GetVectorString(int field, int index) + { + var position = VectorElementPosition(field, index, sizeof(int)); + var stringPosition = CheckedAdd(_buffer, position, ReadInt32(_buffer, position)); + var length = ReadInt32(_buffer, stringPosition); + if (length < 0 || stringPosition + 4 + length > _buffer.Length) + { + throw new FlatBufferFormatException("String is outside the buffer."); + } + + return Encoding.UTF8.GetString(_buffer, stringPosition + 4, length); + } + + static int ReadInt32(byte[] buffer, int position) + { + if (position < 0 || position + 4 > buffer.Length) + { + throw new FlatBufferFormatException("Read is outside the buffer."); + } + + return buffer[position] | + (buffer[position + 1] << 8) | + (buffer[position + 2] << 16) | + (buffer[position + 3] << 24); + } + + static ushort ReadUInt16(byte[] buffer, int position) + { + if (position < 0 || position + 2 > buffer.Length) + { + throw new FlatBufferFormatException("Read is outside the buffer."); + } + + return unchecked((ushort)(buffer[position] | (buffer[position + 1] << 8))); + } + + static int CheckedAdd(byte[] buffer, int position, int offset) + { + var result = (long)position + offset; + if (result < 0 || result >= buffer.Length) + { + throw new FlatBufferFormatException("Offset is outside the buffer."); + } + + return (int)result; + } + + int VectorElementPosition(int field, int index, int elementSize) + { + var position = FieldPosition(field); + if (position == 0) + { + throw new FlatBufferFormatException("Vector is not present."); + } + + var vector = CheckedAdd(_buffer, position, ReadInt32(_buffer, position)); + var length = ReadInt32(_buffer, vector); + if (index < 0 || index >= length) + { + throw new FlatBufferFormatException("Vector index is out of range."); + } + + var result = vector + 4 + (index * elementSize); + if (result < 0 || result + elementSize > _buffer.Length) + { + throw new FlatBufferFormatException("Vector element is outside the buffer."); + } + + return result; + } + } +} diff --git a/source/CompDataFlatbuffer/FlatBufferWriter.cs b/source/CompDataFlatbuffer/FlatBufferWriter.cs new file mode 100644 index 00000000..e4fbbc4e --- /dev/null +++ b/source/CompDataFlatbuffer/FlatBufferWriter.cs @@ -0,0 +1,474 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Text; + +namespace CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer +{ + /// + /// A minimal writer for the FlatBuffers binary wire format. Buffers produced + /// by this writer are readable by any FlatBuffers implementation, including + /// the C++ code generated from lottie_comp.fbs. + /// + /// + /// The wire format is described at https://flatbuffers.dev/flatbuffers_internals.html. + /// Data is written from the end of the buffer towards its start, so all + /// offsets returned by this class are measured backwards from the end of the + /// buffer. + /// + sealed class FlatBufferWriter + { + // The offsets of the vtables that have been written so far. Used to + // share identical vtables between tables. + readonly List _vtables = new List(); + + // The field offsets of the table that is currently being built. + readonly List _currentVTable = new List(); + + byte[] _buffer; + + // The number of bytes at the end of _buffer that contain data. + int _used; + + // The offset of the start of the table that is currently being built, + // or -1 if no table is being built. + int _tableStart = -1; + + int _minAlign = 1; + + internal FlatBufferWriter(int initialSize = 4096) + { + _buffer = new byte[Math.Max(initialSize, 32)]; + } + + /// + /// Starts a table with the given number of fields. Fields are added with + /// the Add methods, then the table is completed by calling . + /// + /// The number of fields declared by the table in the schema. + internal void StartTable(int fieldCount) + { + if (_tableStart >= 0) + { + throw new InvalidOperationException("Nested table construction is not supported."); + } + + _currentVTable.Clear(); + for (var i = 0; i < fieldCount; i++) + { + _currentVTable.Add(0); + } + + _tableStart = _used; + } + + /// + /// Completes the table that is being built and returns its offset. + /// + /// The offset of the table. + internal int EndTable() + { + if (_tableStart < 0) + { + throw new InvalidOperationException("No table is being built."); + } + + // Reserve space for the offset to the vtable. + Prep(sizeof(int), 0); + EnsureSpace(sizeof(int)); + _used += sizeof(int); + var tableEnd = _used; + + // Trailing empty fields are implied by the length of the vtable. + var fieldCount = _currentVTable.Count; + while (fieldCount > 0 && _currentVTable[fieldCount - 1] == 0) + { + fieldCount--; + } + + // Write the vtable: the size of the vtable, the size of the table, + // then the offset of each field within the table. + var vtableSize = (fieldCount + 2) * sizeof(short); + Prep(sizeof(short), vtableSize); + for (var i = fieldCount - 1; i >= 0; i--) + { + var fieldOffset = _currentVTable[i]; + WriteInt16(checked((short)(fieldOffset == 0 ? 0 : tableEnd - fieldOffset))); + } + + WriteInt16(checked((short)(tableEnd - _tableStart))); + WriteInt16(checked((short)vtableSize)); + + var vtableOffset = _used; + + // Share an identical vtable if one has already been written. + var sharedVTableOffset = FindMatchingVTable(vtableOffset, vtableSize); + if (sharedVTableOffset >= 0) + { + _used = vtableOffset - vtableSize; + vtableOffset = sharedVTableOffset; + } + else + { + _vtables.Add(vtableOffset); + } + + // Patch the signed offset from the table to its vtable. + WriteInt32At(tableEnd, vtableOffset - tableEnd); + + _tableStart = -1; + return tableEnd; + } + + internal void AddUInt8(int field, byte value, byte defaultValue) + { + if (value != defaultValue) + { + Prep(sizeof(byte), 0); + WriteByte(value); + Slot(field); + } + } + + internal void AddUInt8(int field, byte? value) + { + if (value.HasValue) + { + Prep(sizeof(byte), 0); + WriteByte(value.Value); + Slot(field); + } + } + + internal void AddBool(int field, bool value, bool defaultValue) + => AddUInt8(field, (byte)(value ? 1 : 0), (byte)(defaultValue ? 1 : 0)); + + internal void AddBool(int field, bool? value) + => AddUInt8(field, value.HasValue ? (byte)(value.Value ? 1 : 0) : (byte?)null); + + internal void AddUInt16(int field, ushort value, ushort defaultValue) + { + if (value != defaultValue) + { + Prep(sizeof(short), 0); + WriteInt16(unchecked((short)value)); + Slot(field); + } + } + + internal void AddInt32(int field, int value, int defaultValue) + { + if (value != defaultValue) + { + Prep(sizeof(int), 0); + WriteInt32(value); + Slot(field); + } + } + + internal void AddInt32(int field, int? value) + { + if (value.HasValue) + { + Prep(sizeof(int), 0); + WriteInt32(value.Value); + Slot(field); + } + } + + internal void AddUInt32(int field, uint value, uint defaultValue) + => AddInt32(field, unchecked((int)value), unchecked((int)defaultValue)); + + internal void AddInt64(int field, long value, long defaultValue) + { + if (value != defaultValue) + { + Prep(sizeof(long), 0); + WriteInt64(value); + Slot(field); + } + } + + internal void AddFloat(int field, float value, float defaultValue) + { + if (!value.Equals(defaultValue)) + { + Prep(sizeof(float), 0); + WriteInt32(BitConverter.SingleToInt32Bits(value)); + Slot(field); + } + } + + internal void AddFloat(int field, float? value) + { + if (value.HasValue) + { + Prep(sizeof(float), 0); + WriteInt32(BitConverter.SingleToInt32Bits(value.Value)); + Slot(field); + } + } + + /// + /// Adds a reference to a previously created table, vector or string. + /// An of 0 means "not present". + /// + /// The id of the field. + /// The offset of the referenced data. + internal void AddOffset(int field, int offset) + { + if (offset != 0) + { + Prep(sizeof(int), 0); + WriteInt32(_used + sizeof(int) - offset); + Slot(field); + } + } + + /// + /// Prepares to write an inline struct of the given size and alignment. + /// The struct's fields must then be written in reverse declaration order + /// using the Write methods, followed by . + /// + /// The alignment of the struct. + /// The size of the struct. + internal void StartStruct(int alignment, int size) => Prep(alignment, size); + + /// + /// Adds an inline struct field whose data was just written by + /// and the Write methods. + /// + /// The id of the field. + internal void AddStruct(int field) => Slot(field); + + internal void WriteFloat(float value) => WriteInt32(BitConverter.SingleToInt32Bits(value)); + + internal void WriteUInt8(byte value) => WriteByte(value); + + internal int CreateString(string value) + { + var bytes = Encoding.UTF8.GetBytes(value); + + // Strings are null terminated for the convenience of C++ readers. + Prep(sizeof(int), bytes.Length + 1); + WriteByte(0); + EnsureSpace(bytes.Length); + _used += bytes.Length; + Array.Copy(bytes, 0, _buffer, _buffer.Length - _used, bytes.Length); + WriteInt32(bytes.Length); + return _used; + } + + internal int CreateByteVector(IReadOnlyList values) + { + StartVector(sizeof(byte), values.Count, sizeof(byte)); + for (var i = values.Count - 1; i >= 0; i--) + { + WriteByte(values[i]); + } + + return EndVector(values.Count); + } + + internal int CreateFloatVector(IReadOnlyList values) + { + StartVector(sizeof(float), values.Count, sizeof(float)); + for (var i = values.Count - 1; i >= 0; i--) + { + WriteInt32(BitConverter.SingleToInt32Bits(values[i])); + } + + return EndVector(values.Count); + } + + internal int CreateUInt32Vector(IReadOnlyList values) + { + StartVector(sizeof(uint), values.Count, sizeof(uint)); + for (var i = values.Count - 1; i >= 0; i--) + { + WriteInt32(unchecked((int)values[i])); + } + + return EndVector(values.Count); + } + + /// + /// Creates a vector of offsets to previously created tables or strings. + /// + /// The offsets of the referenced data. + /// The offset of the vector. + internal int CreateOffsetVector(IReadOnlyList offsets) + { + StartVector(sizeof(int), offsets.Count, sizeof(int)); + for (var i = offsets.Count - 1; i >= 0; i--) + { + Prep(sizeof(int), 0); + WriteInt32(_used + sizeof(int) - offsets[i]); + } + + return EndVector(offsets.Count); + } + + /// + /// Completes the buffer, making its root. + /// + /// The offset of the root table. + /// The 4 character file identifier. + /// The completed buffer. + internal byte[] Finish(int rootTable, string fileIdentifier) + { + if (fileIdentifier is null || fileIdentifier.Length != 4) + { + throw new ArgumentException("File identifier must be 4 characters.", nameof(fileIdentifier)); + } + + Prep(_minAlign, sizeof(int) + 4); + for (var i = 3; i >= 0; i--) + { + WriteByte((byte)fileIdentifier[i]); + } + + WriteInt32(_used + sizeof(int) - rootTable); + + var result = new byte[_used]; + Array.Copy(_buffer, _buffer.Length - _used, result, 0, _used); + return result; + } + + void StartVector(int elementSize, int count, int alignment) + { + Prep(sizeof(int), elementSize * count); + Prep(alignment, elementSize * count); + } + + int EndVector(int count) + { + WriteInt32(count); + return _used; + } + + // Records the offset of the field that was just written. + void Slot(int field) + { + if (field >= _currentVTable.Count) + { + throw new ArgumentOutOfRangeException(nameof(field)); + } + + _currentVTable[field] = _used; + } + + // Pads so that, after writing additionalBytes more bytes, the write + // position will be aligned to the given alignment. + void Prep(int alignment, int additionalBytes) + { + if (alignment > _minAlign) + { + _minAlign = alignment; + } + + var alignSize = ((~(_used + additionalBytes)) + 1) & (alignment - 1); + EnsureSpace(alignSize + additionalBytes); + for (var i = 0; i < alignSize; i++) + { + WriteByte(0); + } + } + + int FindMatchingVTable(int vtableOffset, int vtableSize) + { + foreach (var candidate in _vtables) + { + if (ReadInt16At(candidate) != vtableSize) + { + continue; + } + + var match = true; + for (var i = sizeof(short); i < vtableSize; i += sizeof(short)) + { + if (ReadInt16At(candidate - i) != ReadInt16At(vtableOffset - i)) + { + match = false; + break; + } + } + + if (match) + { + return candidate; + } + } + + return -1; + } + + void EnsureSpace(int size) + { + if (_used + size <= _buffer.Length) + { + return; + } + + var newSize = _buffer.Length; + while (newSize < _used + size) + { + newSize *= 2; + } + + var newBuffer = new byte[newSize]; + Array.Copy(_buffer, _buffer.Length - _used, newBuffer, newSize - _used, _used); + _buffer = newBuffer; + } + + void WriteByte(byte value) + { + EnsureSpace(sizeof(byte)); + _used += sizeof(byte); + _buffer[_buffer.Length - _used] = value; + } + + void WriteInt16(short value) + { + EnsureSpace(sizeof(short)); + _used += sizeof(short); + var index = _buffer.Length - _used; + _buffer[index] = unchecked((byte)value); + _buffer[index + 1] = unchecked((byte)(value >> 8)); + } + + void WriteInt32(int value) + { + EnsureSpace(sizeof(int)); + _used += sizeof(int); + WriteInt32At(_used, value); + } + + void WriteInt64(long value) + { + // The buffer is written backwards, so the high word is written first. + WriteInt32(unchecked((int)(value >> 32))); + WriteInt32(unchecked((int)value)); + } + + void WriteInt32At(int offset, int value) + { + var index = _buffer.Length - offset; + _buffer[index] = unchecked((byte)value); + _buffer[index + 1] = unchecked((byte)(value >> 8)); + _buffer[index + 2] = unchecked((byte)(value >> 16)); + _buffer[index + 3] = unchecked((byte)(value >> 24)); + } + + short ReadInt16At(int offset) + { + var index = _buffer.Length - offset; + return unchecked((short)(_buffer[index] | (_buffer[index + 1] << 8))); + } + } +} diff --git a/source/CompDataFlatbuffer/Schema.cs b/source/CompDataFlatbuffer/Schema.cs new file mode 100644 index 00000000..84b3451a --- /dev/null +++ b/source/CompDataFlatbuffer/Schema.cs @@ -0,0 +1,385 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +namespace CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer +{ + /// + /// Field ids, enum values and constants of the lottie_comp.fbs schema. + /// These must be kept in sync with lottie_comp.fbs; field ids are the + /// 0-based declaration order of the fields of each table. + /// + static class Schema + { + /// + /// The version of the schema that this code reads and writes. Bumped for + /// every additive change to lottie_comp.fbs. + /// + internal const ushort Version = 1; + + /// + /// The 4 character identifier stored at offset 4 of every buffer. + /// + internal const string FileIdentifier = "LCMP"; + + /// + /// The file extension used for serialized compositions. + /// + internal const string FileExtension = ".lcomp"; + + /// + /// The value of an index or reference field that refers to nothing. + /// + internal const uint NullIndex = 0xFFFFFFFF; + + /// + /// The number of bits used to store the index part of an object reference. + /// + internal const int ObjectReferenceIndexBits = 28; + + internal static uint PackObjectReference(ObjectCategory category, int index) + => ((uint)category << ObjectReferenceIndexBits) | (uint)index; + + internal static ObjectCategory UnpackCategory(uint reference) + => (ObjectCategory)(reference >> ObjectReferenceIndexBits); + + internal static int UnpackIndex(uint reference) + => (int)(reference & ((1u << ObjectReferenceIndexBits) - 1)); + + internal static class Animator + { + internal const int FieldCount = 3; + internal const int Property = 0; + internal const int Animation = 1; + internal const int Controller = 2; + } + + internal static class CompObj + { + internal const int FieldCount = 3; + internal const int Comment = 0; + internal const int Animators = 1; + internal const int Properties = 2; + } + + internal static class PropertyValue + { + internal const int FieldCount = 5; + internal const int Name = 0; + internal const int Type = 1; + internal const int Scalar = 2; + internal const int Vector = 3; + internal const int Color = 4; + } + + internal static class ReferenceParameter + { + internal const int FieldCount = 2; + internal const int Name = 0; + internal const int Target = 1; + } + + internal static class SourceParameter + { + internal const int FieldCount = 2; + internal const int Name = 0; + internal const int Brush = 1; + } + + internal static class KeyFrame + { + internal const int FieldCount = 8; + internal const int Progress = 0; + internal const int Easing = 1; + internal const int Kind = 2; + internal const int Expression = 3; + internal const int Scalar = 4; + internal const int Vector = 5; + internal const int Color = 6; + internal const int Path = 7; + } + + internal static class Visual + { + internal const int FieldCount = 18; + internal const int Base = 0; + internal const int Kind = 1; + internal const int BorderMode = 2; + internal const int CenterPoint = 3; + internal const int Clip = 4; + internal const int IsVisible = 5; + internal const int Offset = 6; + internal const int Opacity = 7; + internal const int RotationAngleInDegrees = 8; + internal const int RotationAxis = 9; + internal const int Scale = 10; + internal const int Size = 11; + internal const int TransformMatrix = 12; + internal const int Children = 13; + internal const int Shapes = 14; + internal const int ViewBox = 15; + internal const int Brush = 16; + internal const int Shadow = 17; + } + + internal static class Shape + { + internal const int FieldCount = 20; + internal const int Base = 0; + internal const int Kind = 1; + internal const int CenterPoint = 2; + internal const int Offset = 3; + internal const int RotationAngleInDegrees = 4; + internal const int Scale = 5; + internal const int TransformMatrix = 6; + internal const int Shapes = 7; + internal const int FillBrush = 8; + internal const int StrokeBrush = 9; + internal const int Geometry = 10; + internal const int IsStrokeNonScaling = 11; + internal const int StrokeDashOffset = 12; + internal const int StrokeDashArray = 13; + internal const int StrokeDashCap = 14; + internal const int StrokeStartCap = 15; + internal const int StrokeEndCap = 16; + internal const int StrokeLineJoin = 17; + internal const int StrokeMiterLimit = 18; + internal const int StrokeThickness = 19; + } + + internal static class Geometry + { + internal const int FieldCount = 11; + internal const int Base = 0; + internal const int Kind = 1; + internal const int TrimStart = 2; + internal const int TrimEnd = 3; + internal const int TrimOffset = 4; + internal const int Path = 5; + internal const int Offset = 6; + internal const int Size = 7; + internal const int CornerRadius = 8; + internal const int Center = 9; + internal const int Radius = 10; + } + + internal static class Brush + { + internal const int FieldCount = 23; + internal const int Base = 0; + internal const int Kind = 1; + internal const int Color = 2; + internal const int AnchorPoint = 3; + internal const int CenterPoint = 4; + internal const int ColorStops = 5; + internal const int ExtendMode = 6; + internal const int InterpolationSpace = 7; + internal const int MappingMode = 8; + internal const int Offset = 9; + internal const int RotationAngleInDegrees = 10; + internal const int Scale = 11; + internal const int TransformMatrix = 12; + internal const int StartPoint = 13; + internal const int EndPoint = 14; + internal const int EllipseCenter = 15; + internal const int EllipseRadius = 16; + internal const int GradientOriginOffset = 17; + internal const int Surface = 18; + internal const int Source = 19; + internal const int Mask = 20; + internal const int Effect = 21; + internal const int SourceParameters = 22; + } + + internal static class GradientStop + { + internal const int FieldCount = 3; + internal const int Base = 0; + internal const int Color = 1; + internal const int Offset = 2; + } + + internal static class ViewBox + { + internal const int FieldCount = 2; + internal const int Base = 0; + internal const int Size = 1; + } + + internal static class Clip + { + internal const int FieldCount = 9; + internal const int Base = 0; + internal const int Kind = 1; + internal const int CenterPoint = 2; + internal const int Scale = 3; + internal const int LeftInset = 4; + internal const int RightInset = 5; + internal const int TopInset = 6; + internal const int BottomInset = 7; + internal const int Geometry = 8; + } + + internal static class Shadow + { + internal const int FieldCount = 8; + internal const int Base = 0; + internal const int Kind = 1; + internal const int BlurRadius = 2; + internal const int Color = 3; + internal const int Mask = 4; + internal const int Offset = 5; + internal const int Opacity = 6; + internal const int SourcePolicy = 7; + } + + internal static class Surface + { + internal const int FieldCount = 7; + internal const int Base = 0; + internal const int Kind = 1; + internal const int SourceVisual = 2; + internal const int SourceSize = 3; + internal const int SourceOffset = 4; + internal const int Uri = 5; + internal const int Bytes = 6; + } + + internal static class Effect + { + internal const int FieldCount = 5; + internal const int Base = 0; + internal const int Kind = 1; + internal const int Sources = 2; + internal const int Mode = 3; + internal const int BlurAmount = 4; + } + + internal static class Easing + { + internal const int FieldCount = 9; + internal const int Base = 0; + internal const int Kind = 1; + internal const int ControlPoint1 = 2; + internal const int ControlPoint2 = 3; + internal const int StepCount = 4; + internal const int InitialStep = 5; + internal const int FinalStep = 6; + internal const int IsInitialStepSingleFrame = 7; + internal const int IsFinalStepSingleFrame = 8; + } + + internal static class Animation + { + internal const int FieldCount = 8; + internal const int Base = 0; + internal const int Kind = 1; + internal const int Target = 2; + internal const int ReferenceParameters = 3; + internal const int Expression = 4; + internal const int DurationTicks = 5; + internal const int KeyFrames = 6; + internal const int InterpolationColorSpace = 7; + } + + internal static class PropertySet + { + internal const int FieldCount = 3; + internal const int Base = 0; + internal const int Owner = 1; + internal const int Values = 2; + } + + internal static class Controller + { + internal const int FieldCount = 5; + internal const int Base = 0; + internal const int TargetObject = 1; + internal const int TargetProperty = 2; + internal const int IsPaused = 3; + internal const int IsCustom = 4; + } + + internal static class PropertyBinding + { + internal const int FieldCount = 7; + internal const int Name = 0; + internal const int DisplayName = 1; + internal const int ActualType = 2; + internal const int ExposedType = 3; + internal const int DefaultScalar = 4; + internal const int DefaultVector = 5; + internal const int DefaultColor = 6; + } + + internal static class Marker + { + internal const int FieldCount = 3; + internal const int Name = 0; + internal const int Progress = 1; + internal const int DurationProgress = 2; + } + + internal static class Metadata + { + internal const int FieldCount = 8; + internal const int Name = 0; + internal const int Width = 1; + internal const int Height = 2; + internal const int DurationTicks = 3; + internal const int FramesPerSecond = 4; + internal const int PropertyBindings = 5; + internal const int Markers = 6; + internal const int ThemingPropertySet = 7; + } + + internal static class Composition + { + internal const int FieldCount = 21; + internal const int SchemaVersion = 0; + internal const int RequiredUapVersion = 1; + internal const int Metadata = 2; + internal const int RootVisual = 3; + internal const int Strings = 4; + internal const int Visuals = 5; + internal const int Shapes = 6; + internal const int Geometries = 7; + internal const int CanvasGeometries = 8; + internal const int Brushes = 9; + internal const int GradientStops = 10; + internal const int ViewBoxes = 11; + internal const int Clips = 12; + internal const int Shadows = 13; + internal const int Surfaces = 14; + internal const int Effects = 15; + internal const int Easings = 16; + internal const int Animations = 17; + internal const int PropertySets = 18; + internal const int Controllers = 19; + internal const int CustomControllers = 20; + } + + internal static class CanvasGeometry + { + internal const int FieldCount = 16; + internal const int Kind = 0; + internal const int A = 1; + internal const int B = 2; + internal const int CombineMode = 3; + internal const int Matrix = 4; + internal const int Source = 5; + internal const int Geometries = 6; + internal const int FillRule = 7; + internal const int Ops = 8; + internal const int Operands = 9; + internal const int X = 10; + internal const int Y = 11; + internal const int W = 12; + internal const int H = 13; + internal const int RadiusX = 14; + internal const int RadiusY = 15; + } + } +} diff --git a/source/CompDataFlatbuffer/SchemaEnums.cs b/source/CompDataFlatbuffer/SchemaEnums.cs new file mode 100644 index 00000000..ddafef6d --- /dev/null +++ b/source/CompDataFlatbuffer/SchemaEnums.cs @@ -0,0 +1,139 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +namespace CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer +{ + /// + /// Identifies the node vector that a packed object reference points into. + /// Must be kept in sync with the ObjectCategory enum in lottie_comp.fbs. + /// + enum ObjectCategory : byte + { + Visual = 0, + Shape = 1, + Geometry = 2, + Brush = 3, + Animation = 4, + Easing = 5, + PropertySet = 6, + Surface = 7, + Clip = 8, + Controller = 9, + Shadow = 10, + Effect = 11, + GradientStop = 12, + ViewBox = 13, + } + + enum VisualKind : byte + { + Container = 0, + Sprite = 1, + Shape = 2, + Layer = 3, + } + + enum ShapeKind : byte + { + Container = 0, + Sprite = 1, + } + + enum GeometryKind : byte + { + Path = 0, + Rectangle = 1, + RoundedRectangle = 2, + Ellipse = 3, + } + + enum BrushKind : byte + { + Color = 0, + LinearGradient = 1, + RadialGradient = 2, + Surface = 3, + Mask = 4, + Effect = 5, + } + + enum ClipKind : byte + { + Inset = 0, + Geometric = 1, + } + + enum ShadowKind : byte + { + Drop = 0, + } + + enum SurfaceKind : byte + { + VisualSurface = 0, + LoadedImageFromUri = 1, + LoadedImageFromStream = 2, + } + + enum EffectKind : byte + { + Composite = 0, + GaussianBlur = 1, + } + + enum EasingKind : byte + { + Linear = 0, + CubicBezier = 1, + Step = 2, + } + + enum AnimationKind : byte + { + Scalar = 0, + Vector2 = 1, + Vector3 = 2, + Vector4 = 3, + Color = 4, + Boolean = 5, + Path = 6, + Expression = 7, + } + + enum KeyFrameKind : byte + { + Value = 0, + Expression = 1, + } + + enum CanvasGeometryKind : byte + { + Combination = 0, + Ellipse = 1, + Group = 2, + Path = 3, + RoundedRectangle = 4, + TransformedGeometry = 5, + } + + /// + /// The opcodes of the flattened CanvasPathBuilder command stream. + /// + enum PathOp : byte + { + /// Consumes 2 operands: x, y. + BeginFigure = 0, + + /// Consumes 1 operand: the CanvasFigureLoop. + EndFigure = 1, + + /// Consumes 2 operands: x, y. + AddLine = 2, + + /// Consumes 6 operands: cp1.x, cp1.y, cp2.x, cp2.y, end.x, end.y. + AddCubicBezier = 3, + } +} diff --git a/source/LottieFlatbuffer/lottie_comp.fbs b/source/LottieFlatbuffer/lottie_comp.fbs new file mode 100644 index 00000000..1aa8d4dc --- /dev/null +++ b/source/LottieFlatbuffer/lottie_comp.fbs @@ -0,0 +1,529 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +// +// Serialized form of a translated Lottie animation (a WinCompData object graph). +// +// Design notes +// ------------ +// * The graph is stored as a set of flat node vectors - one per node category - +// and all references between nodes are indices into those vectors rather than +// FlatBuffers offsets. This lets an interpreter keep a single realization cache +// per category that is indexed identically to the serialized data, so +// reference-identity caching is an array lookup rather than a hash lookup. +// * Each category is a single table with a "kind" discriminator rather than a +// union of per-kind tables. This keeps the interpreter to one switch per +// category and lets the shared property-application code be written once. +// * Absent fields mean "property was never set", exactly matching the nullable +// properties of the WinCompData object model. +// +// Versioning rules +// ---------------- +// Changes MUST be additive: new fields are appended to the end of a table, and +// new enum values are appended to the end of an enum. SCHEMA_VERSION is bumped +// for every additive change; readers reject buffers whose schema_version is +// greater than the version they were built against. + +namespace CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema; + +// --------------------------------------------------------------------------- +// Value types. +// --------------------------------------------------------------------------- + +struct Vec2 { x: float; y: float; } +struct Vec3 { x: float; y: float; z: float; } +struct Vec4 { x: float; y: float; z: float; w: float; } +struct Mat3x2 { m11: float; m12: float; m21: float; m22: float; m31: float; m32: float; } +struct Mat4x4 { + m11: float; m12: float; m13: float; m14: float; + m21: float; m22: float; m23: float; m24: float; + m31: float; m32: float; m33: float; m34: float; + m41: float; m42: float; m43: float; m44: float; +} +struct Color { a: ubyte; r: ubyte; g: ubyte; b: ubyte; } + +// --------------------------------------------------------------------------- +// Enums. Values match the corresponding WinCompData enums. +// --------------------------------------------------------------------------- + +// Identifies the node vector that an object reference points into. +// An object reference is packed into a uint as (category << 28) | index. +// The value 0xFFFFFFFF means "no reference". +enum ObjectCategory : ubyte { + Visual = 0, + Shape = 1, + Geometry = 2, + Brush = 3, + Animation = 4, + Easing = 5, + PropertySet = 6, + Surface = 7, + Clip = 8, + Controller = 9, + Shadow = 10, + Effect = 11, + GradientStop = 12, + ViewBox = 13, +} + +enum VisualKind : ubyte { Container = 0, Sprite = 1, Shape = 2, Layer = 3 } +enum ShapeKind : ubyte { Container = 0, Sprite = 1 } +enum GeometryKind : ubyte { Path = 0, Rectangle = 1, RoundedRectangle = 2, Ellipse = 3 } +enum BrushKind : ubyte { Color = 0, LinearGradient = 1, RadialGradient = 2, Surface = 3, Mask = 4, Effect = 5 } +enum ClipKind : ubyte { Inset = 0, Geometric = 1 } +enum ShadowKind : ubyte { Drop = 0 } +enum SurfaceKind : ubyte { VisualSurface = 0, LoadedImageFromUri = 1, LoadedImageFromStream = 2 } +enum EffectKind : ubyte { Composite = 0, GaussianBlur = 1 } +enum EasingKind : ubyte { Linear = 0, CubicBezier = 1, Step = 2 } + +enum AnimationKind : ubyte { + Scalar = 0, + Vector2 = 1, + Vector3 = 2, + Vector4 = 3, + Color = 4, + Boolean = 5, + Path = 6, + Expression = 7, +} + +enum KeyFrameKind : ubyte { Value = 0, Expression = 1 } + +enum CanvasGeometryKind : ubyte { + Combination = 0, + Ellipse = 1, + Group = 2, + Path = 3, + RoundedRectangle = 4, + TransformedGeometry = 5, +} + +// Opcodes of the flattened CanvasPathBuilder command stream. Each opcode +// consumes a fixed number of floats from the operand vector. +enum PathOp : ubyte { + BeginFigure = 0, // 2 operands: x, y + EndFigure = 1, // 1 operand: CanvasFigureLoop as a float + AddLine = 2, // 2 operands: x, y + AddCubicBezier = 3, // 6 operands: cp1.x, cp1.y, cp2.x, cp2.y, end.x, end.y +} + +// Mirrors WinCompData.MetaData.PropertySetValueType. +enum PropertyValueType : ubyte { + None = 0, + Color = 1, + Scalar = 2, + Vector2 = 3, + Vector3 = 4, + Vector4 = 5, +} + +// --------------------------------------------------------------------------- +// Shared structures. +// --------------------------------------------------------------------------- + +// An animation bound to a property of an object. +table Animator { + property: uint; // string index of the animated property name + animation: uint; // index into LottieComposition.animations + controller: uint = 0xFFFFFFFF; // index into LottieComposition.controllers +} + +// State common to every CompositionObject. Omitted when empty. +table CompObj { + comment: uint = 0xFFFFFFFF; // string index + animators: [Animator]; + + // The CompositionPropertySet of the object, if it has any values or + // animations. Index into LottieComposition.property_sets. + properties: uint = 0xFFFFFFFF; +} + +// A named value in a CompositionPropertySet. +table PropertyValue { + name: uint; // string index + type: PropertyValueType; + scalar: float; + vector: Vec4; + color: Color; +} + +// A named reference parameter of a CompositionAnimation. +table ReferenceParameter { + name: uint; // string index + target: uint; // packed object reference +} + +// A named source of a CompositionEffectBrush. +table SourceParameter { + name: uint; // string index + brush: uint; // index into LottieComposition.brushes +} + +table KeyFrame { + progress: float; + easing: uint = 0xFFFFFFFF; // index into LottieComposition.easings + kind: KeyFrameKind; + expression: uint = 0xFFFFFFFF; // string index, for expression key frames + scalar: float; // Scalar and Boolean values + vector: Vec4; // Vector2, Vector3 and Vector4 values + color: Color; + path: uint = 0xFFFFFFFF; // index into LottieComposition.canvas_geometries +} + +// --------------------------------------------------------------------------- +// Node tables. +// --------------------------------------------------------------------------- + +table Visual { + base: CompObj; + kind: VisualKind; + + // Visual. + border_mode: ubyte = null; + center_point: Vec3; + clip: uint = 0xFFFFFFFF; + is_visible: bool = null; + offset: Vec3; + opacity: float = null; + rotation_angle_in_degrees: float = null; + rotation_axis: Vec3; + scale: Vec3; + size: Vec2; + transform_matrix: Mat4x4; + + // ContainerVisual and LayerVisual. + children: [uint]; + + // ShapeVisual. + shapes: [uint]; + view_box: uint = 0xFFFFFFFF; + + // SpriteVisual. + brush: uint = 0xFFFFFFFF; + + // SpriteVisual and LayerVisual. + shadow: uint = 0xFFFFFFFF; +} + +table Shape { + base: CompObj; + kind: ShapeKind; + + // CompositionShape. + center_point: Vec2; + offset: Vec2; + rotation_angle_in_degrees: float = null; + scale: Vec2; + transform_matrix: Mat3x2; + + // CompositionContainerShape. + shapes: [uint]; + + // CompositionSpriteShape. + fill_brush: uint = 0xFFFFFFFF; + stroke_brush: uint = 0xFFFFFFFF; + geometry: uint = 0xFFFFFFFF; + is_stroke_non_scaling: bool = null; + stroke_dash_offset: float = null; + stroke_dash_array: [float]; + stroke_dash_cap: ubyte = null; + stroke_start_cap: ubyte = null; + stroke_end_cap: ubyte = null; + stroke_line_join: ubyte = null; + stroke_miter_limit: float = null; + stroke_thickness: float = null; +} + +table Geometry { + base: CompObj; + kind: GeometryKind; + + // CompositionGeometry. + trim_start: float = null; + trim_end: float = null; + trim_offset: float = null; + + // CompositionPathGeometry. + path: uint = 0xFFFFFFFF; // index into canvas_geometries + + // CompositionRectangleGeometry and CompositionRoundedRectangleGeometry. + offset: Vec2; + size: Vec2; + + // CompositionRoundedRectangleGeometry. + corner_radius: Vec2; + + // CompositionEllipseGeometry. + center: Vec2; + radius: Vec2; +} + +table Brush { + base: CompObj; + kind: BrushKind; + + // CompositionColorBrush. + color: Color; + + // CompositionGradientBrush. + anchor_point: Vec2; + center_point: Vec2; + color_stops: [uint]; + extend_mode: ubyte = null; + interpolation_space: ubyte = null; + mapping_mode: ubyte = null; + offset: Vec2; + rotation_angle_in_degrees: float = null; + scale: Vec2; + transform_matrix: Mat3x2; + + // CompositionLinearGradientBrush. + start_point: Vec2; + end_point: Vec2; + + // CompositionRadialGradientBrush. + ellipse_center: Vec2; + ellipse_radius: Vec2; + gradient_origin_offset: Vec2; + + // CompositionSurfaceBrush. + surface: uint = 0xFFFFFFFF; + + // CompositionMaskBrush. + source: uint = 0xFFFFFFFF; + mask: uint = 0xFFFFFFFF; + + // CompositionEffectBrush. + effect: uint = 0xFFFFFFFF; + source_parameters: [SourceParameter]; +} + +table GradientStop { + base: CompObj; + color: Color; + offset: float; +} + +table ViewBox { + base: CompObj; + size: Vec2; +} + +table Clip { + base: CompObj; + kind: ClipKind; + + // CompositionClip. + center_point: Vec2; + scale: Vec2; + + // InsetClip. + left_inset: float = null; + right_inset: float = null; + top_inset: float = null; + bottom_inset: float = null; + + // CompositionGeometricClip. + geometry: uint = 0xFFFFFFFF; +} + +table Shadow { + base: CompObj; + kind: ShadowKind; + + // DropShadow. + blur_radius: float = null; + color: Color; + mask: uint = 0xFFFFFFFF; + offset: Vec3; + opacity: float = null; + source_policy: ubyte = null; +} + +table Surface { + base: CompObj; + kind: SurfaceKind; + + // CompositionVisualSurface. + source_visual: uint = 0xFFFFFFFF; + source_size: Vec2; + source_offset: Vec2; + + // LoadedImageSurfaceFromUri. + uri: uint = 0xFFFFFFFF; // string index + + // LoadedImageSurfaceFromStream. + bytes: [ubyte]; +} + +table Effect { + base: CompObj; + kind: EffectKind; + + // Names (string indices) of the CompositionEffectSourceParameters. + sources: [uint]; + + // CompositeEffect. + mode: ubyte; // CanvasComposite + + // GaussianBlurEffect. + blur_amount: float; +} + +table Easing { + base: CompObj; + kind: EasingKind; + + // CubicBezierEasingFunction. + control_point_1: Vec2; + control_point_2: Vec2; + + // StepEasingFunction. + step_count: int = null; + initial_step: int = null; + final_step: int = null; + is_initial_step_single_frame: bool = null; + is_final_step_single_frame: bool = null; +} + +table Animation { + base: CompObj; + kind: AnimationKind; + target: uint = 0xFFFFFFFF; // string index + reference_parameters: [ReferenceParameter]; + + // ExpressionAnimation. + expression: uint = 0xFFFFFFFF; // string index + + // KeyFrameAnimation. + duration_ticks: long; + key_frames: [KeyFrame]; + + // ColorKeyFrameAnimation. + interpolation_color_space: ubyte = null; +} + +table PropertySet { + base: CompObj; + + // The object that owns this property set, or 0xFFFFFFFF if the property set + // is standalone (created by Compositor.CreatePropertySet). + owner: uint = 0xFFFFFFFF; // packed object reference + values: [PropertyValue]; +} + +table Controller { + base: CompObj; + + // The object and property that this controller controls. Absent for a + // custom controller. + target_object: uint = 0xFFFFFFFF; // packed object reference + target_property: uint = 0xFFFFFFFF; // string index + is_paused: bool; + is_custom: bool; +} + +// --------------------------------------------------------------------------- +// Metadata. +// --------------------------------------------------------------------------- + +table PropertyBinding { + name: uint; // string index of the binding name + display_name: uint; // string index + actual_type: PropertyValueType; + exposed_type: PropertyValueType; + default_scalar: float; + default_vector: Vec4; + default_color: Color; +} + +table Marker { + name: uint; // string index + progress: float; // start of the marker, 0..1 + duration_progress: float; // duration of the marker, 0..1 +} + +table Metadata { + name: uint = 0xFFFFFFFF; // string index of the composition name + width: float; + height: float; + duration_ticks: long; + frames_per_second: float; + property_bindings: [PropertyBinding]; + markers: [Marker]; + + // The property set that holds the theming properties, or 0xFFFFFFFF. + theming_property_set: uint = 0xFFFFFFFF; // index into property_sets +} + +// --------------------------------------------------------------------------- +// Root. +// --------------------------------------------------------------------------- + +table LottieComposition { + schema_version: ushort; + required_uap_version: ushort; + metadata: Metadata; + + // Index into visuals of the root of the tree. + root_visual: uint = 0xFFFFFFFF; + + strings: [string]; + visuals: [Visual]; + shapes: [Shape]; + geometries: [Geometry]; + canvas_geometries: [CanvasGeometry]; + brushes: [Brush]; + gradient_stops: [GradientStop]; + view_boxes: [ViewBox]; + clips: [Clip]; + shadows: [Shadow]; + surfaces: [Surface]; + effects: [Effect]; + easings: [Easing]; + animations: [Animation]; + property_sets: [PropertySet]; + controllers: [Controller]; + + // Indices into controllers of the custom animation controllers, in the order + // that a host should expose them. + custom_controllers: [uint]; +} + +table CanvasGeometry { + kind: CanvasGeometryKind; + + // Combination. + a: uint = 0xFFFFFFFF; + b: uint = 0xFFFFFFFF; + combine_mode: ubyte; + + // Combination and TransformedGeometry. + matrix: Mat3x2; + + // TransformedGeometry. + source: uint = 0xFFFFFFFF; + + // Group. + geometries: [uint]; + + // Group and Path. + fill_rule: ubyte; // CanvasFilledRegionDetermination + + // Path. + ops: [ubyte]; + operands: [float]; + + // Ellipse (x, y, radius_x, radius_y) and + // RoundedRectangle (x, y, w, h, radius_x, radius_y). + x: float; + y: float; + w: float; + h: float; + radius_x: float; + radius_y: float; +} + +root_type LottieComposition; +file_identifier "LCMP"; +file_extension "lcomp"; From 720203b7d19a14ecffa8c5d52b30d518368e8ef0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 31 Aug 2026 17:54:30 +0000 Subject: [PATCH 02/39] Use official Google.FlatBuffers tooling instead of hand-rolled reader/writer Co-authored-by: jonwis <18537118+jonwis@users.noreply.github.com> --- build/RegenerateFlatbuffers.ps1 | 45 + .../CompDataFlatbuffer.dll.csproj | 24 + .../CompDataFlatbuffer.projitems | 19 + .../CompDataFlatbuffer.shproj | 13 + .../FlatBufferFormatException.cs | 33 - source/CompDataFlatbuffer/FlatBufferTable.cs | 325 --- source/CompDataFlatbuffer/FlatBufferWriter.cs | 474 ---- source/CompDataFlatbuffer/Format.cs | 79 + .../Generated/lottie_comp_generated.cs | 2051 +++++++++++++++++ source/CompDataFlatbuffer/Schema.cs | 385 ---- source/CompDataFlatbuffer/SchemaEnums.cs | 139 -- 11 files changed, 2231 insertions(+), 1356 deletions(-) create mode 100644 build/RegenerateFlatbuffers.ps1 create mode 100644 dlls/CompDataFlatbuffer/CompDataFlatbuffer.dll.csproj create mode 100644 source/CompDataFlatbuffer/CompDataFlatbuffer.projitems create mode 100644 source/CompDataFlatbuffer/CompDataFlatbuffer.shproj delete mode 100644 source/CompDataFlatbuffer/FlatBufferFormatException.cs delete mode 100644 source/CompDataFlatbuffer/FlatBufferTable.cs delete mode 100644 source/CompDataFlatbuffer/FlatBufferWriter.cs create mode 100644 source/CompDataFlatbuffer/Format.cs create mode 100644 source/CompDataFlatbuffer/Generated/lottie_comp_generated.cs delete mode 100644 source/CompDataFlatbuffer/Schema.cs delete mode 100644 source/CompDataFlatbuffer/SchemaEnums.cs diff --git a/build/RegenerateFlatbuffers.ps1 b/build/RegenerateFlatbuffers.ps1 new file mode 100644 index 00000000..8cc52934 --- /dev/null +++ b/build/RegenerateFlatbuffers.ps1 @@ -0,0 +1,45 @@ +# Regenerates the FlatBuffers code for lottie_comp.fbs. +# +# The generated C# and C++ sources are checked in so that contributors do not +# need flatc installed in order to build. Run this script after any change to +# source/LottieFlatbuffer/lottie_comp.fbs, and check in the result. +# +# The version of flatc MUST match the version of the Google.FlatBuffers NuGet +# package referenced by dlls/CompDataFlatbuffer/CompDataFlatbuffer.dll.csproj, +# because the generated C# calls a version-stamped validation method +# (FlatBufferConstants.FLATBUFFERS_) that only exists in the matching +# package. +# +# Download flatc from https://github.com/google/flatbuffers/releases/tag/v25.2.10 + +[CmdletBinding()] +param( + # Path to the flatc executable. Defaults to whatever is on the PATH. + [string]$Flatc = "flatc" +) + +$ErrorActionPreference = "Stop" + +$expectedVersion = "25.2.10" + +$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..") +$schema = Join-Path $repoRoot "source/LottieFlatbuffer/lottie_comp.fbs" +$csharpOut = Join-Path $repoRoot "source/CompDataFlatbuffer/Generated" +$cppOut = Join-Path $repoRoot "dlls/LottieRuntime/Generated" + +$actualVersion = (& $Flatc --version) -replace '^flatc version ', '' +if ($actualVersion.Trim() -ne $expectedVersion) { + throw "flatc version $expectedVersion is required, but '$Flatc' is version $($actualVersion.Trim())." +} + +New-Item -ItemType Directory -Force -Path $csharpOut, $cppOut | Out-Null + +# --gen-onefile keeps the checked in C# to a single file rather than one file +# per type spread over a namespace directory tree. +& $Flatc --csharp --gen-onefile -o $csharpOut $schema +if ($LASTEXITCODE -ne 0) { throw "flatc failed to generate C#." } + +& $Flatc --cpp --scoped-enums -o $cppOut $schema +if ($LASTEXITCODE -ne 0) { throw "flatc failed to generate C++." } + +Write-Host "Generated FlatBuffers code from $schema" diff --git a/dlls/CompDataFlatbuffer/CompDataFlatbuffer.dll.csproj b/dlls/CompDataFlatbuffer/CompDataFlatbuffer.dll.csproj new file mode 100644 index 00000000..cb3d2170 --- /dev/null +++ b/dlls/CompDataFlatbuffer/CompDataFlatbuffer.dll.csproj @@ -0,0 +1,24 @@ + + + + net9.0 + Debug;Release + latest + PUBLIC_CompDataFlatbuffer + + + + + + + + + + + + + + + + diff --git a/source/CompDataFlatbuffer/CompDataFlatbuffer.projitems b/source/CompDataFlatbuffer/CompDataFlatbuffer.projitems new file mode 100644 index 00000000..add80aa3 --- /dev/null +++ b/source/CompDataFlatbuffer/CompDataFlatbuffer.projitems @@ -0,0 +1,19 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 5a3f2c41-9a2e-4c2d-9f4a-1d1a0f6b3c77 + + + CompDataFlatbuffer + + + + + + + + + diff --git a/source/CompDataFlatbuffer/CompDataFlatbuffer.shproj b/source/CompDataFlatbuffer/CompDataFlatbuffer.shproj new file mode 100644 index 00000000..5a214f6b --- /dev/null +++ b/source/CompDataFlatbuffer/CompDataFlatbuffer.shproj @@ -0,0 +1,13 @@ + + + + 5a3f2c41-9a2e-4c2d-9f4a-1d1a0f6b3c77 + 14.0 + + + + + + + + diff --git a/source/CompDataFlatbuffer/FlatBufferFormatException.cs b/source/CompDataFlatbuffer/FlatBufferFormatException.cs deleted file mode 100644 index 62756e2c..00000000 --- a/source/CompDataFlatbuffer/FlatBufferFormatException.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable enable - -using System; - -namespace CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer -{ - /// - /// Thrown when a buffer is not a valid serialized composition. - /// -#if PUBLIC_CompDataFlatbuffer - public -#endif - sealed class FlatBufferFormatException : Exception - { - public FlatBufferFormatException() - { - } - - public FlatBufferFormatException(string message) - : base(message) - { - } - - public FlatBufferFormatException(string message, Exception innerException) - : base(message, innerException) - { - } - } -} diff --git a/source/CompDataFlatbuffer/FlatBufferTable.cs b/source/CompDataFlatbuffer/FlatBufferTable.cs deleted file mode 100644 index 8338ac78..00000000 --- a/source/CompDataFlatbuffer/FlatBufferTable.cs +++ /dev/null @@ -1,325 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable enable - -using System; -using System.Text; - -namespace CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer -{ - /// - /// A minimal reader for the FlatBuffers binary wire format. All accesses are - /// bounds checked; a malformed buffer causes a - /// rather than undefined behavior. - /// - readonly struct FlatBufferTable - { - readonly byte[] _buffer; - readonly int _position; - - internal FlatBufferTable(byte[] buffer, int position) - { - _buffer = buffer; - _position = position; - } - - internal bool IsNull => _buffer is null; - - /// - /// Gets the root table of a buffer, checking its file identifier. - /// - /// The buffer. - /// The expected 4 character file identifier. - /// The root table. - internal static FlatBufferTable GetRoot(byte[] buffer, string fileIdentifier) - { - if (buffer is null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (buffer.Length < 8) - { - throw new FlatBufferFormatException("Buffer is too small."); - } - - for (var i = 0; i < 4; i++) - { - if (buffer[4 + i] != (byte)fileIdentifier[i]) - { - throw new FlatBufferFormatException("Unrecognized file identifier."); - } - } - - var rootOffset = ReadInt32(buffer, 0); - return new FlatBufferTable(buffer, CheckedAdd(buffer, 0, rootOffset)); - } - - /// - /// Returns the absolute position of the data of the field with the given - /// id, or 0 if the field is not present. - /// - /// The id of the field. - /// The position of the field's data, or 0. - internal int FieldPosition(int field) - { - var vtable = _position - ReadInt32(_buffer, _position); - if (vtable < 0 || vtable + 4 > _buffer.Length) - { - throw new FlatBufferFormatException("Invalid vtable offset."); - } - - var vtableSize = ReadUInt16(_buffer, vtable); - var fieldIndex = 4 + (field * 2); - if (fieldIndex + 2 > vtableSize) - { - return 0; - } - - var fieldOffset = ReadUInt16(_buffer, vtable + fieldIndex); - if (fieldOffset == 0) - { - return 0; - } - - var result = _position + fieldOffset; - if (result < 0 || result >= _buffer.Length) - { - throw new FlatBufferFormatException("Field is outside the buffer."); - } - - return result; - } - - internal byte GetUInt8(int field, byte defaultValue) - { - var position = FieldPosition(field); - return position == 0 ? defaultValue : _buffer[position]; - } - - internal byte? GetOptionalUInt8(int field) - { - var position = FieldPosition(field); - return position == 0 ? (byte?)null : _buffer[position]; - } - - internal bool GetBool(int field, bool defaultValue) - => GetUInt8(field, (byte)(defaultValue ? 1 : 0)) != 0; - - internal bool? GetOptionalBool(int field) - { - var value = GetOptionalUInt8(field); - return value.HasValue ? value.Value != 0 : (bool?)null; - } - - internal ushort GetUInt16(int field, ushort defaultValue) - { - var position = FieldPosition(field); - return position == 0 ? defaultValue : ReadUInt16(_buffer, position); - } - - internal int GetInt32(int field, int defaultValue) - { - var position = FieldPosition(field); - return position == 0 ? defaultValue : ReadInt32(_buffer, position); - } - - internal int? GetOptionalInt32(int field) - { - var position = FieldPosition(field); - return position == 0 ? (int?)null : ReadInt32(_buffer, position); - } - - internal uint GetUInt32(int field, uint defaultValue) - { - var position = FieldPosition(field); - return position == 0 ? defaultValue : unchecked((uint)ReadInt32(_buffer, position)); - } - - internal long GetInt64(int field, long defaultValue) - { - var position = FieldPosition(field); - if (position == 0) - { - return defaultValue; - } - - var low = unchecked((uint)ReadInt32(_buffer, position)); - var high = ReadInt32(_buffer, position + 4); - return unchecked((long)(((ulong)(uint)high << 32) | low)); - } - - internal float GetFloat(int field, float defaultValue) - { - var position = FieldPosition(field); - return position == 0 ? defaultValue : BitConverter.Int32BitsToSingle(ReadInt32(_buffer, position)); - } - - internal float? GetOptionalFloat(int field) - { - var position = FieldPosition(field); - return position == 0 ? (float?)null : BitConverter.Int32BitsToSingle(ReadInt32(_buffer, position)); - } - - /// - /// Returns the position of an inline struct field, or 0 if not present. - /// Use the ReadStructFloat and ReadStructUInt8 methods to read its fields. - /// - /// The id of the field. - /// The position of the struct, or 0. - internal int GetStructPosition(int field) => FieldPosition(field); - - internal float ReadStructFloat(int structPosition, int offset) - => BitConverter.Int32BitsToSingle(ReadInt32(_buffer, structPosition + offset)); - - internal byte ReadStructUInt8(int structPosition, int offset) - { - if (structPosition + offset >= _buffer.Length) - { - throw new FlatBufferFormatException("Struct is outside the buffer."); - } - - return _buffer[structPosition + offset]; - } - - internal FlatBufferTable GetTable(int field) - { - var position = FieldPosition(field); - return position == 0 - ? default - : new FlatBufferTable(_buffer, CheckedAdd(_buffer, position, ReadInt32(_buffer, position))); - } - - internal string? GetString(int field) - { - var position = FieldPosition(field); - if (position == 0) - { - return null; - } - - var stringPosition = CheckedAdd(_buffer, position, ReadInt32(_buffer, position)); - var length = ReadInt32(_buffer, stringPosition); - if (length < 0 || stringPosition + 4 + length > _buffer.Length) - { - throw new FlatBufferFormatException("String is outside the buffer."); - } - - return Encoding.UTF8.GetString(_buffer, stringPosition + 4, length); - } - - /// - /// Returns the number of elements in a vector field. - /// - /// The id of the field. - /// The number of elements, or 0 if the field is not present. - internal int VectorLength(int field) - { - var position = FieldPosition(field); - if (position == 0) - { - return 0; - } - - var vector = CheckedAdd(_buffer, position, ReadInt32(_buffer, position)); - var length = ReadInt32(_buffer, vector); - if (length < 0) - { - throw new FlatBufferFormatException("Invalid vector length."); - } - - return length; - } - - internal byte GetVectorUInt8(int field, int index) - { - var position = VectorElementPosition(field, index, sizeof(byte)); - return _buffer[position]; - } - - internal float GetVectorFloat(int field, int index) - => BitConverter.Int32BitsToSingle(ReadInt32(_buffer, VectorElementPosition(field, index, sizeof(float)))); - - internal uint GetVectorUInt32(int field, int index) - => unchecked((uint)ReadInt32(_buffer, VectorElementPosition(field, index, sizeof(uint)))); - - internal FlatBufferTable GetVectorTable(int field, int index) - { - var position = VectorElementPosition(field, index, sizeof(int)); - return new FlatBufferTable(_buffer, CheckedAdd(_buffer, position, ReadInt32(_buffer, position))); - } - - internal string GetVectorString(int field, int index) - { - var position = VectorElementPosition(field, index, sizeof(int)); - var stringPosition = CheckedAdd(_buffer, position, ReadInt32(_buffer, position)); - var length = ReadInt32(_buffer, stringPosition); - if (length < 0 || stringPosition + 4 + length > _buffer.Length) - { - throw new FlatBufferFormatException("String is outside the buffer."); - } - - return Encoding.UTF8.GetString(_buffer, stringPosition + 4, length); - } - - static int ReadInt32(byte[] buffer, int position) - { - if (position < 0 || position + 4 > buffer.Length) - { - throw new FlatBufferFormatException("Read is outside the buffer."); - } - - return buffer[position] | - (buffer[position + 1] << 8) | - (buffer[position + 2] << 16) | - (buffer[position + 3] << 24); - } - - static ushort ReadUInt16(byte[] buffer, int position) - { - if (position < 0 || position + 2 > buffer.Length) - { - throw new FlatBufferFormatException("Read is outside the buffer."); - } - - return unchecked((ushort)(buffer[position] | (buffer[position + 1] << 8))); - } - - static int CheckedAdd(byte[] buffer, int position, int offset) - { - var result = (long)position + offset; - if (result < 0 || result >= buffer.Length) - { - throw new FlatBufferFormatException("Offset is outside the buffer."); - } - - return (int)result; - } - - int VectorElementPosition(int field, int index, int elementSize) - { - var position = FieldPosition(field); - if (position == 0) - { - throw new FlatBufferFormatException("Vector is not present."); - } - - var vector = CheckedAdd(_buffer, position, ReadInt32(_buffer, position)); - var length = ReadInt32(_buffer, vector); - if (index < 0 || index >= length) - { - throw new FlatBufferFormatException("Vector index is out of range."); - } - - var result = vector + 4 + (index * elementSize); - if (result < 0 || result + elementSize > _buffer.Length) - { - throw new FlatBufferFormatException("Vector element is outside the buffer."); - } - - return result; - } - } -} diff --git a/source/CompDataFlatbuffer/FlatBufferWriter.cs b/source/CompDataFlatbuffer/FlatBufferWriter.cs deleted file mode 100644 index e4fbbc4e..00000000 --- a/source/CompDataFlatbuffer/FlatBufferWriter.cs +++ /dev/null @@ -1,474 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable enable - -using System; -using System.Collections.Generic; -using System.Text; - -namespace CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer -{ - /// - /// A minimal writer for the FlatBuffers binary wire format. Buffers produced - /// by this writer are readable by any FlatBuffers implementation, including - /// the C++ code generated from lottie_comp.fbs. - /// - /// - /// The wire format is described at https://flatbuffers.dev/flatbuffers_internals.html. - /// Data is written from the end of the buffer towards its start, so all - /// offsets returned by this class are measured backwards from the end of the - /// buffer. - /// - sealed class FlatBufferWriter - { - // The offsets of the vtables that have been written so far. Used to - // share identical vtables between tables. - readonly List _vtables = new List(); - - // The field offsets of the table that is currently being built. - readonly List _currentVTable = new List(); - - byte[] _buffer; - - // The number of bytes at the end of _buffer that contain data. - int _used; - - // The offset of the start of the table that is currently being built, - // or -1 if no table is being built. - int _tableStart = -1; - - int _minAlign = 1; - - internal FlatBufferWriter(int initialSize = 4096) - { - _buffer = new byte[Math.Max(initialSize, 32)]; - } - - /// - /// Starts a table with the given number of fields. Fields are added with - /// the Add methods, then the table is completed by calling . - /// - /// The number of fields declared by the table in the schema. - internal void StartTable(int fieldCount) - { - if (_tableStart >= 0) - { - throw new InvalidOperationException("Nested table construction is not supported."); - } - - _currentVTable.Clear(); - for (var i = 0; i < fieldCount; i++) - { - _currentVTable.Add(0); - } - - _tableStart = _used; - } - - /// - /// Completes the table that is being built and returns its offset. - /// - /// The offset of the table. - internal int EndTable() - { - if (_tableStart < 0) - { - throw new InvalidOperationException("No table is being built."); - } - - // Reserve space for the offset to the vtable. - Prep(sizeof(int), 0); - EnsureSpace(sizeof(int)); - _used += sizeof(int); - var tableEnd = _used; - - // Trailing empty fields are implied by the length of the vtable. - var fieldCount = _currentVTable.Count; - while (fieldCount > 0 && _currentVTable[fieldCount - 1] == 0) - { - fieldCount--; - } - - // Write the vtable: the size of the vtable, the size of the table, - // then the offset of each field within the table. - var vtableSize = (fieldCount + 2) * sizeof(short); - Prep(sizeof(short), vtableSize); - for (var i = fieldCount - 1; i >= 0; i--) - { - var fieldOffset = _currentVTable[i]; - WriteInt16(checked((short)(fieldOffset == 0 ? 0 : tableEnd - fieldOffset))); - } - - WriteInt16(checked((short)(tableEnd - _tableStart))); - WriteInt16(checked((short)vtableSize)); - - var vtableOffset = _used; - - // Share an identical vtable if one has already been written. - var sharedVTableOffset = FindMatchingVTable(vtableOffset, vtableSize); - if (sharedVTableOffset >= 0) - { - _used = vtableOffset - vtableSize; - vtableOffset = sharedVTableOffset; - } - else - { - _vtables.Add(vtableOffset); - } - - // Patch the signed offset from the table to its vtable. - WriteInt32At(tableEnd, vtableOffset - tableEnd); - - _tableStart = -1; - return tableEnd; - } - - internal void AddUInt8(int field, byte value, byte defaultValue) - { - if (value != defaultValue) - { - Prep(sizeof(byte), 0); - WriteByte(value); - Slot(field); - } - } - - internal void AddUInt8(int field, byte? value) - { - if (value.HasValue) - { - Prep(sizeof(byte), 0); - WriteByte(value.Value); - Slot(field); - } - } - - internal void AddBool(int field, bool value, bool defaultValue) - => AddUInt8(field, (byte)(value ? 1 : 0), (byte)(defaultValue ? 1 : 0)); - - internal void AddBool(int field, bool? value) - => AddUInt8(field, value.HasValue ? (byte)(value.Value ? 1 : 0) : (byte?)null); - - internal void AddUInt16(int field, ushort value, ushort defaultValue) - { - if (value != defaultValue) - { - Prep(sizeof(short), 0); - WriteInt16(unchecked((short)value)); - Slot(field); - } - } - - internal void AddInt32(int field, int value, int defaultValue) - { - if (value != defaultValue) - { - Prep(sizeof(int), 0); - WriteInt32(value); - Slot(field); - } - } - - internal void AddInt32(int field, int? value) - { - if (value.HasValue) - { - Prep(sizeof(int), 0); - WriteInt32(value.Value); - Slot(field); - } - } - - internal void AddUInt32(int field, uint value, uint defaultValue) - => AddInt32(field, unchecked((int)value), unchecked((int)defaultValue)); - - internal void AddInt64(int field, long value, long defaultValue) - { - if (value != defaultValue) - { - Prep(sizeof(long), 0); - WriteInt64(value); - Slot(field); - } - } - - internal void AddFloat(int field, float value, float defaultValue) - { - if (!value.Equals(defaultValue)) - { - Prep(sizeof(float), 0); - WriteInt32(BitConverter.SingleToInt32Bits(value)); - Slot(field); - } - } - - internal void AddFloat(int field, float? value) - { - if (value.HasValue) - { - Prep(sizeof(float), 0); - WriteInt32(BitConverter.SingleToInt32Bits(value.Value)); - Slot(field); - } - } - - /// - /// Adds a reference to a previously created table, vector or string. - /// An of 0 means "not present". - /// - /// The id of the field. - /// The offset of the referenced data. - internal void AddOffset(int field, int offset) - { - if (offset != 0) - { - Prep(sizeof(int), 0); - WriteInt32(_used + sizeof(int) - offset); - Slot(field); - } - } - - /// - /// Prepares to write an inline struct of the given size and alignment. - /// The struct's fields must then be written in reverse declaration order - /// using the Write methods, followed by . - /// - /// The alignment of the struct. - /// The size of the struct. - internal void StartStruct(int alignment, int size) => Prep(alignment, size); - - /// - /// Adds an inline struct field whose data was just written by - /// and the Write methods. - /// - /// The id of the field. - internal void AddStruct(int field) => Slot(field); - - internal void WriteFloat(float value) => WriteInt32(BitConverter.SingleToInt32Bits(value)); - - internal void WriteUInt8(byte value) => WriteByte(value); - - internal int CreateString(string value) - { - var bytes = Encoding.UTF8.GetBytes(value); - - // Strings are null terminated for the convenience of C++ readers. - Prep(sizeof(int), bytes.Length + 1); - WriteByte(0); - EnsureSpace(bytes.Length); - _used += bytes.Length; - Array.Copy(bytes, 0, _buffer, _buffer.Length - _used, bytes.Length); - WriteInt32(bytes.Length); - return _used; - } - - internal int CreateByteVector(IReadOnlyList values) - { - StartVector(sizeof(byte), values.Count, sizeof(byte)); - for (var i = values.Count - 1; i >= 0; i--) - { - WriteByte(values[i]); - } - - return EndVector(values.Count); - } - - internal int CreateFloatVector(IReadOnlyList values) - { - StartVector(sizeof(float), values.Count, sizeof(float)); - for (var i = values.Count - 1; i >= 0; i--) - { - WriteInt32(BitConverter.SingleToInt32Bits(values[i])); - } - - return EndVector(values.Count); - } - - internal int CreateUInt32Vector(IReadOnlyList values) - { - StartVector(sizeof(uint), values.Count, sizeof(uint)); - for (var i = values.Count - 1; i >= 0; i--) - { - WriteInt32(unchecked((int)values[i])); - } - - return EndVector(values.Count); - } - - /// - /// Creates a vector of offsets to previously created tables or strings. - /// - /// The offsets of the referenced data. - /// The offset of the vector. - internal int CreateOffsetVector(IReadOnlyList offsets) - { - StartVector(sizeof(int), offsets.Count, sizeof(int)); - for (var i = offsets.Count - 1; i >= 0; i--) - { - Prep(sizeof(int), 0); - WriteInt32(_used + sizeof(int) - offsets[i]); - } - - return EndVector(offsets.Count); - } - - /// - /// Completes the buffer, making its root. - /// - /// The offset of the root table. - /// The 4 character file identifier. - /// The completed buffer. - internal byte[] Finish(int rootTable, string fileIdentifier) - { - if (fileIdentifier is null || fileIdentifier.Length != 4) - { - throw new ArgumentException("File identifier must be 4 characters.", nameof(fileIdentifier)); - } - - Prep(_minAlign, sizeof(int) + 4); - for (var i = 3; i >= 0; i--) - { - WriteByte((byte)fileIdentifier[i]); - } - - WriteInt32(_used + sizeof(int) - rootTable); - - var result = new byte[_used]; - Array.Copy(_buffer, _buffer.Length - _used, result, 0, _used); - return result; - } - - void StartVector(int elementSize, int count, int alignment) - { - Prep(sizeof(int), elementSize * count); - Prep(alignment, elementSize * count); - } - - int EndVector(int count) - { - WriteInt32(count); - return _used; - } - - // Records the offset of the field that was just written. - void Slot(int field) - { - if (field >= _currentVTable.Count) - { - throw new ArgumentOutOfRangeException(nameof(field)); - } - - _currentVTable[field] = _used; - } - - // Pads so that, after writing additionalBytes more bytes, the write - // position will be aligned to the given alignment. - void Prep(int alignment, int additionalBytes) - { - if (alignment > _minAlign) - { - _minAlign = alignment; - } - - var alignSize = ((~(_used + additionalBytes)) + 1) & (alignment - 1); - EnsureSpace(alignSize + additionalBytes); - for (var i = 0; i < alignSize; i++) - { - WriteByte(0); - } - } - - int FindMatchingVTable(int vtableOffset, int vtableSize) - { - foreach (var candidate in _vtables) - { - if (ReadInt16At(candidate) != vtableSize) - { - continue; - } - - var match = true; - for (var i = sizeof(short); i < vtableSize; i += sizeof(short)) - { - if (ReadInt16At(candidate - i) != ReadInt16At(vtableOffset - i)) - { - match = false; - break; - } - } - - if (match) - { - return candidate; - } - } - - return -1; - } - - void EnsureSpace(int size) - { - if (_used + size <= _buffer.Length) - { - return; - } - - var newSize = _buffer.Length; - while (newSize < _used + size) - { - newSize *= 2; - } - - var newBuffer = new byte[newSize]; - Array.Copy(_buffer, _buffer.Length - _used, newBuffer, newSize - _used, _used); - _buffer = newBuffer; - } - - void WriteByte(byte value) - { - EnsureSpace(sizeof(byte)); - _used += sizeof(byte); - _buffer[_buffer.Length - _used] = value; - } - - void WriteInt16(short value) - { - EnsureSpace(sizeof(short)); - _used += sizeof(short); - var index = _buffer.Length - _used; - _buffer[index] = unchecked((byte)value); - _buffer[index + 1] = unchecked((byte)(value >> 8)); - } - - void WriteInt32(int value) - { - EnsureSpace(sizeof(int)); - _used += sizeof(int); - WriteInt32At(_used, value); - } - - void WriteInt64(long value) - { - // The buffer is written backwards, so the high word is written first. - WriteInt32(unchecked((int)(value >> 32))); - WriteInt32(unchecked((int)value)); - } - - void WriteInt32At(int offset, int value) - { - var index = _buffer.Length - offset; - _buffer[index] = unchecked((byte)value); - _buffer[index + 1] = unchecked((byte)(value >> 8)); - _buffer[index + 2] = unchecked((byte)(value >> 16)); - _buffer[index + 3] = unchecked((byte)(value >> 24)); - } - - short ReadInt16At(int offset) - { - var index = _buffer.Length - offset; - return unchecked((short)(_buffer[index] | (_buffer[index + 1] << 8))); - } - } -} diff --git a/source/CompDataFlatbuffer/Format.cs b/source/CompDataFlatbuffer/Format.cs new file mode 100644 index 00000000..b019590c --- /dev/null +++ b/source/CompDataFlatbuffer/Format.cs @@ -0,0 +1,79 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +namespace CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer +{ + /// + /// Constants and helpers that describe the lottie_comp.fbs wire format but + /// which are not expressible in the schema itself, and so are not produced + /// by the FlatBuffers compiler. + /// +#if PUBLIC_CompDataFlatbuffer + public +#endif + static class Format + { + /// + /// The version of the schema that this code reads and writes. This is stored in + /// the schema_version field of every buffer. It is incremented for every change + /// to lottie_comp.fbs. Only additive changes are permitted, so a reader can load + /// any buffer whose schema_version is less than or equal to its own. + /// + public const ushort Version = 1; + + /// + /// The 4 character identifier stored at offset 4 of every buffer. This is the + /// file_identifier declared in lottie_comp.fbs. + /// + public const string FileIdentifier = "LCMP"; + + /// + /// The file extension used for serialized compositions. This is the + /// file_extension declared in lottie_comp.fbs. + /// + public const string FileExtension = ".lcomp"; + + /// + /// The value of an index or object reference field that refers to nothing. + /// + public const uint NullIndex = 0xFFFFFFFF; + + /// + /// The number of low bits of an object reference that store the index. The + /// remaining high bits store the , i.e. which + /// of the root table's node vectors the index refers to. + /// + const int ObjectReferenceIndexBits = 28; + + const uint ObjectReferenceIndexMask = (1u << ObjectReferenceIndexBits) - 1; + + /// + /// Combines a category and an index into the single uint used by object + /// reference fields. + /// + /// The vector that indexes. + /// The index of the node within that vector. + /// The packed object reference. + public static uint PackObjectReference(Schema.ObjectCategory category, int index) + => ((uint)category << ObjectReferenceIndexBits) | ((uint)index & ObjectReferenceIndexMask); + + /// + /// Returns the category part of an object reference. + /// + /// A packed object reference. + /// The category of the referenced node. + public static Schema.ObjectCategory UnpackCategory(uint reference) + => (Schema.ObjectCategory)(reference >> ObjectReferenceIndexBits); + + /// + /// Returns the index part of an object reference. + /// + /// A packed object reference. + /// The index of the referenced node within its category's vector. + public static int UnpackIndex(uint reference) + => (int)(reference & ObjectReferenceIndexMask); + } +} diff --git a/source/CompDataFlatbuffer/Generated/lottie_comp_generated.cs b/source/CompDataFlatbuffer/Generated/lottie_comp_generated.cs new file mode 100644 index 00000000..1c33450d --- /dev/null +++ b/source/CompDataFlatbuffer/Generated/lottie_comp_generated.cs @@ -0,0 +1,2051 @@ +// +// automatically generated by the FlatBuffers compiler, do not modify +// + +namespace CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema +{ + +using global::System; +using global::System.Collections.Generic; +using global::Google.FlatBuffers; + +public enum ObjectCategory : byte +{ + Visual = 0, + Shape = 1, + Geometry = 2, + Brush = 3, + Animation = 4, + Easing = 5, + PropertySet = 6, + Surface = 7, + Clip = 8, + Controller = 9, + Shadow = 10, + Effect = 11, + GradientStop = 12, + ViewBox = 13, +}; + +public enum VisualKind : byte +{ + Container = 0, + Sprite = 1, + Shape = 2, + Layer = 3, +}; + +public enum ShapeKind : byte +{ + Container = 0, + Sprite = 1, +}; + +public enum GeometryKind : byte +{ + Path = 0, + Rectangle = 1, + RoundedRectangle = 2, + Ellipse = 3, +}; + +public enum BrushKind : byte +{ + Color = 0, + LinearGradient = 1, + RadialGradient = 2, + Surface = 3, + Mask = 4, + Effect = 5, +}; + +public enum ClipKind : byte +{ + Inset = 0, + Geometric = 1, +}; + +public enum ShadowKind : byte +{ + Drop = 0, +}; + +public enum SurfaceKind : byte +{ + VisualSurface = 0, + LoadedImageFromUri = 1, + LoadedImageFromStream = 2, +}; + +public enum EffectKind : byte +{ + Composite = 0, + GaussianBlur = 1, +}; + +public enum EasingKind : byte +{ + Linear = 0, + CubicBezier = 1, + Step = 2, +}; + +public enum AnimationKind : byte +{ + Scalar = 0, + Vector2 = 1, + Vector3 = 2, + Vector4 = 3, + Color = 4, + Boolean = 5, + Path = 6, + Expression = 7, +}; + +public enum KeyFrameKind : byte +{ + Value = 0, + Expression = 1, +}; + +public enum CanvasGeometryKind : byte +{ + Combination = 0, + Ellipse = 1, + Group = 2, + Path = 3, + RoundedRectangle = 4, + TransformedGeometry = 5, +}; + +public enum PathOp : byte +{ + BeginFigure = 0, + EndFigure = 1, + AddLine = 2, + AddCubicBezier = 3, +}; + +public enum PropertyValueType : byte +{ + None = 0, + Color = 1, + Scalar = 2, + Vector2 = 3, + Vector3 = 4, + Vector4 = 5, +}; + +public struct Vec2 : IFlatbufferObject +{ + private Struct __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public void __init(int _i, ByteBuffer _bb) { __p = new Struct(_i, _bb); } + public Vec2 __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public float X { get { return __p.bb.GetFloat(__p.bb_pos + 0); } } + public float Y { get { return __p.bb.GetFloat(__p.bb_pos + 4); } } + + public static Offset CreateVec2(FlatBufferBuilder builder, float X, float Y) { + builder.Prep(4, 8); + builder.PutFloat(Y); + builder.PutFloat(X); + return new Offset(builder.Offset); + } +} + +public struct Vec3 : IFlatbufferObject +{ + private Struct __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public void __init(int _i, ByteBuffer _bb) { __p = new Struct(_i, _bb); } + public Vec3 __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public float X { get { return __p.bb.GetFloat(__p.bb_pos + 0); } } + public float Y { get { return __p.bb.GetFloat(__p.bb_pos + 4); } } + public float Z { get { return __p.bb.GetFloat(__p.bb_pos + 8); } } + + public static Offset CreateVec3(FlatBufferBuilder builder, float X, float Y, float Z) { + builder.Prep(4, 12); + builder.PutFloat(Z); + builder.PutFloat(Y); + builder.PutFloat(X); + return new Offset(builder.Offset); + } +} + +public struct Vec4 : IFlatbufferObject +{ + private Struct __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public void __init(int _i, ByteBuffer _bb) { __p = new Struct(_i, _bb); } + public Vec4 __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public float X { get { return __p.bb.GetFloat(__p.bb_pos + 0); } } + public float Y { get { return __p.bb.GetFloat(__p.bb_pos + 4); } } + public float Z { get { return __p.bb.GetFloat(__p.bb_pos + 8); } } + public float W { get { return __p.bb.GetFloat(__p.bb_pos + 12); } } + + public static Offset CreateVec4(FlatBufferBuilder builder, float X, float Y, float Z, float W) { + builder.Prep(4, 16); + builder.PutFloat(W); + builder.PutFloat(Z); + builder.PutFloat(Y); + builder.PutFloat(X); + return new Offset(builder.Offset); + } +} + +public struct Mat3x2 : IFlatbufferObject +{ + private Struct __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public void __init(int _i, ByteBuffer _bb) { __p = new Struct(_i, _bb); } + public Mat3x2 __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public float M11 { get { return __p.bb.GetFloat(__p.bb_pos + 0); } } + public float M12 { get { return __p.bb.GetFloat(__p.bb_pos + 4); } } + public float M21 { get { return __p.bb.GetFloat(__p.bb_pos + 8); } } + public float M22 { get { return __p.bb.GetFloat(__p.bb_pos + 12); } } + public float M31 { get { return __p.bb.GetFloat(__p.bb_pos + 16); } } + public float M32 { get { return __p.bb.GetFloat(__p.bb_pos + 20); } } + + public static Offset CreateMat3x2(FlatBufferBuilder builder, float M11, float M12, float M21, float M22, float M31, float M32) { + builder.Prep(4, 24); + builder.PutFloat(M32); + builder.PutFloat(M31); + builder.PutFloat(M22); + builder.PutFloat(M21); + builder.PutFloat(M12); + builder.PutFloat(M11); + return new Offset(builder.Offset); + } +} + +public struct Mat4x4 : IFlatbufferObject +{ + private Struct __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public void __init(int _i, ByteBuffer _bb) { __p = new Struct(_i, _bb); } + public Mat4x4 __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public float M11 { get { return __p.bb.GetFloat(__p.bb_pos + 0); } } + public float M12 { get { return __p.bb.GetFloat(__p.bb_pos + 4); } } + public float M13 { get { return __p.bb.GetFloat(__p.bb_pos + 8); } } + public float M14 { get { return __p.bb.GetFloat(__p.bb_pos + 12); } } + public float M21 { get { return __p.bb.GetFloat(__p.bb_pos + 16); } } + public float M22 { get { return __p.bb.GetFloat(__p.bb_pos + 20); } } + public float M23 { get { return __p.bb.GetFloat(__p.bb_pos + 24); } } + public float M24 { get { return __p.bb.GetFloat(__p.bb_pos + 28); } } + public float M31 { get { return __p.bb.GetFloat(__p.bb_pos + 32); } } + public float M32 { get { return __p.bb.GetFloat(__p.bb_pos + 36); } } + public float M33 { get { return __p.bb.GetFloat(__p.bb_pos + 40); } } + public float M34 { get { return __p.bb.GetFloat(__p.bb_pos + 44); } } + public float M41 { get { return __p.bb.GetFloat(__p.bb_pos + 48); } } + public float M42 { get { return __p.bb.GetFloat(__p.bb_pos + 52); } } + public float M43 { get { return __p.bb.GetFloat(__p.bb_pos + 56); } } + public float M44 { get { return __p.bb.GetFloat(__p.bb_pos + 60); } } + + public static Offset CreateMat4x4(FlatBufferBuilder builder, float M11, float M12, float M13, float M14, float M21, float M22, float M23, float M24, float M31, float M32, float M33, float M34, float M41, float M42, float M43, float M44) { + builder.Prep(4, 64); + builder.PutFloat(M44); + builder.PutFloat(M43); + builder.PutFloat(M42); + builder.PutFloat(M41); + builder.PutFloat(M34); + builder.PutFloat(M33); + builder.PutFloat(M32); + builder.PutFloat(M31); + builder.PutFloat(M24); + builder.PutFloat(M23); + builder.PutFloat(M22); + builder.PutFloat(M21); + builder.PutFloat(M14); + builder.PutFloat(M13); + builder.PutFloat(M12); + builder.PutFloat(M11); + return new Offset(builder.Offset); + } +} + +public struct Color : IFlatbufferObject +{ + private Struct __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public void __init(int _i, ByteBuffer _bb) { __p = new Struct(_i, _bb); } + public Color __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public byte A { get { return __p.bb.Get(__p.bb_pos + 0); } } + public byte R { get { return __p.bb.Get(__p.bb_pos + 1); } } + public byte G { get { return __p.bb.Get(__p.bb_pos + 2); } } + public byte B { get { return __p.bb.Get(__p.bb_pos + 3); } } + + public static Offset CreateColor(FlatBufferBuilder builder, byte A, byte R, byte G, byte B) { + builder.Prep(1, 4); + builder.PutByte(B); + builder.PutByte(G); + builder.PutByte(R); + builder.PutByte(A); + return new Offset(builder.Offset); + } +} + +public struct Animator : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static Animator GetRootAsAnimator(ByteBuffer _bb) { return GetRootAsAnimator(_bb, new Animator()); } + public static Animator GetRootAsAnimator(ByteBuffer _bb, Animator obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public Animator __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public uint Property { get { int o = __p.__offset(4); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)0; } } + public uint Animation { get { int o = __p.__offset(6); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)0; } } + public uint Controller { get { int o = __p.__offset(8); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + + public static Offset CreateAnimator(FlatBufferBuilder builder, + uint property = 0, + uint animation = 0, + uint controller = 4294967295) { + builder.StartTable(3); + Animator.AddController(builder, controller); + Animator.AddAnimation(builder, animation); + Animator.AddProperty(builder, property); + return Animator.EndAnimator(builder); + } + + public static void StartAnimator(FlatBufferBuilder builder) { builder.StartTable(3); } + public static void AddProperty(FlatBufferBuilder builder, uint property) { builder.AddUint(0, property, 0); } + public static void AddAnimation(FlatBufferBuilder builder, uint animation) { builder.AddUint(1, animation, 0); } + public static void AddController(FlatBufferBuilder builder, uint controller) { builder.AddUint(2, controller, 4294967295); } + public static Offset EndAnimator(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class AnimatorVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyField(tablePos, 4 /*Property*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 6 /*Animation*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 8 /*Controller*/, 4 /*uint*/, 4, false) + && verifier.VerifyTableEnd(tablePos); + } +} +public struct CompObj : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static CompObj GetRootAsCompObj(ByteBuffer _bb) { return GetRootAsCompObj(_bb, new CompObj()); } + public static CompObj GetRootAsCompObj(ByteBuffer _bb, CompObj obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public CompObj __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public uint Comment { get { int o = __p.__offset(4); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Animator? Animators(int j) { int o = __p.__offset(6); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Animator?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Animator()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } + public int AnimatorsLength { get { int o = __p.__offset(6); return o != 0 ? __p.__vector_len(o) : 0; } } + public uint Properties { get { int o = __p.__offset(8); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + + public static Offset CreateCompObj(FlatBufferBuilder builder, + uint comment = 4294967295, + VectorOffset animatorsOffset = default(VectorOffset), + uint properties = 4294967295) { + builder.StartTable(3); + CompObj.AddProperties(builder, properties); + CompObj.AddAnimators(builder, animatorsOffset); + CompObj.AddComment(builder, comment); + return CompObj.EndCompObj(builder); + } + + public static void StartCompObj(FlatBufferBuilder builder) { builder.StartTable(3); } + public static void AddComment(FlatBufferBuilder builder, uint comment) { builder.AddUint(0, comment, 4294967295); } + public static void AddAnimators(FlatBufferBuilder builder, VectorOffset animatorsOffset) { builder.AddOffset(1, animatorsOffset.Value, 0); } + public static VectorOffset CreateAnimatorsVector(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreateAnimatorsVectorBlock(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateAnimatorsVectorBlock(FlatBufferBuilder builder, ArraySegment> data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateAnimatorsVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add>(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartAnimatorsVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddProperties(FlatBufferBuilder builder, uint properties) { builder.AddUint(2, properties, 4294967295); } + public static Offset EndCompObj(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class CompObjVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyField(tablePos, 4 /*Comment*/, 4 /*uint*/, 4, false) + && verifier.VerifyVectorOfTables(tablePos, 6 /*Animators*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.AnimatorVerify.Verify, false) + && verifier.VerifyField(tablePos, 8 /*Properties*/, 4 /*uint*/, 4, false) + && verifier.VerifyTableEnd(tablePos); + } +} +public struct PropertyValue : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static PropertyValue GetRootAsPropertyValue(ByteBuffer _bb) { return GetRootAsPropertyValue(_bb, new PropertyValue()); } + public static PropertyValue GetRootAsPropertyValue(ByteBuffer _bb, PropertyValue obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public PropertyValue __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public uint Name { get { int o = __p.__offset(4); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)0; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertyValueType Type { get { int o = __p.__offset(6); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertyValueType)__p.bb.Get(o + __p.bb_pos) : CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertyValueType.None; } } + public float Scalar { get { int o = __p.__offset(8); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)0.0f; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec4? Vector { get { int o = __p.__offset(10); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec4?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec4()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Color? Color { get { int o = __p.__offset(12); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Color?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Color()).__assign(o + __p.bb_pos, __p.bb) : null; } } + + public static void StartPropertyValue(FlatBufferBuilder builder) { builder.StartTable(5); } + public static void AddName(FlatBufferBuilder builder, uint name) { builder.AddUint(0, name, 0); } + public static void AddType(FlatBufferBuilder builder, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertyValueType type) { builder.AddByte(1, (byte)type, 0); } + public static void AddScalar(FlatBufferBuilder builder, float scalar) { builder.AddFloat(2, scalar, 0.0f); } + public static void AddVector(FlatBufferBuilder builder, Offset vectorOffset) { builder.AddStruct(3, vectorOffset.Value, 0); } + public static void AddColor(FlatBufferBuilder builder, Offset colorOffset) { builder.AddStruct(4, colorOffset.Value, 0); } + public static Offset EndPropertyValue(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class PropertyValueVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyField(tablePos, 4 /*Name*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 6 /*Type*/, 1 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertyValueType*/, 1, false) + && verifier.VerifyField(tablePos, 8 /*Scalar*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 10 /*Vector*/, 16 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec4*/, 4, false) + && verifier.VerifyField(tablePos, 12 /*Color*/, 4 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Color*/, 1, false) + && verifier.VerifyTableEnd(tablePos); + } +} +public struct ReferenceParameter : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static ReferenceParameter GetRootAsReferenceParameter(ByteBuffer _bb) { return GetRootAsReferenceParameter(_bb, new ReferenceParameter()); } + public static ReferenceParameter GetRootAsReferenceParameter(ByteBuffer _bb, ReferenceParameter obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public ReferenceParameter __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public uint Name { get { int o = __p.__offset(4); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)0; } } + public uint Target { get { int o = __p.__offset(6); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)0; } } + + public static Offset CreateReferenceParameter(FlatBufferBuilder builder, + uint name = 0, + uint target = 0) { + builder.StartTable(2); + ReferenceParameter.AddTarget(builder, target); + ReferenceParameter.AddName(builder, name); + return ReferenceParameter.EndReferenceParameter(builder); + } + + public static void StartReferenceParameter(FlatBufferBuilder builder) { builder.StartTable(2); } + public static void AddName(FlatBufferBuilder builder, uint name) { builder.AddUint(0, name, 0); } + public static void AddTarget(FlatBufferBuilder builder, uint target) { builder.AddUint(1, target, 0); } + public static Offset EndReferenceParameter(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class ReferenceParameterVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyField(tablePos, 4 /*Name*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 6 /*Target*/, 4 /*uint*/, 4, false) + && verifier.VerifyTableEnd(tablePos); + } +} +public struct SourceParameter : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static SourceParameter GetRootAsSourceParameter(ByteBuffer _bb) { return GetRootAsSourceParameter(_bb, new SourceParameter()); } + public static SourceParameter GetRootAsSourceParameter(ByteBuffer _bb, SourceParameter obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public SourceParameter __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public uint Name { get { int o = __p.__offset(4); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)0; } } + public uint Brush { get { int o = __p.__offset(6); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)0; } } + + public static Offset CreateSourceParameter(FlatBufferBuilder builder, + uint name = 0, + uint brush = 0) { + builder.StartTable(2); + SourceParameter.AddBrush(builder, brush); + SourceParameter.AddName(builder, name); + return SourceParameter.EndSourceParameter(builder); + } + + public static void StartSourceParameter(FlatBufferBuilder builder) { builder.StartTable(2); } + public static void AddName(FlatBufferBuilder builder, uint name) { builder.AddUint(0, name, 0); } + public static void AddBrush(FlatBufferBuilder builder, uint brush) { builder.AddUint(1, brush, 0); } + public static Offset EndSourceParameter(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class SourceParameterVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyField(tablePos, 4 /*Name*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 6 /*Brush*/, 4 /*uint*/, 4, false) + && verifier.VerifyTableEnd(tablePos); + } +} +public struct KeyFrame : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static KeyFrame GetRootAsKeyFrame(ByteBuffer _bb) { return GetRootAsKeyFrame(_bb, new KeyFrame()); } + public static KeyFrame GetRootAsKeyFrame(ByteBuffer _bb, KeyFrame obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public KeyFrame __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public float Progress { get { int o = __p.__offset(4); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)0.0f; } } + public uint Easing { get { int o = __p.__offset(6); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.KeyFrameKind Kind { get { int o = __p.__offset(8); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.KeyFrameKind)__p.bb.Get(o + __p.bb_pos) : CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.KeyFrameKind.Value; } } + public uint Expression { get { int o = __p.__offset(10); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public float Scalar { get { int o = __p.__offset(12); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)0.0f; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec4? Vector { get { int o = __p.__offset(14); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec4?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec4()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Color? Color { get { int o = __p.__offset(16); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Color?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Color()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public uint Path { get { int o = __p.__offset(18); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + + public static void StartKeyFrame(FlatBufferBuilder builder) { builder.StartTable(8); } + public static void AddProgress(FlatBufferBuilder builder, float progress) { builder.AddFloat(0, progress, 0.0f); } + public static void AddEasing(FlatBufferBuilder builder, uint easing) { builder.AddUint(1, easing, 4294967295); } + public static void AddKind(FlatBufferBuilder builder, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.KeyFrameKind kind) { builder.AddByte(2, (byte)kind, 0); } + public static void AddExpression(FlatBufferBuilder builder, uint expression) { builder.AddUint(3, expression, 4294967295); } + public static void AddScalar(FlatBufferBuilder builder, float scalar) { builder.AddFloat(4, scalar, 0.0f); } + public static void AddVector(FlatBufferBuilder builder, Offset vectorOffset) { builder.AddStruct(5, vectorOffset.Value, 0); } + public static void AddColor(FlatBufferBuilder builder, Offset colorOffset) { builder.AddStruct(6, colorOffset.Value, 0); } + public static void AddPath(FlatBufferBuilder builder, uint path) { builder.AddUint(7, path, 4294967295); } + public static Offset EndKeyFrame(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class KeyFrameVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyField(tablePos, 4 /*Progress*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 6 /*Easing*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 8 /*Kind*/, 1 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.KeyFrameKind*/, 1, false) + && verifier.VerifyField(tablePos, 10 /*Expression*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 12 /*Scalar*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 14 /*Vector*/, 16 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec4*/, 4, false) + && verifier.VerifyField(tablePos, 16 /*Color*/, 4 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Color*/, 1, false) + && verifier.VerifyField(tablePos, 18 /*Path*/, 4 /*uint*/, 4, false) + && verifier.VerifyTableEnd(tablePos); + } +} +public struct Visual : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static Visual GetRootAsVisual(ByteBuffer _bb) { return GetRootAsVisual(_bb, new Visual()); } + public static Visual GetRootAsVisual(ByteBuffer _bb, Visual obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public Visual __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj? Base { get { int o = __p.__offset(4); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj()).__assign(__p.__indirect(o + __p.bb_pos), __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.VisualKind Kind { get { int o = __p.__offset(6); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.VisualKind)__p.bb.Get(o + __p.bb_pos) : CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.VisualKind.Container; } } + public byte? BorderMode { get { int o = __p.__offset(8); return o != 0 ? __p.bb.Get(o + __p.bb_pos) : (byte?)null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec3? CenterPoint { get { int o = __p.__offset(10); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec3?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec3()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public uint Clip { get { int o = __p.__offset(12); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public bool? IsVisible { get { int o = __p.__offset(14); return o != 0 ? 0!=__p.bb.Get(o + __p.bb_pos) : (bool?)null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec3? Offset { get { int o = __p.__offset(16); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec3?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec3()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public float? Opacity { get { int o = __p.__offset(18); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float?)null; } } + public float? RotationAngleInDegrees { get { int o = __p.__offset(20); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float?)null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec3? RotationAxis { get { int o = __p.__offset(22); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec3?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec3()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec3? Scale { get { int o = __p.__offset(24); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec3?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec3()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2? Size { get { int o = __p.__offset(26); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Mat4x4? TransformMatrix { get { int o = __p.__offset(28); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Mat4x4?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Mat4x4()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public uint Children(int j) { int o = __p.__offset(30); return o != 0 ? __p.bb.GetUint(__p.__vector(o) + j * 4) : (uint)0; } + public int ChildrenLength { get { int o = __p.__offset(30); return o != 0 ? __p.__vector_len(o) : 0; } } +#if ENABLE_SPAN_T + public Span GetChildrenBytes() { return __p.__vector_as_span(30, 4); } +#else + public ArraySegment? GetChildrenBytes() { return __p.__vector_as_arraysegment(30); } +#endif + public uint[] GetChildrenArray() { return __p.__vector_as_array(30); } + public uint Shapes(int j) { int o = __p.__offset(32); return o != 0 ? __p.bb.GetUint(__p.__vector(o) + j * 4) : (uint)0; } + public int ShapesLength { get { int o = __p.__offset(32); return o != 0 ? __p.__vector_len(o) : 0; } } +#if ENABLE_SPAN_T + public Span GetShapesBytes() { return __p.__vector_as_span(32, 4); } +#else + public ArraySegment? GetShapesBytes() { return __p.__vector_as_arraysegment(32); } +#endif + public uint[] GetShapesArray() { return __p.__vector_as_array(32); } + public uint ViewBox { get { int o = __p.__offset(34); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public uint Brush { get { int o = __p.__offset(36); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public uint Shadow { get { int o = __p.__offset(38); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + + public static void StartVisual(FlatBufferBuilder builder) { builder.StartTable(18); } + public static void AddBase(FlatBufferBuilder builder, Offset baseOffset) { builder.AddOffset(0, baseOffset.Value, 0); } + public static void AddKind(FlatBufferBuilder builder, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.VisualKind kind) { builder.AddByte(1, (byte)kind, 0); } + public static void AddBorderMode(FlatBufferBuilder builder, byte? borderMode) { builder.AddByte(2, borderMode); } + public static void AddCenterPoint(FlatBufferBuilder builder, Offset centerPointOffset) { builder.AddStruct(3, centerPointOffset.Value, 0); } + public static void AddClip(FlatBufferBuilder builder, uint clip) { builder.AddUint(4, clip, 4294967295); } + public static void AddIsVisible(FlatBufferBuilder builder, bool? isVisible) { builder.AddBool(5, isVisible); } + public static void AddOffset(FlatBufferBuilder builder, Offset offsetOffset) { builder.AddStruct(6, offsetOffset.Value, 0); } + public static void AddOpacity(FlatBufferBuilder builder, float? opacity) { builder.AddFloat(7, opacity); } + public static void AddRotationAngleInDegrees(FlatBufferBuilder builder, float? rotationAngleInDegrees) { builder.AddFloat(8, rotationAngleInDegrees); } + public static void AddRotationAxis(FlatBufferBuilder builder, Offset rotationAxisOffset) { builder.AddStruct(9, rotationAxisOffset.Value, 0); } + public static void AddScale(FlatBufferBuilder builder, Offset scaleOffset) { builder.AddStruct(10, scaleOffset.Value, 0); } + public static void AddSize(FlatBufferBuilder builder, Offset sizeOffset) { builder.AddStruct(11, sizeOffset.Value, 0); } + public static void AddTransformMatrix(FlatBufferBuilder builder, Offset transformMatrixOffset) { builder.AddStruct(12, transformMatrixOffset.Value, 0); } + public static void AddChildren(FlatBufferBuilder builder, VectorOffset childrenOffset) { builder.AddOffset(13, childrenOffset.Value, 0); } + public static VectorOffset CreateChildrenVector(FlatBufferBuilder builder, uint[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddUint(data[i]); return builder.EndVector(); } + public static VectorOffset CreateChildrenVectorBlock(FlatBufferBuilder builder, uint[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateChildrenVectorBlock(FlatBufferBuilder builder, ArraySegment data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateChildrenVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartChildrenVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddShapes(FlatBufferBuilder builder, VectorOffset shapesOffset) { builder.AddOffset(14, shapesOffset.Value, 0); } + public static VectorOffset CreateShapesVector(FlatBufferBuilder builder, uint[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddUint(data[i]); return builder.EndVector(); } + public static VectorOffset CreateShapesVectorBlock(FlatBufferBuilder builder, uint[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateShapesVectorBlock(FlatBufferBuilder builder, ArraySegment data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateShapesVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartShapesVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddViewBox(FlatBufferBuilder builder, uint viewBox) { builder.AddUint(15, viewBox, 4294967295); } + public static void AddBrush(FlatBufferBuilder builder, uint brush) { builder.AddUint(16, brush, 4294967295); } + public static void AddShadow(FlatBufferBuilder builder, uint shadow) { builder.AddUint(17, shadow, 4294967295); } + public static Offset EndVisual(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class VisualVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyTable(tablePos, 4 /*Base*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObjVerify.Verify, false) + && verifier.VerifyField(tablePos, 6 /*Kind*/, 1 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.VisualKind*/, 1, false) + && verifier.VerifyField(tablePos, 8 /*BorderMode*/, 1 /*byte*/, 1, false) + && verifier.VerifyField(tablePos, 10 /*CenterPoint*/, 12 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec3*/, 4, false) + && verifier.VerifyField(tablePos, 12 /*Clip*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 14 /*IsVisible*/, 1 /*bool*/, 1, false) + && verifier.VerifyField(tablePos, 16 /*Offset*/, 12 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec3*/, 4, false) + && verifier.VerifyField(tablePos, 18 /*Opacity*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 20 /*RotationAngleInDegrees*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 22 /*RotationAxis*/, 12 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec3*/, 4, false) + && verifier.VerifyField(tablePos, 24 /*Scale*/, 12 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec3*/, 4, false) + && verifier.VerifyField(tablePos, 26 /*Size*/, 8 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2*/, 4, false) + && verifier.VerifyField(tablePos, 28 /*TransformMatrix*/, 64 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Mat4x4*/, 4, false) + && verifier.VerifyVectorOfData(tablePos, 30 /*Children*/, 4 /*uint*/, false) + && verifier.VerifyVectorOfData(tablePos, 32 /*Shapes*/, 4 /*uint*/, false) + && verifier.VerifyField(tablePos, 34 /*ViewBox*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 36 /*Brush*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 38 /*Shadow*/, 4 /*uint*/, 4, false) + && verifier.VerifyTableEnd(tablePos); + } +} +public struct Shape : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static Shape GetRootAsShape(ByteBuffer _bb) { return GetRootAsShape(_bb, new Shape()); } + public static Shape GetRootAsShape(ByteBuffer _bb, Shape obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public Shape __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj? Base { get { int o = __p.__offset(4); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj()).__assign(__p.__indirect(o + __p.bb_pos), __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ShapeKind Kind { get { int o = __p.__offset(6); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ShapeKind)__p.bb.Get(o + __p.bb_pos) : CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ShapeKind.Container; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2? CenterPoint { get { int o = __p.__offset(8); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2? Offset { get { int o = __p.__offset(10); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public float? RotationAngleInDegrees { get { int o = __p.__offset(12); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float?)null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2? Scale { get { int o = __p.__offset(14); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Mat3x2? TransformMatrix { get { int o = __p.__offset(16); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Mat3x2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Mat3x2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public uint Shapes(int j) { int o = __p.__offset(18); return o != 0 ? __p.bb.GetUint(__p.__vector(o) + j * 4) : (uint)0; } + public int ShapesLength { get { int o = __p.__offset(18); return o != 0 ? __p.__vector_len(o) : 0; } } +#if ENABLE_SPAN_T + public Span GetShapesBytes() { return __p.__vector_as_span(18, 4); } +#else + public ArraySegment? GetShapesBytes() { return __p.__vector_as_arraysegment(18); } +#endif + public uint[] GetShapesArray() { return __p.__vector_as_array(18); } + public uint FillBrush { get { int o = __p.__offset(20); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public uint StrokeBrush { get { int o = __p.__offset(22); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public uint Geometry { get { int o = __p.__offset(24); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public bool? IsStrokeNonScaling { get { int o = __p.__offset(26); return o != 0 ? 0!=__p.bb.Get(o + __p.bb_pos) : (bool?)null; } } + public float? StrokeDashOffset { get { int o = __p.__offset(28); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float?)null; } } + public float StrokeDashArray(int j) { int o = __p.__offset(30); return o != 0 ? __p.bb.GetFloat(__p.__vector(o) + j * 4) : (float)0; } + public int StrokeDashArrayLength { get { int o = __p.__offset(30); return o != 0 ? __p.__vector_len(o) : 0; } } +#if ENABLE_SPAN_T + public Span GetStrokeDashArrayBytes() { return __p.__vector_as_span(30, 4); } +#else + public ArraySegment? GetStrokeDashArrayBytes() { return __p.__vector_as_arraysegment(30); } +#endif + public float[] GetStrokeDashArrayArray() { return __p.__vector_as_array(30); } + public byte? StrokeDashCap { get { int o = __p.__offset(32); return o != 0 ? __p.bb.Get(o + __p.bb_pos) : (byte?)null; } } + public byte? StrokeStartCap { get { int o = __p.__offset(34); return o != 0 ? __p.bb.Get(o + __p.bb_pos) : (byte?)null; } } + public byte? StrokeEndCap { get { int o = __p.__offset(36); return o != 0 ? __p.bb.Get(o + __p.bb_pos) : (byte?)null; } } + public byte? StrokeLineJoin { get { int o = __p.__offset(38); return o != 0 ? __p.bb.Get(o + __p.bb_pos) : (byte?)null; } } + public float? StrokeMiterLimit { get { int o = __p.__offset(40); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float?)null; } } + public float? StrokeThickness { get { int o = __p.__offset(42); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float?)null; } } + + public static void StartShape(FlatBufferBuilder builder) { builder.StartTable(20); } + public static void AddBase(FlatBufferBuilder builder, Offset baseOffset) { builder.AddOffset(0, baseOffset.Value, 0); } + public static void AddKind(FlatBufferBuilder builder, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ShapeKind kind) { builder.AddByte(1, (byte)kind, 0); } + public static void AddCenterPoint(FlatBufferBuilder builder, Offset centerPointOffset) { builder.AddStruct(2, centerPointOffset.Value, 0); } + public static void AddOffset(FlatBufferBuilder builder, Offset offsetOffset) { builder.AddStruct(3, offsetOffset.Value, 0); } + public static void AddRotationAngleInDegrees(FlatBufferBuilder builder, float? rotationAngleInDegrees) { builder.AddFloat(4, rotationAngleInDegrees); } + public static void AddScale(FlatBufferBuilder builder, Offset scaleOffset) { builder.AddStruct(5, scaleOffset.Value, 0); } + public static void AddTransformMatrix(FlatBufferBuilder builder, Offset transformMatrixOffset) { builder.AddStruct(6, transformMatrixOffset.Value, 0); } + public static void AddShapes(FlatBufferBuilder builder, VectorOffset shapesOffset) { builder.AddOffset(7, shapesOffset.Value, 0); } + public static VectorOffset CreateShapesVector(FlatBufferBuilder builder, uint[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddUint(data[i]); return builder.EndVector(); } + public static VectorOffset CreateShapesVectorBlock(FlatBufferBuilder builder, uint[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateShapesVectorBlock(FlatBufferBuilder builder, ArraySegment data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateShapesVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartShapesVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddFillBrush(FlatBufferBuilder builder, uint fillBrush) { builder.AddUint(8, fillBrush, 4294967295); } + public static void AddStrokeBrush(FlatBufferBuilder builder, uint strokeBrush) { builder.AddUint(9, strokeBrush, 4294967295); } + public static void AddGeometry(FlatBufferBuilder builder, uint geometry) { builder.AddUint(10, geometry, 4294967295); } + public static void AddIsStrokeNonScaling(FlatBufferBuilder builder, bool? isStrokeNonScaling) { builder.AddBool(11, isStrokeNonScaling); } + public static void AddStrokeDashOffset(FlatBufferBuilder builder, float? strokeDashOffset) { builder.AddFloat(12, strokeDashOffset); } + public static void AddStrokeDashArray(FlatBufferBuilder builder, VectorOffset strokeDashArrayOffset) { builder.AddOffset(13, strokeDashArrayOffset.Value, 0); } + public static VectorOffset CreateStrokeDashArrayVector(FlatBufferBuilder builder, float[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddFloat(data[i]); return builder.EndVector(); } + public static VectorOffset CreateStrokeDashArrayVectorBlock(FlatBufferBuilder builder, float[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateStrokeDashArrayVectorBlock(FlatBufferBuilder builder, ArraySegment data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateStrokeDashArrayVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartStrokeDashArrayVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddStrokeDashCap(FlatBufferBuilder builder, byte? strokeDashCap) { builder.AddByte(14, strokeDashCap); } + public static void AddStrokeStartCap(FlatBufferBuilder builder, byte? strokeStartCap) { builder.AddByte(15, strokeStartCap); } + public static void AddStrokeEndCap(FlatBufferBuilder builder, byte? strokeEndCap) { builder.AddByte(16, strokeEndCap); } + public static void AddStrokeLineJoin(FlatBufferBuilder builder, byte? strokeLineJoin) { builder.AddByte(17, strokeLineJoin); } + public static void AddStrokeMiterLimit(FlatBufferBuilder builder, float? strokeMiterLimit) { builder.AddFloat(18, strokeMiterLimit); } + public static void AddStrokeThickness(FlatBufferBuilder builder, float? strokeThickness) { builder.AddFloat(19, strokeThickness); } + public static Offset EndShape(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class ShapeVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyTable(tablePos, 4 /*Base*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObjVerify.Verify, false) + && verifier.VerifyField(tablePos, 6 /*Kind*/, 1 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ShapeKind*/, 1, false) + && verifier.VerifyField(tablePos, 8 /*CenterPoint*/, 8 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2*/, 4, false) + && verifier.VerifyField(tablePos, 10 /*Offset*/, 8 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2*/, 4, false) + && verifier.VerifyField(tablePos, 12 /*RotationAngleInDegrees*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 14 /*Scale*/, 8 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2*/, 4, false) + && verifier.VerifyField(tablePos, 16 /*TransformMatrix*/, 24 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Mat3x2*/, 4, false) + && verifier.VerifyVectorOfData(tablePos, 18 /*Shapes*/, 4 /*uint*/, false) + && verifier.VerifyField(tablePos, 20 /*FillBrush*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 22 /*StrokeBrush*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 24 /*Geometry*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 26 /*IsStrokeNonScaling*/, 1 /*bool*/, 1, false) + && verifier.VerifyField(tablePos, 28 /*StrokeDashOffset*/, 4 /*float*/, 4, false) + && verifier.VerifyVectorOfData(tablePos, 30 /*StrokeDashArray*/, 4 /*float*/, false) + && verifier.VerifyField(tablePos, 32 /*StrokeDashCap*/, 1 /*byte*/, 1, false) + && verifier.VerifyField(tablePos, 34 /*StrokeStartCap*/, 1 /*byte*/, 1, false) + && verifier.VerifyField(tablePos, 36 /*StrokeEndCap*/, 1 /*byte*/, 1, false) + && verifier.VerifyField(tablePos, 38 /*StrokeLineJoin*/, 1 /*byte*/, 1, false) + && verifier.VerifyField(tablePos, 40 /*StrokeMiterLimit*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 42 /*StrokeThickness*/, 4 /*float*/, 4, false) + && verifier.VerifyTableEnd(tablePos); + } +} +public struct Geometry : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static Geometry GetRootAsGeometry(ByteBuffer _bb) { return GetRootAsGeometry(_bb, new Geometry()); } + public static Geometry GetRootAsGeometry(ByteBuffer _bb, Geometry obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public Geometry __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj? Base { get { int o = __p.__offset(4); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj()).__assign(__p.__indirect(o + __p.bb_pos), __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.GeometryKind Kind { get { int o = __p.__offset(6); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.GeometryKind)__p.bb.Get(o + __p.bb_pos) : CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.GeometryKind.Path; } } + public float? TrimStart { get { int o = __p.__offset(8); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float?)null; } } + public float? TrimEnd { get { int o = __p.__offset(10); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float?)null; } } + public float? TrimOffset { get { int o = __p.__offset(12); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float?)null; } } + public uint Path { get { int o = __p.__offset(14); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2? Offset { get { int o = __p.__offset(16); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2? Size { get { int o = __p.__offset(18); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2? CornerRadius { get { int o = __p.__offset(20); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2? Center { get { int o = __p.__offset(22); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2? Radius { get { int o = __p.__offset(24); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + + public static void StartGeometry(FlatBufferBuilder builder) { builder.StartTable(11); } + public static void AddBase(FlatBufferBuilder builder, Offset baseOffset) { builder.AddOffset(0, baseOffset.Value, 0); } + public static void AddKind(FlatBufferBuilder builder, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.GeometryKind kind) { builder.AddByte(1, (byte)kind, 0); } + public static void AddTrimStart(FlatBufferBuilder builder, float? trimStart) { builder.AddFloat(2, trimStart); } + public static void AddTrimEnd(FlatBufferBuilder builder, float? trimEnd) { builder.AddFloat(3, trimEnd); } + public static void AddTrimOffset(FlatBufferBuilder builder, float? trimOffset) { builder.AddFloat(4, trimOffset); } + public static void AddPath(FlatBufferBuilder builder, uint path) { builder.AddUint(5, path, 4294967295); } + public static void AddOffset(FlatBufferBuilder builder, Offset offsetOffset) { builder.AddStruct(6, offsetOffset.Value, 0); } + public static void AddSize(FlatBufferBuilder builder, Offset sizeOffset) { builder.AddStruct(7, sizeOffset.Value, 0); } + public static void AddCornerRadius(FlatBufferBuilder builder, Offset cornerRadiusOffset) { builder.AddStruct(8, cornerRadiusOffset.Value, 0); } + public static void AddCenter(FlatBufferBuilder builder, Offset centerOffset) { builder.AddStruct(9, centerOffset.Value, 0); } + public static void AddRadius(FlatBufferBuilder builder, Offset radiusOffset) { builder.AddStruct(10, radiusOffset.Value, 0); } + public static Offset EndGeometry(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class GeometryVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyTable(tablePos, 4 /*Base*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObjVerify.Verify, false) + && verifier.VerifyField(tablePos, 6 /*Kind*/, 1 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.GeometryKind*/, 1, false) + && verifier.VerifyField(tablePos, 8 /*TrimStart*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 10 /*TrimEnd*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 12 /*TrimOffset*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 14 /*Path*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 16 /*Offset*/, 8 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2*/, 4, false) + && verifier.VerifyField(tablePos, 18 /*Size*/, 8 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2*/, 4, false) + && verifier.VerifyField(tablePos, 20 /*CornerRadius*/, 8 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2*/, 4, false) + && verifier.VerifyField(tablePos, 22 /*Center*/, 8 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2*/, 4, false) + && verifier.VerifyField(tablePos, 24 /*Radius*/, 8 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2*/, 4, false) + && verifier.VerifyTableEnd(tablePos); + } +} +public struct Brush : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static Brush GetRootAsBrush(ByteBuffer _bb) { return GetRootAsBrush(_bb, new Brush()); } + public static Brush GetRootAsBrush(ByteBuffer _bb, Brush obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public Brush __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj? Base { get { int o = __p.__offset(4); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj()).__assign(__p.__indirect(o + __p.bb_pos), __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.BrushKind Kind { get { int o = __p.__offset(6); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.BrushKind)__p.bb.Get(o + __p.bb_pos) : CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.BrushKind.Color; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Color? Color { get { int o = __p.__offset(8); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Color?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Color()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2? AnchorPoint { get { int o = __p.__offset(10); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2? CenterPoint { get { int o = __p.__offset(12); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public uint ColorStops(int j) { int o = __p.__offset(14); return o != 0 ? __p.bb.GetUint(__p.__vector(o) + j * 4) : (uint)0; } + public int ColorStopsLength { get { int o = __p.__offset(14); return o != 0 ? __p.__vector_len(o) : 0; } } +#if ENABLE_SPAN_T + public Span GetColorStopsBytes() { return __p.__vector_as_span(14, 4); } +#else + public ArraySegment? GetColorStopsBytes() { return __p.__vector_as_arraysegment(14); } +#endif + public uint[] GetColorStopsArray() { return __p.__vector_as_array(14); } + public byte? ExtendMode { get { int o = __p.__offset(16); return o != 0 ? __p.bb.Get(o + __p.bb_pos) : (byte?)null; } } + public byte? InterpolationSpace { get { int o = __p.__offset(18); return o != 0 ? __p.bb.Get(o + __p.bb_pos) : (byte?)null; } } + public byte? MappingMode { get { int o = __p.__offset(20); return o != 0 ? __p.bb.Get(o + __p.bb_pos) : (byte?)null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2? Offset { get { int o = __p.__offset(22); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public float? RotationAngleInDegrees { get { int o = __p.__offset(24); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float?)null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2? Scale { get { int o = __p.__offset(26); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Mat3x2? TransformMatrix { get { int o = __p.__offset(28); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Mat3x2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Mat3x2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2? StartPoint { get { int o = __p.__offset(30); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2? EndPoint { get { int o = __p.__offset(32); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2? EllipseCenter { get { int o = __p.__offset(34); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2? EllipseRadius { get { int o = __p.__offset(36); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2? GradientOriginOffset { get { int o = __p.__offset(38); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public uint Surface { get { int o = __p.__offset(40); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public uint Source { get { int o = __p.__offset(42); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public uint Mask { get { int o = __p.__offset(44); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public uint Effect { get { int o = __p.__offset(46); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.SourceParameter? SourceParameters(int j) { int o = __p.__offset(48); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.SourceParameter?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.SourceParameter()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } + public int SourceParametersLength { get { int o = __p.__offset(48); return o != 0 ? __p.__vector_len(o) : 0; } } + + public static void StartBrush(FlatBufferBuilder builder) { builder.StartTable(23); } + public static void AddBase(FlatBufferBuilder builder, Offset baseOffset) { builder.AddOffset(0, baseOffset.Value, 0); } + public static void AddKind(FlatBufferBuilder builder, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.BrushKind kind) { builder.AddByte(1, (byte)kind, 0); } + public static void AddColor(FlatBufferBuilder builder, Offset colorOffset) { builder.AddStruct(2, colorOffset.Value, 0); } + public static void AddAnchorPoint(FlatBufferBuilder builder, Offset anchorPointOffset) { builder.AddStruct(3, anchorPointOffset.Value, 0); } + public static void AddCenterPoint(FlatBufferBuilder builder, Offset centerPointOffset) { builder.AddStruct(4, centerPointOffset.Value, 0); } + public static void AddColorStops(FlatBufferBuilder builder, VectorOffset colorStopsOffset) { builder.AddOffset(5, colorStopsOffset.Value, 0); } + public static VectorOffset CreateColorStopsVector(FlatBufferBuilder builder, uint[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddUint(data[i]); return builder.EndVector(); } + public static VectorOffset CreateColorStopsVectorBlock(FlatBufferBuilder builder, uint[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateColorStopsVectorBlock(FlatBufferBuilder builder, ArraySegment data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateColorStopsVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartColorStopsVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddExtendMode(FlatBufferBuilder builder, byte? extendMode) { builder.AddByte(6, extendMode); } + public static void AddInterpolationSpace(FlatBufferBuilder builder, byte? interpolationSpace) { builder.AddByte(7, interpolationSpace); } + public static void AddMappingMode(FlatBufferBuilder builder, byte? mappingMode) { builder.AddByte(8, mappingMode); } + public static void AddOffset(FlatBufferBuilder builder, Offset offsetOffset) { builder.AddStruct(9, offsetOffset.Value, 0); } + public static void AddRotationAngleInDegrees(FlatBufferBuilder builder, float? rotationAngleInDegrees) { builder.AddFloat(10, rotationAngleInDegrees); } + public static void AddScale(FlatBufferBuilder builder, Offset scaleOffset) { builder.AddStruct(11, scaleOffset.Value, 0); } + public static void AddTransformMatrix(FlatBufferBuilder builder, Offset transformMatrixOffset) { builder.AddStruct(12, transformMatrixOffset.Value, 0); } + public static void AddStartPoint(FlatBufferBuilder builder, Offset startPointOffset) { builder.AddStruct(13, startPointOffset.Value, 0); } + public static void AddEndPoint(FlatBufferBuilder builder, Offset endPointOffset) { builder.AddStruct(14, endPointOffset.Value, 0); } + public static void AddEllipseCenter(FlatBufferBuilder builder, Offset ellipseCenterOffset) { builder.AddStruct(15, ellipseCenterOffset.Value, 0); } + public static void AddEllipseRadius(FlatBufferBuilder builder, Offset ellipseRadiusOffset) { builder.AddStruct(16, ellipseRadiusOffset.Value, 0); } + public static void AddGradientOriginOffset(FlatBufferBuilder builder, Offset gradientOriginOffsetOffset) { builder.AddStruct(17, gradientOriginOffsetOffset.Value, 0); } + public static void AddSurface(FlatBufferBuilder builder, uint surface) { builder.AddUint(18, surface, 4294967295); } + public static void AddSource(FlatBufferBuilder builder, uint source) { builder.AddUint(19, source, 4294967295); } + public static void AddMask(FlatBufferBuilder builder, uint mask) { builder.AddUint(20, mask, 4294967295); } + public static void AddEffect(FlatBufferBuilder builder, uint effect) { builder.AddUint(21, effect, 4294967295); } + public static void AddSourceParameters(FlatBufferBuilder builder, VectorOffset sourceParametersOffset) { builder.AddOffset(22, sourceParametersOffset.Value, 0); } + public static VectorOffset CreateSourceParametersVector(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreateSourceParametersVectorBlock(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateSourceParametersVectorBlock(FlatBufferBuilder builder, ArraySegment> data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateSourceParametersVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add>(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartSourceParametersVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static Offset EndBrush(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class BrushVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyTable(tablePos, 4 /*Base*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObjVerify.Verify, false) + && verifier.VerifyField(tablePos, 6 /*Kind*/, 1 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.BrushKind*/, 1, false) + && verifier.VerifyField(tablePos, 8 /*Color*/, 4 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Color*/, 1, false) + && verifier.VerifyField(tablePos, 10 /*AnchorPoint*/, 8 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2*/, 4, false) + && verifier.VerifyField(tablePos, 12 /*CenterPoint*/, 8 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2*/, 4, false) + && verifier.VerifyVectorOfData(tablePos, 14 /*ColorStops*/, 4 /*uint*/, false) + && verifier.VerifyField(tablePos, 16 /*ExtendMode*/, 1 /*byte*/, 1, false) + && verifier.VerifyField(tablePos, 18 /*InterpolationSpace*/, 1 /*byte*/, 1, false) + && verifier.VerifyField(tablePos, 20 /*MappingMode*/, 1 /*byte*/, 1, false) + && verifier.VerifyField(tablePos, 22 /*Offset*/, 8 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2*/, 4, false) + && verifier.VerifyField(tablePos, 24 /*RotationAngleInDegrees*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 26 /*Scale*/, 8 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2*/, 4, false) + && verifier.VerifyField(tablePos, 28 /*TransformMatrix*/, 24 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Mat3x2*/, 4, false) + && verifier.VerifyField(tablePos, 30 /*StartPoint*/, 8 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2*/, 4, false) + && verifier.VerifyField(tablePos, 32 /*EndPoint*/, 8 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2*/, 4, false) + && verifier.VerifyField(tablePos, 34 /*EllipseCenter*/, 8 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2*/, 4, false) + && verifier.VerifyField(tablePos, 36 /*EllipseRadius*/, 8 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2*/, 4, false) + && verifier.VerifyField(tablePos, 38 /*GradientOriginOffset*/, 8 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2*/, 4, false) + && verifier.VerifyField(tablePos, 40 /*Surface*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 42 /*Source*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 44 /*Mask*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 46 /*Effect*/, 4 /*uint*/, 4, false) + && verifier.VerifyVectorOfTables(tablePos, 48 /*SourceParameters*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.SourceParameterVerify.Verify, false) + && verifier.VerifyTableEnd(tablePos); + } +} +public struct GradientStop : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static GradientStop GetRootAsGradientStop(ByteBuffer _bb) { return GetRootAsGradientStop(_bb, new GradientStop()); } + public static GradientStop GetRootAsGradientStop(ByteBuffer _bb, GradientStop obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public GradientStop __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj? Base { get { int o = __p.__offset(4); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj()).__assign(__p.__indirect(o + __p.bb_pos), __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Color? Color { get { int o = __p.__offset(6); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Color?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Color()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public float Offset { get { int o = __p.__offset(8); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)0.0f; } } + + public static void StartGradientStop(FlatBufferBuilder builder) { builder.StartTable(3); } + public static void AddBase(FlatBufferBuilder builder, Offset baseOffset) { builder.AddOffset(0, baseOffset.Value, 0); } + public static void AddColor(FlatBufferBuilder builder, Offset colorOffset) { builder.AddStruct(1, colorOffset.Value, 0); } + public static void AddOffset(FlatBufferBuilder builder, float offset) { builder.AddFloat(2, offset, 0.0f); } + public static Offset EndGradientStop(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class GradientStopVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyTable(tablePos, 4 /*Base*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObjVerify.Verify, false) + && verifier.VerifyField(tablePos, 6 /*Color*/, 4 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Color*/, 1, false) + && verifier.VerifyField(tablePos, 8 /*Offset*/, 4 /*float*/, 4, false) + && verifier.VerifyTableEnd(tablePos); + } +} +public struct ViewBox : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static ViewBox GetRootAsViewBox(ByteBuffer _bb) { return GetRootAsViewBox(_bb, new ViewBox()); } + public static ViewBox GetRootAsViewBox(ByteBuffer _bb, ViewBox obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public ViewBox __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj? Base { get { int o = __p.__offset(4); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj()).__assign(__p.__indirect(o + __p.bb_pos), __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2? Size { get { int o = __p.__offset(6); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + + public static void StartViewBox(FlatBufferBuilder builder) { builder.StartTable(2); } + public static void AddBase(FlatBufferBuilder builder, Offset baseOffset) { builder.AddOffset(0, baseOffset.Value, 0); } + public static void AddSize(FlatBufferBuilder builder, Offset sizeOffset) { builder.AddStruct(1, sizeOffset.Value, 0); } + public static Offset EndViewBox(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class ViewBoxVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyTable(tablePos, 4 /*Base*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObjVerify.Verify, false) + && verifier.VerifyField(tablePos, 6 /*Size*/, 8 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2*/, 4, false) + && verifier.VerifyTableEnd(tablePos); + } +} +public struct Clip : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static Clip GetRootAsClip(ByteBuffer _bb) { return GetRootAsClip(_bb, new Clip()); } + public static Clip GetRootAsClip(ByteBuffer _bb, Clip obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public Clip __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj? Base { get { int o = __p.__offset(4); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj()).__assign(__p.__indirect(o + __p.bb_pos), __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ClipKind Kind { get { int o = __p.__offset(6); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ClipKind)__p.bb.Get(o + __p.bb_pos) : CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ClipKind.Inset; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2? CenterPoint { get { int o = __p.__offset(8); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2? Scale { get { int o = __p.__offset(10); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public float? LeftInset { get { int o = __p.__offset(12); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float?)null; } } + public float? RightInset { get { int o = __p.__offset(14); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float?)null; } } + public float? TopInset { get { int o = __p.__offset(16); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float?)null; } } + public float? BottomInset { get { int o = __p.__offset(18); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float?)null; } } + public uint Geometry { get { int o = __p.__offset(20); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + + public static void StartClip(FlatBufferBuilder builder) { builder.StartTable(9); } + public static void AddBase(FlatBufferBuilder builder, Offset baseOffset) { builder.AddOffset(0, baseOffset.Value, 0); } + public static void AddKind(FlatBufferBuilder builder, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ClipKind kind) { builder.AddByte(1, (byte)kind, 0); } + public static void AddCenterPoint(FlatBufferBuilder builder, Offset centerPointOffset) { builder.AddStruct(2, centerPointOffset.Value, 0); } + public static void AddScale(FlatBufferBuilder builder, Offset scaleOffset) { builder.AddStruct(3, scaleOffset.Value, 0); } + public static void AddLeftInset(FlatBufferBuilder builder, float? leftInset) { builder.AddFloat(4, leftInset); } + public static void AddRightInset(FlatBufferBuilder builder, float? rightInset) { builder.AddFloat(5, rightInset); } + public static void AddTopInset(FlatBufferBuilder builder, float? topInset) { builder.AddFloat(6, topInset); } + public static void AddBottomInset(FlatBufferBuilder builder, float? bottomInset) { builder.AddFloat(7, bottomInset); } + public static void AddGeometry(FlatBufferBuilder builder, uint geometry) { builder.AddUint(8, geometry, 4294967295); } + public static Offset EndClip(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class ClipVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyTable(tablePos, 4 /*Base*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObjVerify.Verify, false) + && verifier.VerifyField(tablePos, 6 /*Kind*/, 1 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ClipKind*/, 1, false) + && verifier.VerifyField(tablePos, 8 /*CenterPoint*/, 8 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2*/, 4, false) + && verifier.VerifyField(tablePos, 10 /*Scale*/, 8 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2*/, 4, false) + && verifier.VerifyField(tablePos, 12 /*LeftInset*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 14 /*RightInset*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 16 /*TopInset*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 18 /*BottomInset*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 20 /*Geometry*/, 4 /*uint*/, 4, false) + && verifier.VerifyTableEnd(tablePos); + } +} +public struct Shadow : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static Shadow GetRootAsShadow(ByteBuffer _bb) { return GetRootAsShadow(_bb, new Shadow()); } + public static Shadow GetRootAsShadow(ByteBuffer _bb, Shadow obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public Shadow __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj? Base { get { int o = __p.__offset(4); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj()).__assign(__p.__indirect(o + __p.bb_pos), __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ShadowKind Kind { get { int o = __p.__offset(6); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ShadowKind)__p.bb.Get(o + __p.bb_pos) : CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ShadowKind.Drop; } } + public float? BlurRadius { get { int o = __p.__offset(8); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float?)null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Color? Color { get { int o = __p.__offset(10); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Color?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Color()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public uint Mask { get { int o = __p.__offset(12); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec3? Offset { get { int o = __p.__offset(14); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec3?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec3()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public float? Opacity { get { int o = __p.__offset(16); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float?)null; } } + public byte? SourcePolicy { get { int o = __p.__offset(18); return o != 0 ? __p.bb.Get(o + __p.bb_pos) : (byte?)null; } } + + public static void StartShadow(FlatBufferBuilder builder) { builder.StartTable(8); } + public static void AddBase(FlatBufferBuilder builder, Offset baseOffset) { builder.AddOffset(0, baseOffset.Value, 0); } + public static void AddKind(FlatBufferBuilder builder, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ShadowKind kind) { builder.AddByte(1, (byte)kind, 0); } + public static void AddBlurRadius(FlatBufferBuilder builder, float? blurRadius) { builder.AddFloat(2, blurRadius); } + public static void AddColor(FlatBufferBuilder builder, Offset colorOffset) { builder.AddStruct(3, colorOffset.Value, 0); } + public static void AddMask(FlatBufferBuilder builder, uint mask) { builder.AddUint(4, mask, 4294967295); } + public static void AddOffset(FlatBufferBuilder builder, Offset offsetOffset) { builder.AddStruct(5, offsetOffset.Value, 0); } + public static void AddOpacity(FlatBufferBuilder builder, float? opacity) { builder.AddFloat(6, opacity); } + public static void AddSourcePolicy(FlatBufferBuilder builder, byte? sourcePolicy) { builder.AddByte(7, sourcePolicy); } + public static Offset EndShadow(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class ShadowVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyTable(tablePos, 4 /*Base*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObjVerify.Verify, false) + && verifier.VerifyField(tablePos, 6 /*Kind*/, 1 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ShadowKind*/, 1, false) + && verifier.VerifyField(tablePos, 8 /*BlurRadius*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 10 /*Color*/, 4 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Color*/, 1, false) + && verifier.VerifyField(tablePos, 12 /*Mask*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 14 /*Offset*/, 12 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec3*/, 4, false) + && verifier.VerifyField(tablePos, 16 /*Opacity*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 18 /*SourcePolicy*/, 1 /*byte*/, 1, false) + && verifier.VerifyTableEnd(tablePos); + } +} +public struct Surface : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static Surface GetRootAsSurface(ByteBuffer _bb) { return GetRootAsSurface(_bb, new Surface()); } + public static Surface GetRootAsSurface(ByteBuffer _bb, Surface obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public Surface __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj? Base { get { int o = __p.__offset(4); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj()).__assign(__p.__indirect(o + __p.bb_pos), __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.SurfaceKind Kind { get { int o = __p.__offset(6); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.SurfaceKind)__p.bb.Get(o + __p.bb_pos) : CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.SurfaceKind.VisualSurface; } } + public uint SourceVisual { get { int o = __p.__offset(8); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2? SourceSize { get { int o = __p.__offset(10); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2? SourceOffset { get { int o = __p.__offset(12); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public uint Uri { get { int o = __p.__offset(14); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public byte Bytes(int j) { int o = __p.__offset(16); return o != 0 ? __p.bb.Get(__p.__vector(o) + j * 1) : (byte)0; } + public int BytesLength { get { int o = __p.__offset(16); return o != 0 ? __p.__vector_len(o) : 0; } } +#if ENABLE_SPAN_T + public Span GetBytesBytes() { return __p.__vector_as_span(16, 1); } +#else + public ArraySegment? GetBytesBytes() { return __p.__vector_as_arraysegment(16); } +#endif + public byte[] GetBytesArray() { return __p.__vector_as_array(16); } + + public static void StartSurface(FlatBufferBuilder builder) { builder.StartTable(7); } + public static void AddBase(FlatBufferBuilder builder, Offset baseOffset) { builder.AddOffset(0, baseOffset.Value, 0); } + public static void AddKind(FlatBufferBuilder builder, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.SurfaceKind kind) { builder.AddByte(1, (byte)kind, 0); } + public static void AddSourceVisual(FlatBufferBuilder builder, uint sourceVisual) { builder.AddUint(2, sourceVisual, 4294967295); } + public static void AddSourceSize(FlatBufferBuilder builder, Offset sourceSizeOffset) { builder.AddStruct(3, sourceSizeOffset.Value, 0); } + public static void AddSourceOffset(FlatBufferBuilder builder, Offset sourceOffsetOffset) { builder.AddStruct(4, sourceOffsetOffset.Value, 0); } + public static void AddUri(FlatBufferBuilder builder, uint uri) { builder.AddUint(5, uri, 4294967295); } + public static void AddBytes(FlatBufferBuilder builder, VectorOffset bytesOffset) { builder.AddOffset(6, bytesOffset.Value, 0); } + public static VectorOffset CreateBytesVector(FlatBufferBuilder builder, byte[] data) { builder.StartVector(1, data.Length, 1); for (int i = data.Length - 1; i >= 0; i--) builder.AddByte(data[i]); return builder.EndVector(); } + public static VectorOffset CreateBytesVectorBlock(FlatBufferBuilder builder, byte[] data) { builder.StartVector(1, data.Length, 1); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateBytesVectorBlock(FlatBufferBuilder builder, ArraySegment data) { builder.StartVector(1, data.Count, 1); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateBytesVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartBytesVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(1, numElems, 1); } + public static Offset EndSurface(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class SurfaceVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyTable(tablePos, 4 /*Base*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObjVerify.Verify, false) + && verifier.VerifyField(tablePos, 6 /*Kind*/, 1 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.SurfaceKind*/, 1, false) + && verifier.VerifyField(tablePos, 8 /*SourceVisual*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 10 /*SourceSize*/, 8 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2*/, 4, false) + && verifier.VerifyField(tablePos, 12 /*SourceOffset*/, 8 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2*/, 4, false) + && verifier.VerifyField(tablePos, 14 /*Uri*/, 4 /*uint*/, 4, false) + && verifier.VerifyVectorOfData(tablePos, 16 /*Bytes*/, 1 /*byte*/, false) + && verifier.VerifyTableEnd(tablePos); + } +} +public struct Effect : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static Effect GetRootAsEffect(ByteBuffer _bb) { return GetRootAsEffect(_bb, new Effect()); } + public static Effect GetRootAsEffect(ByteBuffer _bb, Effect obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public Effect __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj? Base { get { int o = __p.__offset(4); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj()).__assign(__p.__indirect(o + __p.bb_pos), __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.EffectKind Kind { get { int o = __p.__offset(6); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.EffectKind)__p.bb.Get(o + __p.bb_pos) : CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.EffectKind.Composite; } } + public uint Sources(int j) { int o = __p.__offset(8); return o != 0 ? __p.bb.GetUint(__p.__vector(o) + j * 4) : (uint)0; } + public int SourcesLength { get { int o = __p.__offset(8); return o != 0 ? __p.__vector_len(o) : 0; } } +#if ENABLE_SPAN_T + public Span GetSourcesBytes() { return __p.__vector_as_span(8, 4); } +#else + public ArraySegment? GetSourcesBytes() { return __p.__vector_as_arraysegment(8); } +#endif + public uint[] GetSourcesArray() { return __p.__vector_as_array(8); } + public byte Mode { get { int o = __p.__offset(10); return o != 0 ? __p.bb.Get(o + __p.bb_pos) : (byte)0; } } + public float BlurAmount { get { int o = __p.__offset(12); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)0.0f; } } + + public static Offset CreateEffect(FlatBufferBuilder builder, + Offset @baseOffset = default(Offset), + CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.EffectKind kind = CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.EffectKind.Composite, + VectorOffset sourcesOffset = default(VectorOffset), + byte mode = 0, + float blur_amount = 0.0f) { + builder.StartTable(5); + Effect.AddBlurAmount(builder, blur_amount); + Effect.AddSources(builder, sourcesOffset); + Effect.AddBase(builder, @baseOffset); + Effect.AddMode(builder, mode); + Effect.AddKind(builder, kind); + return Effect.EndEffect(builder); + } + + public static void StartEffect(FlatBufferBuilder builder) { builder.StartTable(5); } + public static void AddBase(FlatBufferBuilder builder, Offset baseOffset) { builder.AddOffset(0, baseOffset.Value, 0); } + public static void AddKind(FlatBufferBuilder builder, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.EffectKind kind) { builder.AddByte(1, (byte)kind, 0); } + public static void AddSources(FlatBufferBuilder builder, VectorOffset sourcesOffset) { builder.AddOffset(2, sourcesOffset.Value, 0); } + public static VectorOffset CreateSourcesVector(FlatBufferBuilder builder, uint[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddUint(data[i]); return builder.EndVector(); } + public static VectorOffset CreateSourcesVectorBlock(FlatBufferBuilder builder, uint[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateSourcesVectorBlock(FlatBufferBuilder builder, ArraySegment data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateSourcesVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartSourcesVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddMode(FlatBufferBuilder builder, byte mode) { builder.AddByte(3, mode, 0); } + public static void AddBlurAmount(FlatBufferBuilder builder, float blurAmount) { builder.AddFloat(4, blurAmount, 0.0f); } + public static Offset EndEffect(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class EffectVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyTable(tablePos, 4 /*Base*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObjVerify.Verify, false) + && verifier.VerifyField(tablePos, 6 /*Kind*/, 1 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.EffectKind*/, 1, false) + && verifier.VerifyVectorOfData(tablePos, 8 /*Sources*/, 4 /*uint*/, false) + && verifier.VerifyField(tablePos, 10 /*Mode*/, 1 /*byte*/, 1, false) + && verifier.VerifyField(tablePos, 12 /*BlurAmount*/, 4 /*float*/, 4, false) + && verifier.VerifyTableEnd(tablePos); + } +} +public struct Easing : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static Easing GetRootAsEasing(ByteBuffer _bb) { return GetRootAsEasing(_bb, new Easing()); } + public static Easing GetRootAsEasing(ByteBuffer _bb, Easing obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public Easing __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj? Base { get { int o = __p.__offset(4); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj()).__assign(__p.__indirect(o + __p.bb_pos), __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.EasingKind Kind { get { int o = __p.__offset(6); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.EasingKind)__p.bb.Get(o + __p.bb_pos) : CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.EasingKind.Linear; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2? ControlPoint1 { get { int o = __p.__offset(8); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2? ControlPoint2 { get { int o = __p.__offset(10); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public int? StepCount { get { int o = __p.__offset(12); return o != 0 ? __p.bb.GetInt(o + __p.bb_pos) : (int?)null; } } + public int? InitialStep { get { int o = __p.__offset(14); return o != 0 ? __p.bb.GetInt(o + __p.bb_pos) : (int?)null; } } + public int? FinalStep { get { int o = __p.__offset(16); return o != 0 ? __p.bb.GetInt(o + __p.bb_pos) : (int?)null; } } + public bool? IsInitialStepSingleFrame { get { int o = __p.__offset(18); return o != 0 ? 0!=__p.bb.Get(o + __p.bb_pos) : (bool?)null; } } + public bool? IsFinalStepSingleFrame { get { int o = __p.__offset(20); return o != 0 ? 0!=__p.bb.Get(o + __p.bb_pos) : (bool?)null; } } + + public static void StartEasing(FlatBufferBuilder builder) { builder.StartTable(9); } + public static void AddBase(FlatBufferBuilder builder, Offset baseOffset) { builder.AddOffset(0, baseOffset.Value, 0); } + public static void AddKind(FlatBufferBuilder builder, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.EasingKind kind) { builder.AddByte(1, (byte)kind, 0); } + public static void AddControlPoint1(FlatBufferBuilder builder, Offset controlPoint1Offset) { builder.AddStruct(2, controlPoint1Offset.Value, 0); } + public static void AddControlPoint2(FlatBufferBuilder builder, Offset controlPoint2Offset) { builder.AddStruct(3, controlPoint2Offset.Value, 0); } + public static void AddStepCount(FlatBufferBuilder builder, int? stepCount) { builder.AddInt(4, stepCount); } + public static void AddInitialStep(FlatBufferBuilder builder, int? initialStep) { builder.AddInt(5, initialStep); } + public static void AddFinalStep(FlatBufferBuilder builder, int? finalStep) { builder.AddInt(6, finalStep); } + public static void AddIsInitialStepSingleFrame(FlatBufferBuilder builder, bool? isInitialStepSingleFrame) { builder.AddBool(7, isInitialStepSingleFrame); } + public static void AddIsFinalStepSingleFrame(FlatBufferBuilder builder, bool? isFinalStepSingleFrame) { builder.AddBool(8, isFinalStepSingleFrame); } + public static Offset EndEasing(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class EasingVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyTable(tablePos, 4 /*Base*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObjVerify.Verify, false) + && verifier.VerifyField(tablePos, 6 /*Kind*/, 1 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.EasingKind*/, 1, false) + && verifier.VerifyField(tablePos, 8 /*ControlPoint1*/, 8 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2*/, 4, false) + && verifier.VerifyField(tablePos, 10 /*ControlPoint2*/, 8 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec2*/, 4, false) + && verifier.VerifyField(tablePos, 12 /*StepCount*/, 4 /*int*/, 4, false) + && verifier.VerifyField(tablePos, 14 /*InitialStep*/, 4 /*int*/, 4, false) + && verifier.VerifyField(tablePos, 16 /*FinalStep*/, 4 /*int*/, 4, false) + && verifier.VerifyField(tablePos, 18 /*IsInitialStepSingleFrame*/, 1 /*bool*/, 1, false) + && verifier.VerifyField(tablePos, 20 /*IsFinalStepSingleFrame*/, 1 /*bool*/, 1, false) + && verifier.VerifyTableEnd(tablePos); + } +} +public struct Animation : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static Animation GetRootAsAnimation(ByteBuffer _bb) { return GetRootAsAnimation(_bb, new Animation()); } + public static Animation GetRootAsAnimation(ByteBuffer _bb, Animation obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public Animation __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj? Base { get { int o = __p.__offset(4); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj()).__assign(__p.__indirect(o + __p.bb_pos), __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.AnimationKind Kind { get { int o = __p.__offset(6); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.AnimationKind)__p.bb.Get(o + __p.bb_pos) : CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.AnimationKind.Scalar; } } + public uint Target { get { int o = __p.__offset(8); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ReferenceParameter? ReferenceParameters(int j) { int o = __p.__offset(10); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ReferenceParameter?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ReferenceParameter()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } + public int ReferenceParametersLength { get { int o = __p.__offset(10); return o != 0 ? __p.__vector_len(o) : 0; } } + public uint Expression { get { int o = __p.__offset(12); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public long DurationTicks { get { int o = __p.__offset(14); return o != 0 ? __p.bb.GetLong(o + __p.bb_pos) : (long)0; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.KeyFrame? KeyFrames(int j) { int o = __p.__offset(16); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.KeyFrame?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.KeyFrame()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } + public int KeyFramesLength { get { int o = __p.__offset(16); return o != 0 ? __p.__vector_len(o) : 0; } } + public byte? InterpolationColorSpace { get { int o = __p.__offset(18); return o != 0 ? __p.bb.Get(o + __p.bb_pos) : (byte?)null; } } + + public static Offset CreateAnimation(FlatBufferBuilder builder, + Offset @baseOffset = default(Offset), + CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.AnimationKind kind = CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.AnimationKind.Scalar, + uint target = 4294967295, + VectorOffset reference_parametersOffset = default(VectorOffset), + uint expression = 4294967295, + long duration_ticks = 0, + VectorOffset key_framesOffset = default(VectorOffset), + byte? interpolation_color_space = null) { + builder.StartTable(8); + Animation.AddDurationTicks(builder, duration_ticks); + Animation.AddKeyFrames(builder, key_framesOffset); + Animation.AddExpression(builder, expression); + Animation.AddReferenceParameters(builder, reference_parametersOffset); + Animation.AddTarget(builder, target); + Animation.AddBase(builder, @baseOffset); + Animation.AddInterpolationColorSpace(builder, interpolation_color_space); + Animation.AddKind(builder, kind); + return Animation.EndAnimation(builder); + } + + public static void StartAnimation(FlatBufferBuilder builder) { builder.StartTable(8); } + public static void AddBase(FlatBufferBuilder builder, Offset baseOffset) { builder.AddOffset(0, baseOffset.Value, 0); } + public static void AddKind(FlatBufferBuilder builder, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.AnimationKind kind) { builder.AddByte(1, (byte)kind, 0); } + public static void AddTarget(FlatBufferBuilder builder, uint target) { builder.AddUint(2, target, 4294967295); } + public static void AddReferenceParameters(FlatBufferBuilder builder, VectorOffset referenceParametersOffset) { builder.AddOffset(3, referenceParametersOffset.Value, 0); } + public static VectorOffset CreateReferenceParametersVector(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreateReferenceParametersVectorBlock(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateReferenceParametersVectorBlock(FlatBufferBuilder builder, ArraySegment> data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateReferenceParametersVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add>(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartReferenceParametersVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddExpression(FlatBufferBuilder builder, uint expression) { builder.AddUint(4, expression, 4294967295); } + public static void AddDurationTicks(FlatBufferBuilder builder, long durationTicks) { builder.AddLong(5, durationTicks, 0); } + public static void AddKeyFrames(FlatBufferBuilder builder, VectorOffset keyFramesOffset) { builder.AddOffset(6, keyFramesOffset.Value, 0); } + public static VectorOffset CreateKeyFramesVector(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreateKeyFramesVectorBlock(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateKeyFramesVectorBlock(FlatBufferBuilder builder, ArraySegment> data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateKeyFramesVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add>(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartKeyFramesVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddInterpolationColorSpace(FlatBufferBuilder builder, byte? interpolationColorSpace) { builder.AddByte(7, interpolationColorSpace); } + public static Offset EndAnimation(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class AnimationVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyTable(tablePos, 4 /*Base*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObjVerify.Verify, false) + && verifier.VerifyField(tablePos, 6 /*Kind*/, 1 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.AnimationKind*/, 1, false) + && verifier.VerifyField(tablePos, 8 /*Target*/, 4 /*uint*/, 4, false) + && verifier.VerifyVectorOfTables(tablePos, 10 /*ReferenceParameters*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ReferenceParameterVerify.Verify, false) + && verifier.VerifyField(tablePos, 12 /*Expression*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 14 /*DurationTicks*/, 8 /*long*/, 8, false) + && verifier.VerifyVectorOfTables(tablePos, 16 /*KeyFrames*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.KeyFrameVerify.Verify, false) + && verifier.VerifyField(tablePos, 18 /*InterpolationColorSpace*/, 1 /*byte*/, 1, false) + && verifier.VerifyTableEnd(tablePos); + } +} +public struct PropertySet : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static PropertySet GetRootAsPropertySet(ByteBuffer _bb) { return GetRootAsPropertySet(_bb, new PropertySet()); } + public static PropertySet GetRootAsPropertySet(ByteBuffer _bb, PropertySet obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public PropertySet __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj? Base { get { int o = __p.__offset(4); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj()).__assign(__p.__indirect(o + __p.bb_pos), __p.bb) : null; } } + public uint Owner { get { int o = __p.__offset(6); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertyValue? Values(int j) { int o = __p.__offset(8); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertyValue?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertyValue()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } + public int ValuesLength { get { int o = __p.__offset(8); return o != 0 ? __p.__vector_len(o) : 0; } } + + public static Offset CreatePropertySet(FlatBufferBuilder builder, + Offset @baseOffset = default(Offset), + uint owner = 4294967295, + VectorOffset valuesOffset = default(VectorOffset)) { + builder.StartTable(3); + PropertySet.AddValues(builder, valuesOffset); + PropertySet.AddOwner(builder, owner); + PropertySet.AddBase(builder, @baseOffset); + return PropertySet.EndPropertySet(builder); + } + + public static void StartPropertySet(FlatBufferBuilder builder) { builder.StartTable(3); } + public static void AddBase(FlatBufferBuilder builder, Offset baseOffset) { builder.AddOffset(0, baseOffset.Value, 0); } + public static void AddOwner(FlatBufferBuilder builder, uint owner) { builder.AddUint(1, owner, 4294967295); } + public static void AddValues(FlatBufferBuilder builder, VectorOffset valuesOffset) { builder.AddOffset(2, valuesOffset.Value, 0); } + public static VectorOffset CreateValuesVector(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreateValuesVectorBlock(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateValuesVectorBlock(FlatBufferBuilder builder, ArraySegment> data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateValuesVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add>(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartValuesVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static Offset EndPropertySet(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class PropertySetVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyTable(tablePos, 4 /*Base*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObjVerify.Verify, false) + && verifier.VerifyField(tablePos, 6 /*Owner*/, 4 /*uint*/, 4, false) + && verifier.VerifyVectorOfTables(tablePos, 8 /*Values*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertyValueVerify.Verify, false) + && verifier.VerifyTableEnd(tablePos); + } +} +public struct Controller : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static Controller GetRootAsController(ByteBuffer _bb) { return GetRootAsController(_bb, new Controller()); } + public static Controller GetRootAsController(ByteBuffer _bb, Controller obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public Controller __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj? Base { get { int o = __p.__offset(4); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObj()).__assign(__p.__indirect(o + __p.bb_pos), __p.bb) : null; } } + public uint TargetObject { get { int o = __p.__offset(6); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public uint TargetProperty { get { int o = __p.__offset(8); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public bool IsPaused { get { int o = __p.__offset(10); return o != 0 ? 0!=__p.bb.Get(o + __p.bb_pos) : (bool)false; } } + public bool IsCustom { get { int o = __p.__offset(12); return o != 0 ? 0!=__p.bb.Get(o + __p.bb_pos) : (bool)false; } } + + public static Offset CreateController(FlatBufferBuilder builder, + Offset @baseOffset = default(Offset), + uint target_object = 4294967295, + uint target_property = 4294967295, + bool is_paused = false, + bool is_custom = false) { + builder.StartTable(5); + Controller.AddTargetProperty(builder, target_property); + Controller.AddTargetObject(builder, target_object); + Controller.AddBase(builder, @baseOffset); + Controller.AddIsCustom(builder, is_custom); + Controller.AddIsPaused(builder, is_paused); + return Controller.EndController(builder); + } + + public static void StartController(FlatBufferBuilder builder) { builder.StartTable(5); } + public static void AddBase(FlatBufferBuilder builder, Offset baseOffset) { builder.AddOffset(0, baseOffset.Value, 0); } + public static void AddTargetObject(FlatBufferBuilder builder, uint targetObject) { builder.AddUint(1, targetObject, 4294967295); } + public static void AddTargetProperty(FlatBufferBuilder builder, uint targetProperty) { builder.AddUint(2, targetProperty, 4294967295); } + public static void AddIsPaused(FlatBufferBuilder builder, bool isPaused) { builder.AddBool(3, isPaused, false); } + public static void AddIsCustom(FlatBufferBuilder builder, bool isCustom) { builder.AddBool(4, isCustom, false); } + public static Offset EndController(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class ControllerVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyTable(tablePos, 4 /*Base*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CompObjVerify.Verify, false) + && verifier.VerifyField(tablePos, 6 /*TargetObject*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 8 /*TargetProperty*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 10 /*IsPaused*/, 1 /*bool*/, 1, false) + && verifier.VerifyField(tablePos, 12 /*IsCustom*/, 1 /*bool*/, 1, false) + && verifier.VerifyTableEnd(tablePos); + } +} +public struct PropertyBinding : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static PropertyBinding GetRootAsPropertyBinding(ByteBuffer _bb) { return GetRootAsPropertyBinding(_bb, new PropertyBinding()); } + public static PropertyBinding GetRootAsPropertyBinding(ByteBuffer _bb, PropertyBinding obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public PropertyBinding __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public uint Name { get { int o = __p.__offset(4); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)0; } } + public uint DisplayName { get { int o = __p.__offset(6); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)0; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertyValueType ActualType { get { int o = __p.__offset(8); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertyValueType)__p.bb.Get(o + __p.bb_pos) : CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertyValueType.None; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertyValueType ExposedType { get { int o = __p.__offset(10); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertyValueType)__p.bb.Get(o + __p.bb_pos) : CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertyValueType.None; } } + public float DefaultScalar { get { int o = __p.__offset(12); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)0.0f; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec4? DefaultVector { get { int o = __p.__offset(14); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec4?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec4()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Color? DefaultColor { get { int o = __p.__offset(16); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Color?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Color()).__assign(o + __p.bb_pos, __p.bb) : null; } } + + public static void StartPropertyBinding(FlatBufferBuilder builder) { builder.StartTable(7); } + public static void AddName(FlatBufferBuilder builder, uint name) { builder.AddUint(0, name, 0); } + public static void AddDisplayName(FlatBufferBuilder builder, uint displayName) { builder.AddUint(1, displayName, 0); } + public static void AddActualType(FlatBufferBuilder builder, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertyValueType actualType) { builder.AddByte(2, (byte)actualType, 0); } + public static void AddExposedType(FlatBufferBuilder builder, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertyValueType exposedType) { builder.AddByte(3, (byte)exposedType, 0); } + public static void AddDefaultScalar(FlatBufferBuilder builder, float defaultScalar) { builder.AddFloat(4, defaultScalar, 0.0f); } + public static void AddDefaultVector(FlatBufferBuilder builder, Offset defaultVectorOffset) { builder.AddStruct(5, defaultVectorOffset.Value, 0); } + public static void AddDefaultColor(FlatBufferBuilder builder, Offset defaultColorOffset) { builder.AddStruct(6, defaultColorOffset.Value, 0); } + public static Offset EndPropertyBinding(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class PropertyBindingVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyField(tablePos, 4 /*Name*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 6 /*DisplayName*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 8 /*ActualType*/, 1 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertyValueType*/, 1, false) + && verifier.VerifyField(tablePos, 10 /*ExposedType*/, 1 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertyValueType*/, 1, false) + && verifier.VerifyField(tablePos, 12 /*DefaultScalar*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 14 /*DefaultVector*/, 16 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Vec4*/, 4, false) + && verifier.VerifyField(tablePos, 16 /*DefaultColor*/, 4 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Color*/, 1, false) + && verifier.VerifyTableEnd(tablePos); + } +} +public struct Marker : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static Marker GetRootAsMarker(ByteBuffer _bb) { return GetRootAsMarker(_bb, new Marker()); } + public static Marker GetRootAsMarker(ByteBuffer _bb, Marker obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public Marker __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public uint Name { get { int o = __p.__offset(4); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)0; } } + public float Progress { get { int o = __p.__offset(6); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)0.0f; } } + public float DurationProgress { get { int o = __p.__offset(8); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)0.0f; } } + + public static Offset CreateMarker(FlatBufferBuilder builder, + uint name = 0, + float progress = 0.0f, + float duration_progress = 0.0f) { + builder.StartTable(3); + Marker.AddDurationProgress(builder, duration_progress); + Marker.AddProgress(builder, progress); + Marker.AddName(builder, name); + return Marker.EndMarker(builder); + } + + public static void StartMarker(FlatBufferBuilder builder) { builder.StartTable(3); } + public static void AddName(FlatBufferBuilder builder, uint name) { builder.AddUint(0, name, 0); } + public static void AddProgress(FlatBufferBuilder builder, float progress) { builder.AddFloat(1, progress, 0.0f); } + public static void AddDurationProgress(FlatBufferBuilder builder, float durationProgress) { builder.AddFloat(2, durationProgress, 0.0f); } + public static Offset EndMarker(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class MarkerVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyField(tablePos, 4 /*Name*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 6 /*Progress*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 8 /*DurationProgress*/, 4 /*float*/, 4, false) + && verifier.VerifyTableEnd(tablePos); + } +} +public struct Metadata : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static Metadata GetRootAsMetadata(ByteBuffer _bb) { return GetRootAsMetadata(_bb, new Metadata()); } + public static Metadata GetRootAsMetadata(ByteBuffer _bb, Metadata obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public Metadata __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public uint Name { get { int o = __p.__offset(4); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public float Width { get { int o = __p.__offset(6); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)0.0f; } } + public float Height { get { int o = __p.__offset(8); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)0.0f; } } + public long DurationTicks { get { int o = __p.__offset(10); return o != 0 ? __p.bb.GetLong(o + __p.bb_pos) : (long)0; } } + public float FramesPerSecond { get { int o = __p.__offset(12); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)0.0f; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertyBinding? PropertyBindings(int j) { int o = __p.__offset(14); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertyBinding?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertyBinding()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } + public int PropertyBindingsLength { get { int o = __p.__offset(14); return o != 0 ? __p.__vector_len(o) : 0; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Marker? Markers(int j) { int o = __p.__offset(16); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Marker?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Marker()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } + public int MarkersLength { get { int o = __p.__offset(16); return o != 0 ? __p.__vector_len(o) : 0; } } + public uint ThemingPropertySet { get { int o = __p.__offset(18); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + + public static Offset CreateMetadata(FlatBufferBuilder builder, + uint name = 4294967295, + float width = 0.0f, + float height = 0.0f, + long duration_ticks = 0, + float frames_per_second = 0.0f, + VectorOffset property_bindingsOffset = default(VectorOffset), + VectorOffset markersOffset = default(VectorOffset), + uint theming_property_set = 4294967295) { + builder.StartTable(8); + Metadata.AddDurationTicks(builder, duration_ticks); + Metadata.AddThemingPropertySet(builder, theming_property_set); + Metadata.AddMarkers(builder, markersOffset); + Metadata.AddPropertyBindings(builder, property_bindingsOffset); + Metadata.AddFramesPerSecond(builder, frames_per_second); + Metadata.AddHeight(builder, height); + Metadata.AddWidth(builder, width); + Metadata.AddName(builder, name); + return Metadata.EndMetadata(builder); + } + + public static void StartMetadata(FlatBufferBuilder builder) { builder.StartTable(8); } + public static void AddName(FlatBufferBuilder builder, uint name) { builder.AddUint(0, name, 4294967295); } + public static void AddWidth(FlatBufferBuilder builder, float width) { builder.AddFloat(1, width, 0.0f); } + public static void AddHeight(FlatBufferBuilder builder, float height) { builder.AddFloat(2, height, 0.0f); } + public static void AddDurationTicks(FlatBufferBuilder builder, long durationTicks) { builder.AddLong(3, durationTicks, 0); } + public static void AddFramesPerSecond(FlatBufferBuilder builder, float framesPerSecond) { builder.AddFloat(4, framesPerSecond, 0.0f); } + public static void AddPropertyBindings(FlatBufferBuilder builder, VectorOffset propertyBindingsOffset) { builder.AddOffset(5, propertyBindingsOffset.Value, 0); } + public static VectorOffset CreatePropertyBindingsVector(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreatePropertyBindingsVectorBlock(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreatePropertyBindingsVectorBlock(FlatBufferBuilder builder, ArraySegment> data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreatePropertyBindingsVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add>(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartPropertyBindingsVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddMarkers(FlatBufferBuilder builder, VectorOffset markersOffset) { builder.AddOffset(6, markersOffset.Value, 0); } + public static VectorOffset CreateMarkersVector(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreateMarkersVectorBlock(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateMarkersVectorBlock(FlatBufferBuilder builder, ArraySegment> data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateMarkersVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add>(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartMarkersVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddThemingPropertySet(FlatBufferBuilder builder, uint themingPropertySet) { builder.AddUint(7, themingPropertySet, 4294967295); } + public static Offset EndMetadata(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class MetadataVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyField(tablePos, 4 /*Name*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 6 /*Width*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 8 /*Height*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 10 /*DurationTicks*/, 8 /*long*/, 8, false) + && verifier.VerifyField(tablePos, 12 /*FramesPerSecond*/, 4 /*float*/, 4, false) + && verifier.VerifyVectorOfTables(tablePos, 14 /*PropertyBindings*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertyBindingVerify.Verify, false) + && verifier.VerifyVectorOfTables(tablePos, 16 /*Markers*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.MarkerVerify.Verify, false) + && verifier.VerifyField(tablePos, 18 /*ThemingPropertySet*/, 4 /*uint*/, 4, false) + && verifier.VerifyTableEnd(tablePos); + } +} +public struct LottieComposition : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static LottieComposition GetRootAsLottieComposition(ByteBuffer _bb) { return GetRootAsLottieComposition(_bb, new LottieComposition()); } + public static LottieComposition GetRootAsLottieComposition(ByteBuffer _bb, LottieComposition obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public static bool LottieCompositionBufferHasIdentifier(ByteBuffer _bb) { return Table.__has_identifier(_bb, "LCMP"); } + public static bool VerifyLottieComposition(ByteBuffer _bb) {Google.FlatBuffers.Verifier verifier = new Google.FlatBuffers.Verifier(_bb); return verifier.VerifyBuffer("LCMP", false, LottieCompositionVerify.Verify); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public LottieComposition __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public ushort SchemaVersion { get { int o = __p.__offset(4); return o != 0 ? __p.bb.GetUshort(o + __p.bb_pos) : (ushort)0; } } + public ushort RequiredUapVersion { get { int o = __p.__offset(6); return o != 0 ? __p.bb.GetUshort(o + __p.bb_pos) : (ushort)0; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Metadata? Metadata { get { int o = __p.__offset(8); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Metadata?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Metadata()).__assign(__p.__indirect(o + __p.bb_pos), __p.bb) : null; } } + public uint RootVisual { get { int o = __p.__offset(10); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public string Strings(int j) { int o = __p.__offset(12); return o != 0 ? __p.__string(__p.__vector(o) + j * 4) : null; } + public int StringsLength { get { int o = __p.__offset(12); return o != 0 ? __p.__vector_len(o) : 0; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Visual? Visuals(int j) { int o = __p.__offset(14); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Visual?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Visual()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } + public int VisualsLength { get { int o = __p.__offset(14); return o != 0 ? __p.__vector_len(o) : 0; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Shape? Shapes(int j) { int o = __p.__offset(16); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Shape?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Shape()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } + public int ShapesLength { get { int o = __p.__offset(16); return o != 0 ? __p.__vector_len(o) : 0; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Geometry? Geometries(int j) { int o = __p.__offset(18); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Geometry?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Geometry()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } + public int GeometriesLength { get { int o = __p.__offset(18); return o != 0 ? __p.__vector_len(o) : 0; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CanvasGeometry? CanvasGeometries(int j) { int o = __p.__offset(20); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CanvasGeometry?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CanvasGeometry()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } + public int CanvasGeometriesLength { get { int o = __p.__offset(20); return o != 0 ? __p.__vector_len(o) : 0; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Brush? Brushes(int j) { int o = __p.__offset(22); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Brush?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Brush()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } + public int BrushesLength { get { int o = __p.__offset(22); return o != 0 ? __p.__vector_len(o) : 0; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.GradientStop? GradientStops(int j) { int o = __p.__offset(24); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.GradientStop?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.GradientStop()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } + public int GradientStopsLength { get { int o = __p.__offset(24); return o != 0 ? __p.__vector_len(o) : 0; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ViewBox? ViewBoxes(int j) { int o = __p.__offset(26); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ViewBox?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ViewBox()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } + public int ViewBoxesLength { get { int o = __p.__offset(26); return o != 0 ? __p.__vector_len(o) : 0; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Clip? Clips(int j) { int o = __p.__offset(28); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Clip?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Clip()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } + public int ClipsLength { get { int o = __p.__offset(28); return o != 0 ? __p.__vector_len(o) : 0; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Shadow? Shadows(int j) { int o = __p.__offset(30); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Shadow?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Shadow()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } + public int ShadowsLength { get { int o = __p.__offset(30); return o != 0 ? __p.__vector_len(o) : 0; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Surface? Surfaces(int j) { int o = __p.__offset(32); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Surface?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Surface()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } + public int SurfacesLength { get { int o = __p.__offset(32); return o != 0 ? __p.__vector_len(o) : 0; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Effect? Effects(int j) { int o = __p.__offset(34); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Effect?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Effect()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } + public int EffectsLength { get { int o = __p.__offset(34); return o != 0 ? __p.__vector_len(o) : 0; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Easing? Easings(int j) { int o = __p.__offset(36); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Easing?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Easing()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } + public int EasingsLength { get { int o = __p.__offset(36); return o != 0 ? __p.__vector_len(o) : 0; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Animation? Animations(int j) { int o = __p.__offset(38); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Animation?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Animation()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } + public int AnimationsLength { get { int o = __p.__offset(38); return o != 0 ? __p.__vector_len(o) : 0; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertySet? PropertySets(int j) { int o = __p.__offset(40); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertySet?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertySet()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } + public int PropertySetsLength { get { int o = __p.__offset(40); return o != 0 ? __p.__vector_len(o) : 0; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Controller? Controllers(int j) { int o = __p.__offset(42); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Controller?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Controller()).__assign(__p.__indirect(__p.__vector(o) + j * 4), __p.bb) : null; } + public int ControllersLength { get { int o = __p.__offset(42); return o != 0 ? __p.__vector_len(o) : 0; } } + public uint CustomControllers(int j) { int o = __p.__offset(44); return o != 0 ? __p.bb.GetUint(__p.__vector(o) + j * 4) : (uint)0; } + public int CustomControllersLength { get { int o = __p.__offset(44); return o != 0 ? __p.__vector_len(o) : 0; } } +#if ENABLE_SPAN_T + public Span GetCustomControllersBytes() { return __p.__vector_as_span(44, 4); } +#else + public ArraySegment? GetCustomControllersBytes() { return __p.__vector_as_arraysegment(44); } +#endif + public uint[] GetCustomControllersArray() { return __p.__vector_as_array(44); } + + public static Offset CreateLottieComposition(FlatBufferBuilder builder, + ushort schema_version = 0, + ushort required_uap_version = 0, + Offset metadataOffset = default(Offset), + uint root_visual = 4294967295, + VectorOffset stringsOffset = default(VectorOffset), + VectorOffset visualsOffset = default(VectorOffset), + VectorOffset shapesOffset = default(VectorOffset), + VectorOffset geometriesOffset = default(VectorOffset), + VectorOffset canvas_geometriesOffset = default(VectorOffset), + VectorOffset brushesOffset = default(VectorOffset), + VectorOffset gradient_stopsOffset = default(VectorOffset), + VectorOffset view_boxesOffset = default(VectorOffset), + VectorOffset clipsOffset = default(VectorOffset), + VectorOffset shadowsOffset = default(VectorOffset), + VectorOffset surfacesOffset = default(VectorOffset), + VectorOffset effectsOffset = default(VectorOffset), + VectorOffset easingsOffset = default(VectorOffset), + VectorOffset animationsOffset = default(VectorOffset), + VectorOffset property_setsOffset = default(VectorOffset), + VectorOffset controllersOffset = default(VectorOffset), + VectorOffset custom_controllersOffset = default(VectorOffset)) { + builder.StartTable(21); + LottieComposition.AddCustomControllers(builder, custom_controllersOffset); + LottieComposition.AddControllers(builder, controllersOffset); + LottieComposition.AddPropertySets(builder, property_setsOffset); + LottieComposition.AddAnimations(builder, animationsOffset); + LottieComposition.AddEasings(builder, easingsOffset); + LottieComposition.AddEffects(builder, effectsOffset); + LottieComposition.AddSurfaces(builder, surfacesOffset); + LottieComposition.AddShadows(builder, shadowsOffset); + LottieComposition.AddClips(builder, clipsOffset); + LottieComposition.AddViewBoxes(builder, view_boxesOffset); + LottieComposition.AddGradientStops(builder, gradient_stopsOffset); + LottieComposition.AddBrushes(builder, brushesOffset); + LottieComposition.AddCanvasGeometries(builder, canvas_geometriesOffset); + LottieComposition.AddGeometries(builder, geometriesOffset); + LottieComposition.AddShapes(builder, shapesOffset); + LottieComposition.AddVisuals(builder, visualsOffset); + LottieComposition.AddStrings(builder, stringsOffset); + LottieComposition.AddRootVisual(builder, root_visual); + LottieComposition.AddMetadata(builder, metadataOffset); + LottieComposition.AddRequiredUapVersion(builder, required_uap_version); + LottieComposition.AddSchemaVersion(builder, schema_version); + return LottieComposition.EndLottieComposition(builder); + } + + public static void StartLottieComposition(FlatBufferBuilder builder) { builder.StartTable(21); } + public static void AddSchemaVersion(FlatBufferBuilder builder, ushort schemaVersion) { builder.AddUshort(0, schemaVersion, 0); } + public static void AddRequiredUapVersion(FlatBufferBuilder builder, ushort requiredUapVersion) { builder.AddUshort(1, requiredUapVersion, 0); } + public static void AddMetadata(FlatBufferBuilder builder, Offset metadataOffset) { builder.AddOffset(2, metadataOffset.Value, 0); } + public static void AddRootVisual(FlatBufferBuilder builder, uint rootVisual) { builder.AddUint(3, rootVisual, 4294967295); } + public static void AddStrings(FlatBufferBuilder builder, VectorOffset stringsOffset) { builder.AddOffset(4, stringsOffset.Value, 0); } + public static VectorOffset CreateStringsVector(FlatBufferBuilder builder, StringOffset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreateStringsVectorBlock(FlatBufferBuilder builder, StringOffset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateStringsVectorBlock(FlatBufferBuilder builder, ArraySegment data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateStringsVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartStringsVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddVisuals(FlatBufferBuilder builder, VectorOffset visualsOffset) { builder.AddOffset(5, visualsOffset.Value, 0); } + public static VectorOffset CreateVisualsVector(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreateVisualsVectorBlock(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateVisualsVectorBlock(FlatBufferBuilder builder, ArraySegment> data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateVisualsVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add>(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartVisualsVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddShapes(FlatBufferBuilder builder, VectorOffset shapesOffset) { builder.AddOffset(6, shapesOffset.Value, 0); } + public static VectorOffset CreateShapesVector(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreateShapesVectorBlock(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateShapesVectorBlock(FlatBufferBuilder builder, ArraySegment> data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateShapesVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add>(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartShapesVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddGeometries(FlatBufferBuilder builder, VectorOffset geometriesOffset) { builder.AddOffset(7, geometriesOffset.Value, 0); } + public static VectorOffset CreateGeometriesVector(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreateGeometriesVectorBlock(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateGeometriesVectorBlock(FlatBufferBuilder builder, ArraySegment> data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateGeometriesVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add>(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartGeometriesVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddCanvasGeometries(FlatBufferBuilder builder, VectorOffset canvasGeometriesOffset) { builder.AddOffset(8, canvasGeometriesOffset.Value, 0); } + public static VectorOffset CreateCanvasGeometriesVector(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreateCanvasGeometriesVectorBlock(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateCanvasGeometriesVectorBlock(FlatBufferBuilder builder, ArraySegment> data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateCanvasGeometriesVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add>(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartCanvasGeometriesVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddBrushes(FlatBufferBuilder builder, VectorOffset brushesOffset) { builder.AddOffset(9, brushesOffset.Value, 0); } + public static VectorOffset CreateBrushesVector(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreateBrushesVectorBlock(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateBrushesVectorBlock(FlatBufferBuilder builder, ArraySegment> data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateBrushesVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add>(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartBrushesVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddGradientStops(FlatBufferBuilder builder, VectorOffset gradientStopsOffset) { builder.AddOffset(10, gradientStopsOffset.Value, 0); } + public static VectorOffset CreateGradientStopsVector(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreateGradientStopsVectorBlock(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateGradientStopsVectorBlock(FlatBufferBuilder builder, ArraySegment> data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateGradientStopsVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add>(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartGradientStopsVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddViewBoxes(FlatBufferBuilder builder, VectorOffset viewBoxesOffset) { builder.AddOffset(11, viewBoxesOffset.Value, 0); } + public static VectorOffset CreateViewBoxesVector(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreateViewBoxesVectorBlock(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateViewBoxesVectorBlock(FlatBufferBuilder builder, ArraySegment> data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateViewBoxesVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add>(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartViewBoxesVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddClips(FlatBufferBuilder builder, VectorOffset clipsOffset) { builder.AddOffset(12, clipsOffset.Value, 0); } + public static VectorOffset CreateClipsVector(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreateClipsVectorBlock(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateClipsVectorBlock(FlatBufferBuilder builder, ArraySegment> data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateClipsVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add>(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartClipsVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddShadows(FlatBufferBuilder builder, VectorOffset shadowsOffset) { builder.AddOffset(13, shadowsOffset.Value, 0); } + public static VectorOffset CreateShadowsVector(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreateShadowsVectorBlock(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateShadowsVectorBlock(FlatBufferBuilder builder, ArraySegment> data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateShadowsVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add>(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartShadowsVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddSurfaces(FlatBufferBuilder builder, VectorOffset surfacesOffset) { builder.AddOffset(14, surfacesOffset.Value, 0); } + public static VectorOffset CreateSurfacesVector(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreateSurfacesVectorBlock(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateSurfacesVectorBlock(FlatBufferBuilder builder, ArraySegment> data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateSurfacesVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add>(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartSurfacesVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddEffects(FlatBufferBuilder builder, VectorOffset effectsOffset) { builder.AddOffset(15, effectsOffset.Value, 0); } + public static VectorOffset CreateEffectsVector(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreateEffectsVectorBlock(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateEffectsVectorBlock(FlatBufferBuilder builder, ArraySegment> data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateEffectsVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add>(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartEffectsVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddEasings(FlatBufferBuilder builder, VectorOffset easingsOffset) { builder.AddOffset(16, easingsOffset.Value, 0); } + public static VectorOffset CreateEasingsVector(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreateEasingsVectorBlock(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateEasingsVectorBlock(FlatBufferBuilder builder, ArraySegment> data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateEasingsVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add>(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartEasingsVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddAnimations(FlatBufferBuilder builder, VectorOffset animationsOffset) { builder.AddOffset(17, animationsOffset.Value, 0); } + public static VectorOffset CreateAnimationsVector(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreateAnimationsVectorBlock(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateAnimationsVectorBlock(FlatBufferBuilder builder, ArraySegment> data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateAnimationsVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add>(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartAnimationsVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddPropertySets(FlatBufferBuilder builder, VectorOffset propertySetsOffset) { builder.AddOffset(18, propertySetsOffset.Value, 0); } + public static VectorOffset CreatePropertySetsVector(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreatePropertySetsVectorBlock(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreatePropertySetsVectorBlock(FlatBufferBuilder builder, ArraySegment> data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreatePropertySetsVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add>(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartPropertySetsVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddControllers(FlatBufferBuilder builder, VectorOffset controllersOffset) { builder.AddOffset(19, controllersOffset.Value, 0); } + public static VectorOffset CreateControllersVector(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddOffset(data[i].Value); return builder.EndVector(); } + public static VectorOffset CreateControllersVectorBlock(FlatBufferBuilder builder, Offset[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateControllersVectorBlock(FlatBufferBuilder builder, ArraySegment> data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateControllersVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add>(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartControllersVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddCustomControllers(FlatBufferBuilder builder, VectorOffset customControllersOffset) { builder.AddOffset(20, customControllersOffset.Value, 0); } + public static VectorOffset CreateCustomControllersVector(FlatBufferBuilder builder, uint[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddUint(data[i]); return builder.EndVector(); } + public static VectorOffset CreateCustomControllersVectorBlock(FlatBufferBuilder builder, uint[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateCustomControllersVectorBlock(FlatBufferBuilder builder, ArraySegment data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateCustomControllersVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartCustomControllersVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static Offset EndLottieComposition(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } + public static void FinishLottieCompositionBuffer(FlatBufferBuilder builder, Offset offset) { builder.Finish(offset.Value, "LCMP"); } + public static void FinishSizePrefixedLottieCompositionBuffer(FlatBufferBuilder builder, Offset offset) { builder.FinishSizePrefixed(offset.Value, "LCMP"); } +} + + +static public class LottieCompositionVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyField(tablePos, 4 /*SchemaVersion*/, 2 /*ushort*/, 2, false) + && verifier.VerifyField(tablePos, 6 /*RequiredUapVersion*/, 2 /*ushort*/, 2, false) + && verifier.VerifyTable(tablePos, 8 /*Metadata*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.MetadataVerify.Verify, false) + && verifier.VerifyField(tablePos, 10 /*RootVisual*/, 4 /*uint*/, 4, false) + && verifier.VerifyVectorOfStrings(tablePos, 12 /*Strings*/, false) + && verifier.VerifyVectorOfTables(tablePos, 14 /*Visuals*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.VisualVerify.Verify, false) + && verifier.VerifyVectorOfTables(tablePos, 16 /*Shapes*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ShapeVerify.Verify, false) + && verifier.VerifyVectorOfTables(tablePos, 18 /*Geometries*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.GeometryVerify.Verify, false) + && verifier.VerifyVectorOfTables(tablePos, 20 /*CanvasGeometries*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CanvasGeometryVerify.Verify, false) + && verifier.VerifyVectorOfTables(tablePos, 22 /*Brushes*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.BrushVerify.Verify, false) + && verifier.VerifyVectorOfTables(tablePos, 24 /*GradientStops*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.GradientStopVerify.Verify, false) + && verifier.VerifyVectorOfTables(tablePos, 26 /*ViewBoxes*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ViewBoxVerify.Verify, false) + && verifier.VerifyVectorOfTables(tablePos, 28 /*Clips*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ClipVerify.Verify, false) + && verifier.VerifyVectorOfTables(tablePos, 30 /*Shadows*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ShadowVerify.Verify, false) + && verifier.VerifyVectorOfTables(tablePos, 32 /*Surfaces*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.SurfaceVerify.Verify, false) + && verifier.VerifyVectorOfTables(tablePos, 34 /*Effects*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.EffectVerify.Verify, false) + && verifier.VerifyVectorOfTables(tablePos, 36 /*Easings*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.EasingVerify.Verify, false) + && verifier.VerifyVectorOfTables(tablePos, 38 /*Animations*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.AnimationVerify.Verify, false) + && verifier.VerifyVectorOfTables(tablePos, 40 /*PropertySets*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.PropertySetVerify.Verify, false) + && verifier.VerifyVectorOfTables(tablePos, 42 /*Controllers*/, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.ControllerVerify.Verify, false) + && verifier.VerifyVectorOfData(tablePos, 44 /*CustomControllers*/, 4 /*uint*/, false) + && verifier.VerifyTableEnd(tablePos); + } +} +public struct CanvasGeometry : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_25_2_10(); } + public static CanvasGeometry GetRootAsCanvasGeometry(ByteBuffer _bb) { return GetRootAsCanvasGeometry(_bb, new CanvasGeometry()); } + public static CanvasGeometry GetRootAsCanvasGeometry(ByteBuffer _bb, CanvasGeometry obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public CanvasGeometry __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CanvasGeometryKind Kind { get { int o = __p.__offset(4); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CanvasGeometryKind)__p.bb.Get(o + __p.bb_pos) : CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CanvasGeometryKind.Combination; } } + public uint A { get { int o = __p.__offset(6); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public uint B { get { int o = __p.__offset(8); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public byte CombineMode { get { int o = __p.__offset(10); return o != 0 ? __p.bb.Get(o + __p.bb_pos) : (byte)0; } } + public CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Mat3x2? Matrix { get { int o = __p.__offset(12); return o != 0 ? (CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Mat3x2?)(new CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Mat3x2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public uint Source { get { int o = __p.__offset(14); return o != 0 ? __p.bb.GetUint(o + __p.bb_pos) : (uint)4294967295; } } + public uint Geometries(int j) { int o = __p.__offset(16); return o != 0 ? __p.bb.GetUint(__p.__vector(o) + j * 4) : (uint)0; } + public int GeometriesLength { get { int o = __p.__offset(16); return o != 0 ? __p.__vector_len(o) : 0; } } +#if ENABLE_SPAN_T + public Span GetGeometriesBytes() { return __p.__vector_as_span(16, 4); } +#else + public ArraySegment? GetGeometriesBytes() { return __p.__vector_as_arraysegment(16); } +#endif + public uint[] GetGeometriesArray() { return __p.__vector_as_array(16); } + public byte FillRule { get { int o = __p.__offset(18); return o != 0 ? __p.bb.Get(o + __p.bb_pos) : (byte)0; } } + public byte Ops(int j) { int o = __p.__offset(20); return o != 0 ? __p.bb.Get(__p.__vector(o) + j * 1) : (byte)0; } + public int OpsLength { get { int o = __p.__offset(20); return o != 0 ? __p.__vector_len(o) : 0; } } +#if ENABLE_SPAN_T + public Span GetOpsBytes() { return __p.__vector_as_span(20, 1); } +#else + public ArraySegment? GetOpsBytes() { return __p.__vector_as_arraysegment(20); } +#endif + public byte[] GetOpsArray() { return __p.__vector_as_array(20); } + public float Operands(int j) { int o = __p.__offset(22); return o != 0 ? __p.bb.GetFloat(__p.__vector(o) + j * 4) : (float)0; } + public int OperandsLength { get { int o = __p.__offset(22); return o != 0 ? __p.__vector_len(o) : 0; } } +#if ENABLE_SPAN_T + public Span GetOperandsBytes() { return __p.__vector_as_span(22, 4); } +#else + public ArraySegment? GetOperandsBytes() { return __p.__vector_as_arraysegment(22); } +#endif + public float[] GetOperandsArray() { return __p.__vector_as_array(22); } + public float X { get { int o = __p.__offset(24); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)0.0f; } } + public float Y { get { int o = __p.__offset(26); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)0.0f; } } + public float W { get { int o = __p.__offset(28); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)0.0f; } } + public float H { get { int o = __p.__offset(30); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)0.0f; } } + public float RadiusX { get { int o = __p.__offset(32); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)0.0f; } } + public float RadiusY { get { int o = __p.__offset(34); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)0.0f; } } + + public static void StartCanvasGeometry(FlatBufferBuilder builder) { builder.StartTable(16); } + public static void AddKind(FlatBufferBuilder builder, CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CanvasGeometryKind kind) { builder.AddByte(0, (byte)kind, 0); } + public static void AddA(FlatBufferBuilder builder, uint a) { builder.AddUint(1, a, 4294967295); } + public static void AddB(FlatBufferBuilder builder, uint b) { builder.AddUint(2, b, 4294967295); } + public static void AddCombineMode(FlatBufferBuilder builder, byte combineMode) { builder.AddByte(3, combineMode, 0); } + public static void AddMatrix(FlatBufferBuilder builder, Offset matrixOffset) { builder.AddStruct(4, matrixOffset.Value, 0); } + public static void AddSource(FlatBufferBuilder builder, uint source) { builder.AddUint(5, source, 4294967295); } + public static void AddGeometries(FlatBufferBuilder builder, VectorOffset geometriesOffset) { builder.AddOffset(6, geometriesOffset.Value, 0); } + public static VectorOffset CreateGeometriesVector(FlatBufferBuilder builder, uint[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddUint(data[i]); return builder.EndVector(); } + public static VectorOffset CreateGeometriesVectorBlock(FlatBufferBuilder builder, uint[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateGeometriesVectorBlock(FlatBufferBuilder builder, ArraySegment data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateGeometriesVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartGeometriesVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddFillRule(FlatBufferBuilder builder, byte fillRule) { builder.AddByte(7, fillRule, 0); } + public static void AddOps(FlatBufferBuilder builder, VectorOffset opsOffset) { builder.AddOffset(8, opsOffset.Value, 0); } + public static VectorOffset CreateOpsVector(FlatBufferBuilder builder, byte[] data) { builder.StartVector(1, data.Length, 1); for (int i = data.Length - 1; i >= 0; i--) builder.AddByte(data[i]); return builder.EndVector(); } + public static VectorOffset CreateOpsVectorBlock(FlatBufferBuilder builder, byte[] data) { builder.StartVector(1, data.Length, 1); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateOpsVectorBlock(FlatBufferBuilder builder, ArraySegment data) { builder.StartVector(1, data.Count, 1); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateOpsVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartOpsVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(1, numElems, 1); } + public static void AddOperands(FlatBufferBuilder builder, VectorOffset operandsOffset) { builder.AddOffset(9, operandsOffset.Value, 0); } + public static VectorOffset CreateOperandsVector(FlatBufferBuilder builder, float[] data) { builder.StartVector(4, data.Length, 4); for (int i = data.Length - 1; i >= 0; i--) builder.AddFloat(data[i]); return builder.EndVector(); } + public static VectorOffset CreateOperandsVectorBlock(FlatBufferBuilder builder, float[] data) { builder.StartVector(4, data.Length, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateOperandsVectorBlock(FlatBufferBuilder builder, ArraySegment data) { builder.StartVector(4, data.Count, 4); builder.Add(data); return builder.EndVector(); } + public static VectorOffset CreateOperandsVectorBlock(FlatBufferBuilder builder, IntPtr dataPtr, int sizeInBytes) { builder.StartVector(1, sizeInBytes, 1); builder.Add(dataPtr, sizeInBytes); return builder.EndVector(); } + public static void StartOperandsVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 4); } + public static void AddX(FlatBufferBuilder builder, float x) { builder.AddFloat(10, x, 0.0f); } + public static void AddY(FlatBufferBuilder builder, float y) { builder.AddFloat(11, y, 0.0f); } + public static void AddW(FlatBufferBuilder builder, float w) { builder.AddFloat(12, w, 0.0f); } + public static void AddH(FlatBufferBuilder builder, float h) { builder.AddFloat(13, h, 0.0f); } + public static void AddRadiusX(FlatBufferBuilder builder, float radiusX) { builder.AddFloat(14, radiusX, 0.0f); } + public static void AddRadiusY(FlatBufferBuilder builder, float radiusY) { builder.AddFloat(15, radiusY, 0.0f); } + public static Offset EndCanvasGeometry(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class CanvasGeometryVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyField(tablePos, 4 /*Kind*/, 1 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.CanvasGeometryKind*/, 1, false) + && verifier.VerifyField(tablePos, 6 /*A*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 8 /*B*/, 4 /*uint*/, 4, false) + && verifier.VerifyField(tablePos, 10 /*CombineMode*/, 1 /*byte*/, 1, false) + && verifier.VerifyField(tablePos, 12 /*Matrix*/, 24 /*CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema.Mat3x2*/, 4, false) + && verifier.VerifyField(tablePos, 14 /*Source*/, 4 /*uint*/, 4, false) + && verifier.VerifyVectorOfData(tablePos, 16 /*Geometries*/, 4 /*uint*/, false) + && verifier.VerifyField(tablePos, 18 /*FillRule*/, 1 /*byte*/, 1, false) + && verifier.VerifyVectorOfData(tablePos, 20 /*Ops*/, 1 /*byte*/, false) + && verifier.VerifyVectorOfData(tablePos, 22 /*Operands*/, 4 /*float*/, false) + && verifier.VerifyField(tablePos, 24 /*X*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 26 /*Y*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 28 /*W*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 30 /*H*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 32 /*RadiusX*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 34 /*RadiusY*/, 4 /*float*/, 4, false) + && verifier.VerifyTableEnd(tablePos); + } +} + +} diff --git a/source/CompDataFlatbuffer/Schema.cs b/source/CompDataFlatbuffer/Schema.cs deleted file mode 100644 index 84b3451a..00000000 --- a/source/CompDataFlatbuffer/Schema.cs +++ /dev/null @@ -1,385 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable enable - -namespace CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer -{ - /// - /// Field ids, enum values and constants of the lottie_comp.fbs schema. - /// These must be kept in sync with lottie_comp.fbs; field ids are the - /// 0-based declaration order of the fields of each table. - /// - static class Schema - { - /// - /// The version of the schema that this code reads and writes. Bumped for - /// every additive change to lottie_comp.fbs. - /// - internal const ushort Version = 1; - - /// - /// The 4 character identifier stored at offset 4 of every buffer. - /// - internal const string FileIdentifier = "LCMP"; - - /// - /// The file extension used for serialized compositions. - /// - internal const string FileExtension = ".lcomp"; - - /// - /// The value of an index or reference field that refers to nothing. - /// - internal const uint NullIndex = 0xFFFFFFFF; - - /// - /// The number of bits used to store the index part of an object reference. - /// - internal const int ObjectReferenceIndexBits = 28; - - internal static uint PackObjectReference(ObjectCategory category, int index) - => ((uint)category << ObjectReferenceIndexBits) | (uint)index; - - internal static ObjectCategory UnpackCategory(uint reference) - => (ObjectCategory)(reference >> ObjectReferenceIndexBits); - - internal static int UnpackIndex(uint reference) - => (int)(reference & ((1u << ObjectReferenceIndexBits) - 1)); - - internal static class Animator - { - internal const int FieldCount = 3; - internal const int Property = 0; - internal const int Animation = 1; - internal const int Controller = 2; - } - - internal static class CompObj - { - internal const int FieldCount = 3; - internal const int Comment = 0; - internal const int Animators = 1; - internal const int Properties = 2; - } - - internal static class PropertyValue - { - internal const int FieldCount = 5; - internal const int Name = 0; - internal const int Type = 1; - internal const int Scalar = 2; - internal const int Vector = 3; - internal const int Color = 4; - } - - internal static class ReferenceParameter - { - internal const int FieldCount = 2; - internal const int Name = 0; - internal const int Target = 1; - } - - internal static class SourceParameter - { - internal const int FieldCount = 2; - internal const int Name = 0; - internal const int Brush = 1; - } - - internal static class KeyFrame - { - internal const int FieldCount = 8; - internal const int Progress = 0; - internal const int Easing = 1; - internal const int Kind = 2; - internal const int Expression = 3; - internal const int Scalar = 4; - internal const int Vector = 5; - internal const int Color = 6; - internal const int Path = 7; - } - - internal static class Visual - { - internal const int FieldCount = 18; - internal const int Base = 0; - internal const int Kind = 1; - internal const int BorderMode = 2; - internal const int CenterPoint = 3; - internal const int Clip = 4; - internal const int IsVisible = 5; - internal const int Offset = 6; - internal const int Opacity = 7; - internal const int RotationAngleInDegrees = 8; - internal const int RotationAxis = 9; - internal const int Scale = 10; - internal const int Size = 11; - internal const int TransformMatrix = 12; - internal const int Children = 13; - internal const int Shapes = 14; - internal const int ViewBox = 15; - internal const int Brush = 16; - internal const int Shadow = 17; - } - - internal static class Shape - { - internal const int FieldCount = 20; - internal const int Base = 0; - internal const int Kind = 1; - internal const int CenterPoint = 2; - internal const int Offset = 3; - internal const int RotationAngleInDegrees = 4; - internal const int Scale = 5; - internal const int TransformMatrix = 6; - internal const int Shapes = 7; - internal const int FillBrush = 8; - internal const int StrokeBrush = 9; - internal const int Geometry = 10; - internal const int IsStrokeNonScaling = 11; - internal const int StrokeDashOffset = 12; - internal const int StrokeDashArray = 13; - internal const int StrokeDashCap = 14; - internal const int StrokeStartCap = 15; - internal const int StrokeEndCap = 16; - internal const int StrokeLineJoin = 17; - internal const int StrokeMiterLimit = 18; - internal const int StrokeThickness = 19; - } - - internal static class Geometry - { - internal const int FieldCount = 11; - internal const int Base = 0; - internal const int Kind = 1; - internal const int TrimStart = 2; - internal const int TrimEnd = 3; - internal const int TrimOffset = 4; - internal const int Path = 5; - internal const int Offset = 6; - internal const int Size = 7; - internal const int CornerRadius = 8; - internal const int Center = 9; - internal const int Radius = 10; - } - - internal static class Brush - { - internal const int FieldCount = 23; - internal const int Base = 0; - internal const int Kind = 1; - internal const int Color = 2; - internal const int AnchorPoint = 3; - internal const int CenterPoint = 4; - internal const int ColorStops = 5; - internal const int ExtendMode = 6; - internal const int InterpolationSpace = 7; - internal const int MappingMode = 8; - internal const int Offset = 9; - internal const int RotationAngleInDegrees = 10; - internal const int Scale = 11; - internal const int TransformMatrix = 12; - internal const int StartPoint = 13; - internal const int EndPoint = 14; - internal const int EllipseCenter = 15; - internal const int EllipseRadius = 16; - internal const int GradientOriginOffset = 17; - internal const int Surface = 18; - internal const int Source = 19; - internal const int Mask = 20; - internal const int Effect = 21; - internal const int SourceParameters = 22; - } - - internal static class GradientStop - { - internal const int FieldCount = 3; - internal const int Base = 0; - internal const int Color = 1; - internal const int Offset = 2; - } - - internal static class ViewBox - { - internal const int FieldCount = 2; - internal const int Base = 0; - internal const int Size = 1; - } - - internal static class Clip - { - internal const int FieldCount = 9; - internal const int Base = 0; - internal const int Kind = 1; - internal const int CenterPoint = 2; - internal const int Scale = 3; - internal const int LeftInset = 4; - internal const int RightInset = 5; - internal const int TopInset = 6; - internal const int BottomInset = 7; - internal const int Geometry = 8; - } - - internal static class Shadow - { - internal const int FieldCount = 8; - internal const int Base = 0; - internal const int Kind = 1; - internal const int BlurRadius = 2; - internal const int Color = 3; - internal const int Mask = 4; - internal const int Offset = 5; - internal const int Opacity = 6; - internal const int SourcePolicy = 7; - } - - internal static class Surface - { - internal const int FieldCount = 7; - internal const int Base = 0; - internal const int Kind = 1; - internal const int SourceVisual = 2; - internal const int SourceSize = 3; - internal const int SourceOffset = 4; - internal const int Uri = 5; - internal const int Bytes = 6; - } - - internal static class Effect - { - internal const int FieldCount = 5; - internal const int Base = 0; - internal const int Kind = 1; - internal const int Sources = 2; - internal const int Mode = 3; - internal const int BlurAmount = 4; - } - - internal static class Easing - { - internal const int FieldCount = 9; - internal const int Base = 0; - internal const int Kind = 1; - internal const int ControlPoint1 = 2; - internal const int ControlPoint2 = 3; - internal const int StepCount = 4; - internal const int InitialStep = 5; - internal const int FinalStep = 6; - internal const int IsInitialStepSingleFrame = 7; - internal const int IsFinalStepSingleFrame = 8; - } - - internal static class Animation - { - internal const int FieldCount = 8; - internal const int Base = 0; - internal const int Kind = 1; - internal const int Target = 2; - internal const int ReferenceParameters = 3; - internal const int Expression = 4; - internal const int DurationTicks = 5; - internal const int KeyFrames = 6; - internal const int InterpolationColorSpace = 7; - } - - internal static class PropertySet - { - internal const int FieldCount = 3; - internal const int Base = 0; - internal const int Owner = 1; - internal const int Values = 2; - } - - internal static class Controller - { - internal const int FieldCount = 5; - internal const int Base = 0; - internal const int TargetObject = 1; - internal const int TargetProperty = 2; - internal const int IsPaused = 3; - internal const int IsCustom = 4; - } - - internal static class PropertyBinding - { - internal const int FieldCount = 7; - internal const int Name = 0; - internal const int DisplayName = 1; - internal const int ActualType = 2; - internal const int ExposedType = 3; - internal const int DefaultScalar = 4; - internal const int DefaultVector = 5; - internal const int DefaultColor = 6; - } - - internal static class Marker - { - internal const int FieldCount = 3; - internal const int Name = 0; - internal const int Progress = 1; - internal const int DurationProgress = 2; - } - - internal static class Metadata - { - internal const int FieldCount = 8; - internal const int Name = 0; - internal const int Width = 1; - internal const int Height = 2; - internal const int DurationTicks = 3; - internal const int FramesPerSecond = 4; - internal const int PropertyBindings = 5; - internal const int Markers = 6; - internal const int ThemingPropertySet = 7; - } - - internal static class Composition - { - internal const int FieldCount = 21; - internal const int SchemaVersion = 0; - internal const int RequiredUapVersion = 1; - internal const int Metadata = 2; - internal const int RootVisual = 3; - internal const int Strings = 4; - internal const int Visuals = 5; - internal const int Shapes = 6; - internal const int Geometries = 7; - internal const int CanvasGeometries = 8; - internal const int Brushes = 9; - internal const int GradientStops = 10; - internal const int ViewBoxes = 11; - internal const int Clips = 12; - internal const int Shadows = 13; - internal const int Surfaces = 14; - internal const int Effects = 15; - internal const int Easings = 16; - internal const int Animations = 17; - internal const int PropertySets = 18; - internal const int Controllers = 19; - internal const int CustomControllers = 20; - } - - internal static class CanvasGeometry - { - internal const int FieldCount = 16; - internal const int Kind = 0; - internal const int A = 1; - internal const int B = 2; - internal const int CombineMode = 3; - internal const int Matrix = 4; - internal const int Source = 5; - internal const int Geometries = 6; - internal const int FillRule = 7; - internal const int Ops = 8; - internal const int Operands = 9; - internal const int X = 10; - internal const int Y = 11; - internal const int W = 12; - internal const int H = 13; - internal const int RadiusX = 14; - internal const int RadiusY = 15; - } - } -} diff --git a/source/CompDataFlatbuffer/SchemaEnums.cs b/source/CompDataFlatbuffer/SchemaEnums.cs deleted file mode 100644 index ddafef6d..00000000 --- a/source/CompDataFlatbuffer/SchemaEnums.cs +++ /dev/null @@ -1,139 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable enable - -namespace CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer -{ - /// - /// Identifies the node vector that a packed object reference points into. - /// Must be kept in sync with the ObjectCategory enum in lottie_comp.fbs. - /// - enum ObjectCategory : byte - { - Visual = 0, - Shape = 1, - Geometry = 2, - Brush = 3, - Animation = 4, - Easing = 5, - PropertySet = 6, - Surface = 7, - Clip = 8, - Controller = 9, - Shadow = 10, - Effect = 11, - GradientStop = 12, - ViewBox = 13, - } - - enum VisualKind : byte - { - Container = 0, - Sprite = 1, - Shape = 2, - Layer = 3, - } - - enum ShapeKind : byte - { - Container = 0, - Sprite = 1, - } - - enum GeometryKind : byte - { - Path = 0, - Rectangle = 1, - RoundedRectangle = 2, - Ellipse = 3, - } - - enum BrushKind : byte - { - Color = 0, - LinearGradient = 1, - RadialGradient = 2, - Surface = 3, - Mask = 4, - Effect = 5, - } - - enum ClipKind : byte - { - Inset = 0, - Geometric = 1, - } - - enum ShadowKind : byte - { - Drop = 0, - } - - enum SurfaceKind : byte - { - VisualSurface = 0, - LoadedImageFromUri = 1, - LoadedImageFromStream = 2, - } - - enum EffectKind : byte - { - Composite = 0, - GaussianBlur = 1, - } - - enum EasingKind : byte - { - Linear = 0, - CubicBezier = 1, - Step = 2, - } - - enum AnimationKind : byte - { - Scalar = 0, - Vector2 = 1, - Vector3 = 2, - Vector4 = 3, - Color = 4, - Boolean = 5, - Path = 6, - Expression = 7, - } - - enum KeyFrameKind : byte - { - Value = 0, - Expression = 1, - } - - enum CanvasGeometryKind : byte - { - Combination = 0, - Ellipse = 1, - Group = 2, - Path = 3, - RoundedRectangle = 4, - TransformedGeometry = 5, - } - - /// - /// The opcodes of the flattened CanvasPathBuilder command stream. - /// - enum PathOp : byte - { - /// Consumes 2 operands: x, y. - BeginFigure = 0, - - /// Consumes 1 operand: the CanvasFigureLoop. - EndFigure = 1, - - /// Consumes 2 operands: x, y. - AddLine = 2, - - /// Consumes 6 operands: cp1.x, cp1.y, cp2.x, cp2.y, end.x, end.y. - AddCubicBezier = 3, - } -} From a5b26958513ab7777a3d0e15d7e81499b8d89fee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 31 Aug 2026 18:06:20 +0000 Subject: [PATCH 03/39] Add GraphIndexer: deterministic reference-identity walk of the WinCompData graph Co-authored-by: jonwis <18537118+jonwis@users.noreply.github.com> --- .../CompDataFlatbuffer.dll.csproj | 1 + .../CompDataFlatbuffer.projitems | 1 + source/CompDataFlatbuffer/GraphIndexer.cs | 562 ++++++++++++++++++ 3 files changed, 564 insertions(+) create mode 100644 source/CompDataFlatbuffer/GraphIndexer.cs diff --git a/dlls/CompDataFlatbuffer/CompDataFlatbuffer.dll.csproj b/dlls/CompDataFlatbuffer/CompDataFlatbuffer.dll.csproj index cb3d2170..08da3387 100644 --- a/dlls/CompDataFlatbuffer/CompDataFlatbuffer.dll.csproj +++ b/dlls/CompDataFlatbuffer/CompDataFlatbuffer.dll.csproj @@ -19,6 +19,7 @@ + diff --git a/source/CompDataFlatbuffer/CompDataFlatbuffer.projitems b/source/CompDataFlatbuffer/CompDataFlatbuffer.projitems index add80aa3..57f9979a 100644 --- a/source/CompDataFlatbuffer/CompDataFlatbuffer.projitems +++ b/source/CompDataFlatbuffer/CompDataFlatbuffer.projitems @@ -10,6 +10,7 @@ + + <_SourceDir>..\..\source\ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/CompDataFlatbuffer.Tests/CompositionTreeDumper.cs b/tests/CompDataFlatbuffer.Tests/CompositionTreeDumper.cs new file mode 100644 index 00000000..ccc2fab0 --- /dev/null +++ b/tests/CompDataFlatbuffer.Tests/CompositionTreeDumper.cs @@ -0,0 +1,851 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Numerics; +using System.Text; +using CommunityToolkit.WinUI.Lottie.WinCompData; +using CommunityToolkit.WinUI.Lottie.WinCompData.MetaData; +using CommunityToolkit.WinUI.Lottie.WinCompData.Mgce; +using CommunityToolkit.WinUI.Lottie.WinCompData.Mgcg; +using CommunityToolkit.WinUI.Lottie.WinUIXamlMediaData; +using Expressions = CommunityToolkit.WinUI.Lottie.WinCompData.Expressions; +using Wui = CommunityToolkit.WinUI.Lottie.WinCompData.Wui; + +namespace CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Tests +{ + /// + /// Renders a WinCompData graph as canonical text. + /// + /// + /// Two graphs produce identical text if and only if they are structurally + /// equivalent, including the sharing of nodes: each node is given an id the first + /// time it is reached by a depth first walk from the root, and every later reference + /// to it is written as that id. So a graph in which two parents share one child + /// cannot produce the same text as one in which they have a child each. + /// + /// This is the oracle for the round trip tests. It is deliberately independent of + /// both the serializer and the deserializer, and it fails loudly on a node type it + /// does not know about, so that a type added to WinCompData cannot silently escape + /// comparison. + /// + static class CompositionTreeDumper + { + /// + /// Renders a graph as canonical text. + /// + /// The root of the graph. + /// The canonical text of the graph. + public static string Dump(Visual root) + { + var writer = new Writer(); + writer.WriteVisual(root); + return writer.ToString(); + } + + sealed class Writer + { + readonly StringBuilder _builder = new StringBuilder(); + + // Ids are assigned per object, in the order that the objects are first + // reached. Reference equality is what matters here: two distinct objects with + // identical contents are different nodes and must get different ids. + readonly Dictionary _ids = + new Dictionary(ReferenceEqualityComparer.Instance); + + int _indent; + + public override string ToString() => _builder.ToString(); + + public void WriteVisual(Visual? value) + { + if (WriteNodeHeader(value, "Visual")) + { + return; + } + + WriteCompositionObject(value!); + + WriteOptional("BorderMode", value!.BorderMode); + WriteOptional("CenterPoint", value.CenterPoint); + WriteChild("Clip", value.Clip, WriteClip); + WriteOptional("IsVisible", value.IsVisible); + WriteOptional("Offset", value.Offset); + WriteOptional("Opacity", value.Opacity); + WriteOptional("RotationAngleInDegrees", value.RotationAngleInDegrees); + WriteOptional("RotationAxis", value.RotationAxis); + WriteOptional("Scale", value.Scale); + WriteOptional("Size", value.Size); + WriteOptional("TransformMatrix", value.TransformMatrix); + + if (value is ContainerVisual container) + { + WriteList("Children", container.Children, WriteVisual); + } + + switch (value) + { + case ShapeVisual shapeVisual: + WriteList("Shapes", shapeVisual.Shapes, WriteShape); + WriteChild("ViewBox", shapeVisual.ViewBox, WriteViewBox); + break; + case SpriteVisual spriteVisual: + WriteChild("Brush", spriteVisual.Brush, WriteBrush); + WriteChild("Shadow", spriteVisual.Shadow, WriteShadow); + break; + case LayerVisual layerVisual: + WriteChild("Shadow", layerVisual.Shadow, WriteShadow); + break; + case ContainerVisual _: + break; + default: + throw new InvalidOperationException($"Unexpected visual {value.Type}."); + } + + _indent--; + } + + public void WriteShape(CompositionShape? value) + { + if (WriteNodeHeader(value, "Shape")) + { + return; + } + + WriteCompositionObject(value!); + + WriteOptional("CenterPoint", value!.CenterPoint); + WriteOptional("Offset", value.Offset); + WriteOptional("RotationAngleInDegrees", value.RotationAngleInDegrees); + WriteOptional("Scale", value.Scale); + WriteOptional("TransformMatrix", value.TransformMatrix); + + switch (value) + { + case CompositionContainerShape containerShape: + WriteList("Shapes", containerShape.Shapes, WriteShape); + break; + case CompositionSpriteShape spriteShape: + WriteChild("FillBrush", spriteShape.FillBrush, WriteBrush); + WriteChild("StrokeBrush", spriteShape.StrokeBrush, WriteBrush); + WriteChild("Geometry", spriteShape.Geometry, WriteGeometry); + WriteOptional("IsStrokeNonScaling", spriteShape.IsStrokeNonScaling); + WriteOptional("StrokeDashOffset", spriteShape.StrokeDashOffset); + WriteFloats("StrokeDashArray", spriteShape.StrokeDashArray); + WriteOptional("StrokeDashCap", spriteShape.StrokeDashCap); + WriteOptional("StrokeStartCap", spriteShape.StrokeStartCap); + WriteOptional("StrokeEndCap", spriteShape.StrokeEndCap); + WriteOptional("StrokeLineJoin", spriteShape.StrokeLineJoin); + WriteOptional("StrokeMiterLimit", spriteShape.StrokeMiterLimit); + WriteOptional("StrokeThickness", spriteShape.StrokeThickness); + break; + default: + throw new InvalidOperationException($"Unexpected shape {value.Type}."); + } + + _indent--; + } + + public void WriteGeometry(CompositionGeometry? value) + { + if (WriteNodeHeader(value, "Geometry")) + { + return; + } + + WriteCompositionObject(value!); + + WriteOptional("TrimStart", value!.TrimStart); + WriteOptional("TrimEnd", value.TrimEnd); + WriteOptional("TrimOffset", value.TrimOffset); + + switch (value) + { + case CompositionPathGeometry pathGeometry: + WriteChild( + "Path", + pathGeometry.Path is null ? null : (CanvasGeometry)pathGeometry.Path.Source, + WriteCanvasGeometry); + break; + case CompositionRoundedRectangleGeometry roundedRectangle: + WriteOptional("Offset", roundedRectangle.Offset); + WriteOptional("Size", roundedRectangle.Size); + Write("CornerRadius", roundedRectangle.CornerRadius); + break; + case CompositionRectangleGeometry rectangle: + WriteOptional("Offset", rectangle.Offset); + WriteOptional("Size", rectangle.Size); + break; + case CompositionEllipseGeometry ellipse: + Write("Center", ellipse.Center); + Write("Radius", ellipse.Radius); + break; + default: + throw new InvalidOperationException($"Unexpected geometry {value.Type}."); + } + + _indent--; + } + + public void WriteBrush(CompositionBrush? value) + { + if (WriteNodeHeader(value, "Brush")) + { + return; + } + + WriteCompositionObject(value!); + + switch (value) + { + case CompositionColorBrush colorBrush: + WriteOptional("Color", colorBrush.Color); + break; + case CompositionMaskBrush maskBrush: + WriteChild("Source", maskBrush.Source, WriteBrush); + WriteChild("Mask", maskBrush.Mask, WriteBrush); + break; + case CompositionSurfaceBrush surfaceBrush: + WriteChild("Surface", surfaceBrush.Surface, WriteSurface); + break; + case CompositionEffectBrush effectBrush: + var effect = effectBrush.GetEffectFactory().Effect; + WriteChild("Effect", effect, WriteEffect); + + foreach (var source in effect.Sources) + { + WriteChild( + $"Source({source.Name})", + effectBrush.GetSourceParameter(source.Name), + WriteBrush); + } + + break; + case CompositionLinearGradientBrush _: + case CompositionRadialGradientBrush _: + break; + default: + throw new InvalidOperationException($"Unexpected brush {value!.Type}."); + } + + if (value is CompositionGradientBrush gradientBrush) + { + WriteOptional("AnchorPoint", gradientBrush.AnchorPoint); + WriteOptional("CenterPoint", gradientBrush.CenterPoint); + WriteList("ColorStops", gradientBrush.ColorStops, WriteGradientStop); + WriteOptional("ExtendMode", gradientBrush.ExtendMode); + WriteOptional("InterpolationSpace", gradientBrush.InterpolationSpace); + WriteOptional("MappingMode", gradientBrush.MappingMode); + WriteOptional("Offset", gradientBrush.Offset); + WriteOptional("RotationAngleInDegrees", gradientBrush.RotationAngleInDegrees); + WriteOptional("Scale", gradientBrush.Scale); + WriteOptional("TransformMatrix", gradientBrush.TransformMatrix); + + switch (gradientBrush) + { + case CompositionLinearGradientBrush linear: + WriteOptional("StartPoint", linear.StartPoint); + WriteOptional("EndPoint", linear.EndPoint); + break; + case CompositionRadialGradientBrush radial: + WriteOptional("EllipseCenter", radial.EllipseCenter); + WriteOptional("EllipseRadius", radial.EllipseRadius); + WriteOptional("GradientOriginOffset", radial.GradientOriginOffset); + break; + } + } + + _indent--; + } + + public void WriteGradientStop(CompositionColorGradientStop? value) + { + if (WriteNodeHeader(value, "GradientStop")) + { + return; + } + + WriteCompositionObject(value!); + Write("Color", value!.Color); + Write("Offset", value.Offset); + _indent--; + } + + public void WriteViewBox(CompositionViewBox? value) + { + if (WriteNodeHeader(value, "ViewBox")) + { + return; + } + + WriteCompositionObject(value!); + Write("Size", value!.Size); + _indent--; + } + + public void WriteClip(CompositionClip? value) + { + if (WriteNodeHeader(value, "Clip")) + { + return; + } + + WriteCompositionObject(value!); + WriteOptional("CenterPoint", value!.CenterPoint); + WriteOptional("Scale", value.Scale); + + switch (value) + { + case InsetClip inset: + WriteOptional("LeftInset", inset.LeftInset); + WriteOptional("RightInset", inset.RightInset); + WriteOptional("TopInset", inset.TopInset); + WriteOptional("BottomInset", inset.BottomInset); + break; + case CompositionGeometricClip geometric: + WriteChild("Geometry", geometric.Geometry, WriteGeometry); + break; + default: + throw new InvalidOperationException($"Unexpected clip {value.Type}."); + } + + _indent--; + } + + public void WriteShadow(CompositionShadow? value) + { + if (WriteNodeHeader(value, "Shadow")) + { + return; + } + + WriteCompositionObject(value!); + + switch (value) + { + case DropShadow dropShadow: + WriteOptional("BlurRadius", dropShadow.BlurRadius); + WriteOptional("Color", dropShadow.Color); + WriteChild("Mask", dropShadow.Mask, WriteBrush); + WriteOptional("Offset", dropShadow.Offset); + WriteOptional("Opacity", dropShadow.Opacity); + WriteOptional("SourcePolicy", dropShadow.SourcePolicy); + break; + default: + throw new InvalidOperationException($"Unexpected shadow {value!.Type}."); + } + + _indent--; + } + + public void WriteSurface(ICompositionSurface? value) + { + if (WriteNodeHeader(value, "Surface")) + { + return; + } + + switch (value) + { + case CompositionVisualSurface visualSurface: + WriteCompositionObject(visualSurface); + WriteChild("SourceVisual", visualSurface.SourceVisual, WriteVisual); + WriteOptional("SourceSize", visualSurface.SourceSize); + WriteOptional("SourceOffset", visualSurface.SourceOffset); + break; + case LoadedImageSurfaceFromUri fromUri: + WriteLine($"Kind: FromUri"); + WriteLine($"Uri: {fromUri.Uri}"); + break; + case LoadedImageSurfaceFromStream fromStream: + WriteLine($"Kind: FromStream"); + WriteLine($"Bytes: {Convert.ToBase64String(fromStream.Bytes)}"); + break; + default: + throw new InvalidOperationException($"Unexpected surface {value!.GetType().Name}."); + } + + _indent--; + } + + public void WriteEffect(GraphicsEffectBase? value) + { + if (WriteNodeHeader(value, "Effect")) + { + return; + } + + WriteLine($"Sources: {string.Join(", ", value!.Sources.Select(s => s.Name))}"); + + switch (value) + { + case CompositeEffect composite: + Write("Mode", composite.Mode); + break; + case GaussianBlurEffect blur: + Write("BlurAmount", blur.BlurAmount); + break; + default: + throw new InvalidOperationException($"Unexpected effect {value.Type}."); + } + + _indent--; + } + + public void WriteEasing(CompositionEasingFunction? value) + { + if (WriteNodeHeader(value, "Easing")) + { + return; + } + + WriteCompositionObject(value!); + + switch (value) + { + case LinearEasingFunction _: + break; + case CubicBezierEasingFunction cubicBezier: + Write("ControlPoint1", cubicBezier.ControlPoint1); + Write("ControlPoint2", cubicBezier.ControlPoint2); + break; + case StepEasingFunction step: + WriteOptional("StepCount", step.StepCount); + WriteOptional("InitialStep", step.InitialStep); + WriteOptional("FinalStep", step.FinalStep); + WriteOptional("IsInitialStepSingleFrame", step.IsInitialStepSingleFrame); + WriteOptional("IsFinalStepSingleFrame", step.IsFinalStepSingleFrame); + break; + default: + throw new InvalidOperationException($"Unexpected easing {value!.Type}."); + } + + _indent--; + } + + public void WriteAnimation(CompositionAnimation? value) + { + if (WriteNodeHeader(value, "Animation")) + { + return; + } + + WriteCompositionObject(value!); + WriteLine($"Target: {Text(value!.Target)}"); + + // ReferenceParameters is a dictionary, so it is sorted to make the dump + // independent of hash ordering. + foreach (var parameter in value.ReferenceParameters.OrderBy(p => p.Key, StringComparer.Ordinal)) + { + WriteChild($"Reference({parameter.Key})", parameter.Value, WriteCompositionObjectReference); + } + + switch (value) + { + case ExpressionAnimation expression: + // Expressions are compared as text because that is how they are + // stored in the buffer, and how Windows.UI.Composition accepts them. + WriteLine($"Expression: {expression.Expression.ToText()}"); + break; + case KeyFrameAnimation_ keyFrameAnimation: + WriteLine($"Duration: {keyFrameAnimation.Duration.Ticks}"); + + if (value is ColorKeyFrameAnimation color) + { + Write("InterpolationColorSpace", color.InterpolationColorSpace); + } + + foreach (var keyFrame in keyFrameAnimation.KeyFrames) + { + WriteKeyFrame(keyFrame); + } + + break; + default: + throw new InvalidOperationException($"Unexpected animation {value.Type}."); + } + + _indent--; + } + + void WriteKeyFrame(KeyFrameAnimation_.KeyFrame value) + { + WriteLine($"KeyFrame @{Text(value.Progress)}"); + _indent++; + WriteChild("Easing", value.Easing, WriteEasing); + + switch (value) + { + case KeyFrameAnimation_.ExpressionKeyFrame expression: + WriteLine($"Expression: {expression.Expression.ToText()}"); + break; + case KeyFrameAnimation.ValueKeyFrame scalar: + WriteLine($"Value: {Text(scalar.Value)}"); + break; + case KeyFrameAnimation.ValueKeyFrame boolean: + WriteLine($"Value: {boolean.Value}"); + break; + case KeyFrameAnimation.ValueKeyFrame vector2: + WriteLine($"Value: {Text(vector2.Value)}"); + break; + case KeyFrameAnimation.ValueKeyFrame vector3: + WriteLine($"Value: {Text(vector3.Value)}"); + break; + case KeyFrameAnimation.ValueKeyFrame vector4: + WriteLine($"Value: {Text(vector4.Value)}"); + break; + case KeyFrameAnimation.ValueKeyFrame color: + WriteLine($"Value: {Text(color.Value)}"); + break; + case KeyFrameAnimation.ValueKeyFrame path: + WriteChild("Value", (CanvasGeometry)path.Value.Source, WriteCanvasGeometry); + break; + default: + throw new InvalidOperationException($"Unexpected key frame {value.Type}."); + } + + _indent--; + } + + public void WriteCanvasGeometry(CanvasGeometry? value) + { + if (WriteNodeHeader(value, "CanvasGeometry")) + { + return; + } + + switch (value) + { + case CanvasGeometry.Combination combination: + WriteChild("A", combination.A, WriteCanvasGeometry); + WriteChild("B", combination.B, WriteCanvasGeometry); + Write("CombineMode", combination.CombineMode); + Write("Matrix", combination.Matrix); + break; + case CanvasGeometry.TransformedGeometry transformed: + WriteChild("Source", transformed.SourceGeometry, WriteCanvasGeometry); + Write("TransformMatrix", transformed.TransformMatrix); + break; + case CanvasGeometry.Group group: + Write("FilledRegionDetermination", group.FilledRegionDetermination); + WriteList("Geometries", group.Geometries, WriteCanvasGeometry); + break; + case CanvasGeometry.Path path: + Write("FilledRegionDetermination", path.FilledRegionDetermination); + + foreach (var command in path.Commands) + { + WriteLine(Text(command)); + } + + break; + case CanvasGeometry.Ellipse ellipse: + WriteLine( + $"Ellipse: {Text(ellipse.X)} {Text(ellipse.Y)} " + + $"{Text(ellipse.RadiusX)} {Text(ellipse.RadiusY)}"); + break; + case CanvasGeometry.RoundedRectangle rectangle: + WriteLine( + $"RoundedRectangle: {Text(rectangle.X)} {Text(rectangle.Y)} " + + $"{Text(rectangle.W)} {Text(rectangle.H)} " + + $"{Text(rectangle.RadiusX)} {Text(rectangle.RadiusY)}"); + break; + default: + throw new InvalidOperationException($"Unexpected canvas geometry {value!.Type}."); + } + + _indent--; + } + + void WriteCompositionObjectReference(CompositionObject? value) + { + switch (value) + { + case null: + WriteLine("null"); + break; + case Visual visual: + WriteVisual(visual); + break; + case CompositionShape shape: + WriteShape(shape); + break; + case CompositionGeometry geometry: + WriteGeometry(geometry); + break; + case CompositionBrush brush: + WriteBrush(brush); + break; + case CompositionAnimation animation: + WriteAnimation(animation); + break; + case CompositionEasingFunction easing: + WriteEasing(easing); + break; + case CompositionPropertySet propertySet: + WritePropertySet(propertySet); + break; + case CompositionVisualSurface surface: + WriteSurface(surface); + break; + case CompositionClip clip: + WriteClip(clip); + break; + case AnimationController controller: + WriteController(controller); + break; + case CompositionShadow shadow: + WriteShadow(shadow); + break; + case CompositionColorGradientStop stop: + WriteGradientStop(stop); + break; + case CompositionViewBox viewBox: + WriteViewBox(viewBox); + break; + default: + throw new InvalidOperationException($"Unexpected reference to {value.Type}."); + } + } + + void WritePropertySet(CompositionPropertySet? value) + { + if (WriteNodeHeader(value, "PropertySet")) + { + return; + } + + WritePropertySetContents(value!); + _indent--; + } + + void WriteController(AnimationController? value) + { + if (WriteNodeHeader(value, "Controller")) + { + return; + } + + WriteLine($"IsCustom: {value!.IsCustom}"); + WriteLine($"IsPaused: {value.IsPaused}"); + WriteLine($"TargetProperty: {Text(value.TargetProperty)}"); + WriteCompositionObject(value); + _indent--; + } + + // Writes the state that every CompositionObject has. The property set is + // written inline rather than as a node, because it belongs to the object. + void WriteCompositionObject(CompositionObject value) + { + if (value.Comment is not null) + { + WriteLine($"Comment: {value.Comment}"); + } + + // Properties is created on demand, so it is only written when the object + // actually has some, which is exactly when it is serialized. + var properties = value.Properties; + if (value.Type != CompositionObjectType.CompositionPropertySet && + (properties.Names.Count > 0 || properties.Animators.Any())) + { + WriteLine("Properties"); + _indent++; + WritePropertySetContents(properties); + _indent--; + } + + foreach (var animator in value.Animators) + { + WriteLine($"Animator: {animator.AnimatedProperty}"); + _indent++; + WriteChild("Animation", animator.Animation, WriteAnimation); + + if (animator.Controller is not null) + { + WriteChild("Controller", animator.Controller, WriteController); + } + + _indent--; + } + } + + void WritePropertySetContents(CompositionPropertySet value) + { + // Names is a SortedDictionary, so this order is already canonical. + foreach (var (name, type) in value.Names.Select(n => (n.Key, n.Value))) + { + switch (type) + { + case PropertySetValueType.Color: + value.TryGetColor(name, out var color); + WriteLine($"{name}: {Text(color!.Value)}"); + break; + case PropertySetValueType.Scalar: + value.TryGetScalar(name, out var scalar); + WriteLine($"{name}: {Text(scalar!.Value)}"); + break; + case PropertySetValueType.Vector2: + value.TryGetVector2(name, out var vector2); + WriteLine($"{name}: {Text(vector2!.Value)}"); + break; + case PropertySetValueType.Vector3: + value.TryGetVector3(name, out var vector3); + WriteLine($"{name}: {Text(vector3!.Value)}"); + break; + case PropertySetValueType.Vector4: + value.TryGetVector4(name, out var vector4); + WriteLine($"{name}: {Text(vector4!.Value)}"); + break; + default: + throw new InvalidOperationException($"Unexpected property {type}."); + } + } + + foreach (var animator in value.Animators) + { + WriteLine($"Animator: {animator.AnimatedProperty}"); + _indent++; + WriteChild("Animation", animator.Animation, WriteAnimation); + _indent--; + } + } + + // Writes the "Kind #id" line that starts a node, and returns true if the node + // has been written before, in which case the caller must not write it again. + // Leaves the indent increased when it returns false. + bool WriteNodeHeader(object? value, string category) + { + if (value is null) + { + WriteLine($"{category}: null"); + return true; + } + + if (_ids.TryGetValue(value, out var existing)) + { + WriteLine($"{category} #{existing} (shared)"); + return true; + } + + var id = _ids.Count; + _ids.Add(value, id); + WriteLine($"{category} #{id} {TypeNameOf(value)}"); + _indent++; + return false; + } + + static string TypeNameOf(object value) + => value switch + { + CompositionObject compositionObject => compositionObject.Type.ToString(), + CanvasGeometry canvasGeometry => canvasGeometry.Type.ToString(), + GraphicsEffectBase effect => effect.Type.ToString(), + LoadedImageSurface surface => surface.Type.ToString(), + _ => value.GetType().Name, + }; + + void WriteChild(string name, T? value, Action write) + where T : class + { + WriteLine($"{name}:"); + _indent++; + write(value); + _indent--; + } + + void WriteList(string name, IEnumerable values, Action write) + where T : class + { + var array = values.ToArray(); + if (array.Length == 0) + { + return; + } + + WriteLine($"{name}: [{array.Length}]"); + _indent++; + foreach (var value in array) + { + write(value); + } + + _indent--; + } + + // Written only when the property has a value, so that "never set" and "set to + // the default" stay distinguishable, exactly as they are in the buffer. + void WriteOptional(string name, T? value) + where T : struct + { + if (value.HasValue) + { + WriteLine($"{name}: {Text(value.Value)}"); + } + } + + void Write(string name, T value) + where T : struct + => WriteLine($"{name}: {Text(value)}"); + + void WriteFloats(string name, IEnumerable values) + { + var array = values.ToArray(); + if (array.Length > 0) + { + WriteLine($"{name}: {string.Join(", ", array.Select(v => Text(v)))}"); + } + } + + void WriteLine(string text) + => _builder.Append(' ', _indent * 2).Append(text).Append('\n'); + + // Values are formatted with round trip precision and the invariant culture, so + // that the dump is exact and machine independent. + static string Text(object? value) + => value switch + { + null => "null", + float f => f.ToString("R", CultureInfo.InvariantCulture), + Vector2 v => $"<{Text(v.X)}, {Text(v.Y)}>", + Vector3 v => $"<{Text(v.X)}, {Text(v.Y)}, {Text(v.Z)}>", + Vector4 v => $"<{Text(v.X)}, {Text(v.Y)}, {Text(v.Z)}, {Text(v.W)}>", + Matrix3x2 m => + $"[{Text(m.M11)}, {Text(m.M12)}, {Text(m.M21)}, " + + $"{Text(m.M22)}, {Text(m.M31)}, {Text(m.M32)}]", + Matrix4x4 m => + $"[{Text(m.M11)}, {Text(m.M12)}, {Text(m.M13)}, {Text(m.M14)}, " + + $"{Text(m.M21)}, {Text(m.M22)}, {Text(m.M23)}, {Text(m.M24)}, " + + $"{Text(m.M31)}, {Text(m.M32)}, {Text(m.M33)}, {Text(m.M34)}, " + + $"{Text(m.M41)}, {Text(m.M42)}, {Text(m.M43)}, {Text(m.M44)}]", + Wui.Color c => $"#{c.A:X2}{c.R:X2}{c.G:X2}{c.B:X2}", + CanvasPathBuilder.Command.BeginFigure begin => $"BeginFigure {Text(begin.StartPoint)}", + CanvasPathBuilder.Command.EndFigure end => $"EndFigure {end.FigureLoop}", + CanvasPathBuilder.Command.AddLine line => $"AddLine {Text(line.EndPoint)}", + CanvasPathBuilder.Command.AddCubicBezier bezier => + $"AddCubicBezier {Text(bezier.ControlPoint1)} " + + $"{Text(bezier.ControlPoint2)} {Text(bezier.EndPoint)}", + _ => value.ToString() ?? string.Empty, + }; + } + + sealed class ReferenceEqualityComparer : IEqualityComparer + { + internal static readonly ReferenceEqualityComparer Instance = new ReferenceEqualityComparer(); + + ReferenceEqualityComparer() + { + } + + bool IEqualityComparer.Equals(object? x, object? y) => ReferenceEquals(x, y); + + int IEqualityComparer.GetHashCode(object obj) + => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj); + } + } +} diff --git a/tests/CompDataFlatbuffer.Tests/Corpus.cs b/tests/CompDataFlatbuffer.Tests/Corpus.cs new file mode 100644 index 00000000..69bddd44 --- /dev/null +++ b/tests/CompDataFlatbuffer.Tests/Corpus.cs @@ -0,0 +1,141 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using CommunityToolkit.WinUI.Lottie.CompMetadata; +using CommunityToolkit.WinUI.Lottie.LottieData; +using CommunityToolkit.WinUI.Lottie.LottieData.Serialization; +using CommunityToolkit.WinUI.Lottie.LottieMetadata; +using CommunityToolkit.WinUI.Lottie.LottieToWinComp; +using CommunityToolkit.WinUI.Lottie.WinCompData; +using IoPath = System.IO.Path; + +namespace CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Tests +{ + /// + /// Loads the Lottie files that the tests run against and translates them to + /// WinCompData graphs. + /// + static class Corpus + { + // The well known keys under which the translator stores its metadata. These are + // the same values that LottieGen uses to retrieve it. + static readonly Guid LottieMetadataKey = new Guid("EA3D6538-361A-4B1C-960D-50A6C35DC0B4"); + static readonly Guid PropertyBindingNamesKey = new Guid("A115C46A-254C-43E6-A3C7-9DE516C3C3C8"); + + /// + /// Gets the names of the Lottie files that the tests run against. + /// + /// A test case per Lottie file. + public static IEnumerable FileNames() + => Directory.EnumerateFiles(AssetsFolder, "*.json") + .Select(IoPath.GetFileName) + .OrderBy(name => name, StringComparer.Ordinal) + .Select(name => new object[] { name! }); + + static string AssetsFolder + { + get + { + // The tests run from the build output folder, so the repository root is + // found by walking up to the folder that contains the samples. + var directory = new DirectoryInfo(AppContext.BaseDirectory); + + while (directory is not null) + { + var candidate = IoPath.Combine(directory.FullName, "LottieSamples", "Assets"); + if (Directory.Exists(candidate)) + { + return candidate; + } + + directory = directory.Parent; + } + + throw new InvalidOperationException("Could not locate LottieSamples/Assets."); + } + } + + /// + /// Reads and translates one of the Lottie files. + /// + /// The name of the file within the samples folder. + /// The translated graph and the metadata that accompanies it. + public static TranslatedLottie Translate(string fileName) + { + using var stream = File.OpenRead(IoPath.Combine(AssetsFolder, fileName)); + + var lottie = LottieCompositionReader.ReadLottieCompositionFromJsonStream( + stream, + LottieCompositionReader.Options.IgnoreMatchNames, + out var readerIssues); + + if (lottie is null) + { + throw new InvalidOperationException( + $"Failed to read {fileName}: {string.Join("; ", readerIssues.Select(i => i.Description))}"); + } + + var result = LottieToWinCompTranslator.TryTranslateLottieComposition( + lottie, + new TranslatorConfiguration + { + AddCodegenDescriptions = true, + TranslatePropertyBindings = true, + + // The newest version is targeted so that the translation uses every + // feature it can, which gives the tests the widest coverage. + TargetUapVersion = uint.MaxValue, + }); + + if (result.RootVisual is null) + { + throw new InvalidOperationException($"Failed to translate {fileName}."); + } + + return new TranslatedLottie( + result.RootVisual, + checked((ushort)result.MinimumRequiredUapVersion), + result.SourceMetadata.TryGetValue(LottieMetadataKey, out var metadata) + ? (LottieCompositionMetadata)metadata + : null, + result.SourceMetadata.TryGetValue(PropertyBindingNamesKey, out var bindings) + ? (IReadOnlyList)bindings + : null, + (float)lottie.Width, + (float)lottie.Height); + } + + /// + /// A translated Lottie file, and everything needed to serialize it. + /// + /// The root of the translated graph. + /// The minimum UAP version needed to instantiate the graph. + /// Metadata about the source animation. + /// The property bindings that the graph exposes. + /// The width of the source animation. + /// The height of the source animation. + public sealed record TranslatedLottie( + Visual RootVisual, + ushort RequiredUapVersion, + LottieCompositionMetadata? Metadata, + IReadOnlyList? PropertyBindings, + float Width, + float Height) + { + /// + /// Serializes the graph. + /// + /// The serialized graph. + public byte[] Serialize() + => CompositionSerializer.Serialize( + RootVisual, RequiredUapVersion, Metadata, PropertyBindings, Width, Height); + } + } +} diff --git a/tests/CompDataFlatbuffer.Tests/RoundTripTests.cs b/tests/CompDataFlatbuffer.Tests/RoundTripTests.cs new file mode 100644 index 00000000..24a3ac08 --- /dev/null +++ b/tests/CompDataFlatbuffer.Tests/RoundTripTests.cs @@ -0,0 +1,120 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Linq; +using CommunityToolkit.WinUI.Lottie.WinCompData; +using Xunit; + +namespace CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Tests +{ + /// + /// Verifies that a graph survives a trip through the FlatBuffer format unchanged. + /// + /// + /// This is the test that the FlatBuffer based player produces the same visual tree + /// as the JSON based one. The JSON path is Lottie file to translator to + /// WinCompData graph. The FlatBuffer path is that same graph serialized and read + /// back. If the two graphs are identical then any interpreter that walks one + /// produces the same visuals as an interpreter that walks the other, so the + /// comparison is made once here rather than being repeated for every player. + /// + public class RoundTripTests + { + [Theory] + [MemberData(nameof(Corpus.FileNames), MemberType = typeof(Corpus))] + public void RoundTripPreservesTheGraph(string fileName) + { + var translated = Corpus.Translate(fileName); + + var expected = CompositionTreeDumper.Dump(translated.RootVisual); + var actual = CompositionTreeDumper.Dump(CompositionDeserializer.Deserialize(translated.Serialize())); + + Assert.Equal(expected, actual); + } + + [Theory] + [MemberData(nameof(Corpus.FileNames), MemberType = typeof(Corpus))] + public void SerializationIsDeterministic(string fileName) + { + var translated = Corpus.Translate(fileName); + + // The same graph must always produce the same bytes, otherwise builds are + // not reproducible and the output cannot be cached or diffed. + Assert.Equal(translated.Serialize(), translated.Serialize()); + } + + [Theory] + [MemberData(nameof(Corpus.FileNames), MemberType = typeof(Corpus))] + public void RoundTripIsStable(string fileName) + { + var translated = Corpus.Translate(fileName); + + // Serializing a deserialized graph must reproduce the original bytes. This + // catches any state that the deserializer drops but the dump does not cover. + var once = translated.Serialize(); + var twice = CompositionSerializer.Serialize( + CompositionDeserializer.Deserialize(once), + translated.RequiredUapVersion, + translated.Metadata, + translated.PropertyBindings, + translated.Width, + translated.Height); + + Assert.Equal(once, twice); + } + + [Theory] + [MemberData(nameof(Corpus.FileNames), MemberType = typeof(Corpus))] + public void DeserializedGraphSharesNodes(string fileName) + { + var translated = Corpus.Translate(fileName); + var root = CompositionDeserializer.Deserialize(translated.Serialize()); + + // A node that is reached by more than one path must be one object, not a + // copy per path, otherwise a large animation would expand enormously and + // animations bound to a shared object would stop being shared. + Assert.Equal(CountDistinct(translated.RootVisual), CountDistinct(root)); + } + + // Counts the distinct visuals reachable from a root. + static int CountDistinct(Visual root) + { + var seen = new System.Collections.Generic.HashSet( + ReferenceEqualityComparer.Instance); + + void Walk(Visual visual) + { + if (!seen.Add(visual)) + { + return; + } + + if (visual is ContainerVisual container) + { + foreach (var child in container.Children) + { + Walk(child); + } + } + } + + Walk(root); + return seen.Count; + } + + sealed class ReferenceEqualityComparer : System.Collections.Generic.IEqualityComparer + where T : class + { + internal static readonly ReferenceEqualityComparer Instance = new ReferenceEqualityComparer(); + + public bool Equals(T? x, T? y) => ReferenceEquals(x, y); + + public int GetHashCode(T obj) + => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj); + } + } +} From 4de1b91aeb2e540d3a671755bb1d9a02493c869c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 31 Aug 2026 18:34:48 +0000 Subject: [PATCH 08/39] Reject malformed buffers as format errors; add robustness tests Co-authored-by: jonwis <18537118+jonwis@users.noreply.github.com> --- .../CompositionDeserializer.cs | 61 +++++++++--- .../FlatBufferFormatException.cs | 10 ++ .../RobustnessTests.cs | 92 +++++++++++++++++++ 3 files changed, 150 insertions(+), 13 deletions(-) create mode 100644 tests/CompDataFlatbuffer.Tests/RobustnessTests.cs diff --git a/source/CompDataFlatbuffer/CompositionDeserializer.cs b/source/CompDataFlatbuffer/CompositionDeserializer.cs index 8d2d502d..efda0849 100644 --- a/source/CompDataFlatbuffer/CompositionDeserializer.cs +++ b/source/CompDataFlatbuffer/CompositionDeserializer.cs @@ -40,6 +40,11 @@ namespace CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer #endif sealed class CompositionDeserializer { + // The smallest buffer that could possibly be a composition: a root offset + // followed by the four byte file identifier. Checked before the identifier is + // read so that a short buffer is reported as a format error. + const int MinimumBufferLength = 8; + readonly Compositor _compositor = new Compositor(); readonly Fb.LottieComposition _root; readonly string[] _strings; @@ -101,25 +106,46 @@ public static Visual Deserialize(ReadOnlySpan bytes) // The buffer is untrusted, so it is fully verified before any of it is read. var buffer = new ByteBuffer(bytes.ToArray()); - // This checks the file identifier as well as the structure, so a buffer that - // is not a composition at all is rejected here. - if (!Fb.LottieComposition.VerifyLottieComposition(buffer)) + if (bytes.Length < MinimumBufferLength || + !Fb.LottieComposition.LottieCompositionBufferHasIdentifier(buffer)) { - throw new FlatBufferFormatException("The buffer is not a valid composition."); + throw new FlatBufferFormatException("The buffer is not a composition."); } - var root = Fb.LottieComposition.GetRootAsLottieComposition(buffer); - - if (root.SchemaVersion > Format.Version) + // Verification proves that the buffer is structurally sound, but it cannot + // prove that the indices stored in it point at the right things. Neither the + // verifier nor the reader reports a malformed buffer purely by returning + // false; both also throw. Everything is therefore done inside this handler so + // that a corrupt buffer always surfaces as a format error and never as an + // exception that a caller could not have anticipated. Reading past the end of + // the buffer is not a safety problem because ByteBuffer is bounds checked; + // the only thing being corrected here is the type of the exception. + try { - throw new FlatBufferFormatException( - $"Schema version {root.SchemaVersion} is newer than the supported version {Format.Version}."); - } + if (!Fb.LottieComposition.VerifyLottieComposition(buffer)) + { + throw new FlatBufferFormatException("The buffer is not a valid composition."); + } + + var root = Fb.LottieComposition.GetRootAsLottieComposition(buffer); - var deserializer = new CompositionDeserializer(root); - var rootVisual = deserializer.GetVisual(root.RootVisual); + if (root.SchemaVersion > Format.Version) + { + throw new FlatBufferFormatException( + $"Schema version {root.SchemaVersion} is newer than the supported version {Format.Version}."); + } - return rootVisual ?? throw new FlatBufferFormatException("The composition has no root visual."); + var deserializer = new CompositionDeserializer(root); + var rootVisual = deserializer.GetVisual(root.RootVisual); + + return rootVisual ?? throw new FlatBufferFormatException("The composition has no root visual."); + } + catch (Exception e) when (e is ArgumentException or IndexOutOfRangeException or + InvalidOperationException or OverflowException or + FormatException or NullReferenceException) + { + throw new FlatBufferFormatException("The buffer is malformed.", e); + } } // --------------------------------------------------------------------- @@ -181,6 +207,15 @@ void InitializeCompositionObject(CompositionObject target, Fb.CompObj? source) ? GetController(controllerIndex) : null; + // An expression animation is driven by its inputs and so cannot have a + // controller. StartAnimation only asserts this, so it is checked here in + // order to reject the buffer rather than build an unusable graph. + if (customController is not null && animation is ExpressionAnimation) + { + throw new FlatBufferFormatException( + $"Animator '{property}' gives a controller to an expression animation."); + } + var created = target.StartAnimation(property, animation, customController); if (controllerIndex != Format.NullIndex && created.Controller is not null) diff --git a/source/CompDataFlatbuffer/FlatBufferFormatException.cs b/source/CompDataFlatbuffer/FlatBufferFormatException.cs index 5a7c9531..3779041c 100644 --- a/source/CompDataFlatbuffer/FlatBufferFormatException.cs +++ b/source/CompDataFlatbuffer/FlatBufferFormatException.cs @@ -28,5 +28,15 @@ public FlatBufferFormatException(string message) : base(message) { } + + /// + /// Initializes a new instance of the class. + /// + /// A description of what was wrong with the buffer. + /// The failure that revealed the problem. + public FlatBufferFormatException(string message, Exception innerException) + : base(message, innerException) + { + } } } diff --git a/tests/CompDataFlatbuffer.Tests/RobustnessTests.cs b/tests/CompDataFlatbuffer.Tests/RobustnessTests.cs new file mode 100644 index 00000000..aa108d25 --- /dev/null +++ b/tests/CompDataFlatbuffer.Tests/RobustnessTests.cs @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using Xunit; + +namespace CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Tests +{ + /// + /// Verifies that malformed input is rejected rather than being allowed to + /// corrupt the reader. + /// + /// + /// A serialized composition is likely to be loaded from a file or downloaded, so + /// it has to be treated as untrusted. Every one of these cases must fail with a + /// : no other exception type, no crash, and + /// no hang. The native interpreter performs the same checks, so this is also a + /// specification of what that interpreter has to reject. + /// + public class RobustnessTests + { + [Fact] + public void EmptyBufferIsRejected() + => Assert.Throws(() => CompositionDeserializer.Deserialize(Array.Empty())); + + [Fact] + public void GarbageIsRejected() + { + var garbage = new byte[512]; + new Random(0).NextBytes(garbage); + + Assert.Throws(() => CompositionDeserializer.Deserialize(garbage)); + } + + [Fact] + public void BufferWithTheWrongFileIdentifierIsRejected() + { + var buffer = Corpus.Translate("LightBulb.json").Serialize(); + + // The file identifier sits at offset 4, immediately after the root offset. + buffer[4] = (byte)'X'; + + Assert.Throws(() => CompositionDeserializer.Deserialize(buffer)); + } + + [Theory] + [MemberData(nameof(Corpus.FileNames), MemberType = typeof(Corpus))] + public void TruncatedBuffersAreRejected(string fileName) + { + var buffer = Corpus.Translate(fileName).Serialize(); + + // Truncation is checked at a spread of lengths rather than at every length, + // which would make the test run for minutes without finding anything more. + for (var length = 0; length < buffer.Length; length += Math.Max(1, buffer.Length / 64)) + { + var truncated = new byte[length]; + Array.Copy(buffer, truncated, length); + + Assert.Throws(() => CompositionDeserializer.Deserialize(truncated)); + } + } + + [Theory] + [MemberData(nameof(Corpus.FileNames), MemberType = typeof(Corpus))] + public void CorruptedBuffersDoNotCrash(string fileName) + { + var original = Corpus.Translate(fileName).Serialize(); + var random = new Random(fileName.GetHashCode(StringComparison.Ordinal)); + + // Unlike truncation, a flipped bit may leave a buffer that is still valid, + // just describing a different animation. So the requirement here is weaker: + // the reader either succeeds or reports a format error, but never throws + // something else and never reads outside the buffer. + for (var i = 0; i < 200; i++) + { + var corrupted = (byte[])original.Clone(); + corrupted[random.Next(corrupted.Length)] ^= (byte)(1 << random.Next(8)); + + try + { + CompositionDeserializer.Deserialize(corrupted); + } + catch (FlatBufferFormatException) + { + } + } + } + } +} From 428db708cc2813c02978fbfeff45c742e8d81274 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 31 Aug 2026 18:39:05 +0000 Subject: [PATCH 09/39] Add coverage tests exercising every CompositionObjectType and geometry kind Co-authored-by: jonwis <18537118+jonwis@users.noreply.github.com> --- .../CompDataFlatbuffer.Tests/CoverageTests.cs | 474 ++++++++++++++++++ 1 file changed, 474 insertions(+) create mode 100644 tests/CompDataFlatbuffer.Tests/CoverageTests.cs diff --git a/tests/CompDataFlatbuffer.Tests/CoverageTests.cs b/tests/CompDataFlatbuffer.Tests/CoverageTests.cs new file mode 100644 index 00000000..7dfd7f21 --- /dev/null +++ b/tests/CompDataFlatbuffer.Tests/CoverageTests.cs @@ -0,0 +1,474 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using CommunityToolkit.WinUI.Lottie.WinCompData; +using CommunityToolkit.WinUI.Lottie.WinCompData.Mgc; +using CommunityToolkit.WinUI.Lottie.WinCompData.Mgce; +using CommunityToolkit.WinUI.Lottie.WinCompData.Mgcg; +using CommunityToolkit.WinUI.Lottie.WinUIXamlMediaData; +using Xunit; +using Expr = CommunityToolkit.WinUI.Lottie.WinCompData.Expressions; +using Wui = CommunityToolkit.WinUI.Lottie.WinCompData.Wui; + +namespace CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Tests +{ + /// + /// Verifies that every kind of node that a graph can contain survives serialization. + /// + /// + /// The sample animations exercise the parts of the object model that the translator + /// happens to emit for them, which is not all of it. This builds a graph that + /// contains at least one of every , every + /// shape, every path command and every effect, and + /// round trips that. Together with + /// this means a node type cannot be added to WinCompData without either being + /// handled here or failing the build. + /// + public class CoverageTests + { + [Fact] + public void AllCompositionObjectTypesAreCovered() + { + var covered = Collect(BuildGraph()) + .Select(o => o.Type) + .ToHashSet(); + + var missing = Enum.GetValues() + .Where(t => !covered.Contains(t)) + .ToArray(); + + Assert.True( + missing.Length == 0, + $"The coverage graph does not contain: {string.Join(", ", missing)}."); + } + + [Fact] + public void EveryNodeTypeSurvivesARoundTrip() + { + var root = BuildGraph(); + + var expected = CompositionTreeDumper.Dump(root); + var actual = CompositionTreeDumper.Dump( + CompositionDeserializer.Deserialize(CompositionSerializer.Serialize(root, 14, null, null, 100, 100))); + + Assert.Equal(expected, actual); + } + + // Builds a graph containing one of everything. It is not a sensible animation; + // its only job is to reach every branch of the serializer and the deserializer. + static Visual BuildGraph() + { + var c = new Compositor(); + + var root = c.CreateContainerVisual(); + root.Comment = "Root"; + root.Offset = new Vector3(1, 2, 3); + root.CenterPoint = new Vector3(4, 5, 6); + root.RotationAngleInDegrees = 45; + root.RotationAxis = new Vector3(0, 0, 1); + root.Scale = new Vector3(2, 2, 2); + root.Size = new Vector2(100, 100); + root.Opacity = 0.5f; + root.IsVisible = true; + root.BorderMode = CompositionBorderMode.Soft; + root.TransformMatrix = Matrix4x4.Identity; + + // Clips. An inset clip on the root, a geometric clip further down. + var insetClip = c.CreateInsetClip(); + insetClip.LeftInset = 1; + insetClip.RightInset = 2; + insetClip.TopInset = 3; + insetClip.BottomInset = 4; + root.Clip = insetClip; + + // Easings. + var linear = c.CreateLinearEasingFunction(); + var cubic = c.CreateCubicBezierEasingFunction(new Vector2(0.1f, 0.2f), new Vector2(0.3f, 0.4f)); + var step = c.CreateStepEasingFunction(3); + step.IsInitialStepSingleFrame = true; + step.IsFinalStepSingleFrame = true; + step.InitialStep = 1; + step.FinalStep = 2; + + // Every kind of key frame animation, using every kind of easing and both + // kinds of key frame (a value and an expression). + var scalarAnimation = c.CreateScalarKeyFrameAnimation(); + scalarAnimation.Duration = TimeSpan.FromSeconds(1); + scalarAnimation.InsertKeyFrame(0, 0, linear); + scalarAnimation.InsertKeyFrame(0.5f, 1, cubic); + scalarAnimation.InsertExpressionKeyFrame(1, Expr.Expression.Scalar("0"), step); + + var booleanAnimation = c.CreateBooleanKeyFrameAnimation(); + booleanAnimation.Duration = TimeSpan.FromSeconds(1); + booleanAnimation.InsertKeyFrame(0, false); + booleanAnimation.InsertKeyFrame(1, true); + + var colorAnimation = c.CreateColorKeyFrameAnimation(); + colorAnimation.Duration = TimeSpan.FromSeconds(1); + colorAnimation.InterpolationColorSpace = CompositionColorSpace.Hsl; + colorAnimation.InsertKeyFrame(0, Wui.Color.FromArgb(255, 1, 2, 3), linear); + colorAnimation.InsertKeyFrame(1, Wui.Color.FromArgb(0, 4, 5, 6), linear); + + var vector2Animation = c.CreateVector2KeyFrameAnimation(); + vector2Animation.Duration = TimeSpan.FromSeconds(1); + vector2Animation.InsertKeyFrame(0, new Vector2(1, 2), linear); + + var vector3Animation = c.CreateVector3KeyFrameAnimation(); + vector3Animation.Duration = TimeSpan.FromSeconds(1); + vector3Animation.InsertKeyFrame(0, new Vector3(1, 2, 3), linear); + + var vector4Animation = c.CreateVector4KeyFrameAnimation(); + vector4Animation.Duration = TimeSpan.FromSeconds(1); + vector4Animation.InsertKeyFrame(0, new Vector4(1, 2, 3, 4), linear); + + var pathAnimation = c.CreatePathKeyFrameAnimation(); + pathAnimation.Duration = TimeSpan.FromSeconds(1); + pathAnimation.InsertKeyFrame(0, new CompositionPath(BuildPathGeometry()), linear); + + // A property set that other objects reference through expressions. This is + // how the translator exposes theming properties. + var theme = c.CreatePropertySet(); + theme.InsertScalar("Scalar", 1); + theme.InsertVector2("Vector2", new Vector2(1, 2)); + theme.InsertVector3("Vector3", new Vector3(1, 2, 3)); + theme.InsertVector4("Vector4", new Vector4(1, 2, 3, 4)); + theme.InsertColor("Color", Wui.Color.FromArgb(255, 7, 8, 9)); + + var expressionAnimation = c.CreateExpressionAnimation(Expr.Expression.Scalar("_.Scalar")); + expressionAnimation.SetReferenceParameter("_", theme); + + // A custom controller, which is the only kind the format has to carry, since + // a default one is recreated on the other side. + var controller = c.CreateAnimationController(); + controller.Pause(); + + // Shapes. + var shapeVisual = c.CreateShapeVisual(); + shapeVisual.Size = new Vector2(100, 100); + shapeVisual.ViewBox = c.CreateViewBox(); + shapeVisual.ViewBox.Size = new Vector2(50, 50); + + var containerShape = c.CreateContainerShape(); + containerShape.CenterPoint = new Vector2(1, 2); + containerShape.Offset = new Vector2(3, 4); + containerShape.RotationAngleInDegrees = 90; + containerShape.Scale = new Vector2(2, 2); + containerShape.TransformMatrix = Matrix3x2.Identity; + shapeVisual.Shapes.Add(containerShape); + + // Every geometry type, each on its own sprite shape so that the sprite shape + // properties are exercised as well. + var ellipse = c.CreateEllipseGeometry(); + ellipse.Center = new Vector2(1, 2); + ellipse.Radius = new Vector2(3, 4); + ellipse.TrimStart = 0.1f; + ellipse.TrimEnd = 0.9f; + ellipse.TrimOffset = 0.05f; + + var rectangle = c.CreateRectangleGeometry(); + rectangle.Offset = new Vector2(1, 2); + rectangle.Size = new Vector2(3, 4); + + var roundedRectangle = c.CreateRoundedRectangleGeometry(); + roundedRectangle.Offset = new Vector2(1, 2); + roundedRectangle.Size = new Vector2(3, 4); + roundedRectangle.CornerRadius = new Vector2(5, 6); + + var pathGeometry = c.CreatePathGeometry(new CompositionPath(BuildPathGeometry())); + + var colorBrush = c.CreateColorBrush(Wui.Color.FromArgb(255, 10, 20, 30)); + + var linearGradient = c.CreateLinearGradientBrush(); + linearGradient.StartPoint = new Vector2(0, 0); + linearGradient.EndPoint = new Vector2(1, 1); + linearGradient.AnchorPoint = new Vector2(0.5f, 0.5f); + linearGradient.CenterPoint = new Vector2(0.5f, 0.5f); + linearGradient.ExtendMode = CompositionGradientExtendMode.Mirror; + linearGradient.InterpolationSpace = CompositionColorSpace.HslLinear; + linearGradient.ColorStops.Add(c.CreateColorGradientStop(0, Wui.Color.FromArgb(255, 0, 0, 0))); + linearGradient.ColorStops.Add(c.CreateColorGradientStop(1, Wui.Color.FromArgb(255, 255, 255, 255))); + + var radialGradient = c.CreateRadialGradientBrush(); + radialGradient.EllipseCenter = new Vector2(0.5f, 0.5f); + radialGradient.EllipseRadius = new Vector2(0.5f, 0.5f); + radialGradient.GradientOriginOffset = new Vector2(0.1f, 0.1f); + radialGradient.ColorStops.Add(c.CreateColorGradientStop(0, Wui.Color.FromArgb(255, 1, 2, 3))); + + var strokedShape = AddSpriteShape(c, containerShape, ellipse, colorBrush, linearGradient); + strokedShape.StrokeDashArray.Add(1); + strokedShape.StrokeDashArray.Add(2); + strokedShape.StrokeDashOffset = 3; + strokedShape.StrokeDashCap = CompositionStrokeCap.Round; + strokedShape.StrokeStartCap = CompositionStrokeCap.Square; + strokedShape.StrokeEndCap = CompositionStrokeCap.Triangle; + strokedShape.StrokeLineJoin = CompositionStrokeLineJoin.Bevel; + strokedShape.StrokeMiterLimit = 4; + strokedShape.StrokeThickness = 2; + strokedShape.IsStrokeNonScaling = true; + + AddSpriteShape(c, containerShape, rectangle, colorBrush, radialGradient); + AddSpriteShape(c, containerShape, roundedRectangle, colorBrush, null); + AddSpriteShape(c, containerShape, pathGeometry, colorBrush, null); + + // A geometric clip, which is the other consumer of a geometry. + var geometricClip = c.CreateGeometricClip(); + geometricClip.Geometry = pathGeometry; + shapeVisual.Clip = geometricClip; + + // A layer visual with a drop shadow. + var layerVisual = c.CreateLayerVisual(); + layerVisual.Size = new Vector2(100, 100); + + var shadow = c.CreateDropShadow(); + shadow.BlurRadius = 5; + shadow.Color = Wui.Color.FromArgb(128, 0, 0, 0); + shadow.Offset = new Vector3(1, 2, 0); + shadow.Opacity = 0.5f; + shadow.Mask = colorBrush; + shadow.SourcePolicy = CompositionDropShadowSourcePolicy.InheritFromVisualContent; + layerVisual.Shadow = shadow; + layerVisual.Children.Add(shapeVisual); + + // A visual surface, a surface brush over it, and a mask brush combining two + // brushes. These are how the translator implements masking. + var visualSurface = c.CreateVisualSurface(); + visualSurface.SourceVisual = shapeVisual; + visualSurface.SourceSize = new Vector2(100, 100); + visualSurface.SourceOffset = new Vector2(1, 2); + + var surfaceBrush = c.CreateSurfaceBrush(visualSurface); + + // A surface brush over a loaded image, which is the other kind of surface. + var imageBrush = c.CreateSurfaceBrush( + LoadedImageSurface.StartLoadFromUri(new Uri("ms-appx:///image.png"))); + + var streamBrush = c.CreateSurfaceBrush( + LoadedImageSurface.StartLoadFromStream(new byte[] { 1, 2, 3, 4 })); + + var maskBrush = c.CreateMaskBrush(); + maskBrush.Source = surfaceBrush; + maskBrush.Mask = imageBrush; + + // Effects. Both effect types, reached through an effect factory and an + // effect brush. + var compositeEffect = new CompositeEffect( + CanvasComposite.DestinationIn, + new List + { + new CompositionEffectSourceParameter("source"), + new CompositionEffectSourceParameter("mask"), + }); + + var compositeBrush = c.CreateEffectFactory(compositeEffect).CreateBrush(); + compositeBrush.SetSourceParameter("source", surfaceBrush); + compositeBrush.SetSourceParameter("mask", streamBrush); + + var blurEffect = new GaussianBlurEffect(3, new CompositionEffectSourceParameter("source")); + var blurBrush = c.CreateEffectFactory(blurEffect).CreateBrush(); + blurBrush.SetSourceParameter("source", maskBrush); + + var effectVisual = c.CreateSpriteVisual(); + effectVisual.Size = new Vector2(100, 100); + effectVisual.Brush = blurBrush; + + var compositeVisual = c.CreateSpriteVisual(); + compositeVisual.Size = new Vector2(100, 100); + compositeVisual.Brush = compositeBrush; + + root.Children.Add(layerVisual); + root.Children.Add(effectVisual); + root.Children.Add(compositeVisual); + + // Bind an animation of every kind to something, so that the animators and + // their controllers are all reachable. + root.StartAnimation("Opacity", scalarAnimation, controller); + root.StartAnimation("IsVisible", booleanAnimation); + root.StartAnimation("Offset", vector3Animation); + root.StartAnimation("Size", vector2Animation); + root.StartAnimation("RotationAngleInDegrees", expressionAnimation); + colorBrush.StartAnimation("Color", colorAnimation); + pathGeometry.StartAnimation("Path", pathAnimation); + linearGradient.ColorStops[0].StartAnimation("Offset", scalarAnimation); + radialGradient.StartAnimation("EllipseCenter", vector2Animation); + strokedShape.StartAnimation("StrokeThickness", scalarAnimation); + theme.StartAnimation("Vector4", vector4Animation); + + return root; + } + + static CompositionSpriteShape AddSpriteShape( + Compositor compositor, + CompositionContainerShape parent, + CompositionGeometry geometry, + CompositionBrush fill, + CompositionBrush? stroke) + { + var shape = compositor.CreateSpriteShape(); + shape.Geometry = geometry; + shape.FillBrush = fill; + shape.StrokeBrush = stroke; + parent.Shapes.Add(shape); + return shape; + } + + // Builds a geometry that contains every CanvasGeometry shape and every path + // command, so that the flattened path encoding is fully exercised. + static CanvasGeometry BuildPathGeometry() + { + using var builder = new CanvasPathBuilder(null); + builder.SetFilledRegionDetermination(CanvasFilledRegionDetermination.Winding); + builder.BeginFigure(new Vector2(0, 0)); + builder.AddLine(new Vector2(1, 0)); + builder.AddCubicBezier(new Vector2(2, 0), new Vector2(3, 1), new Vector2(4, 1)); + builder.EndFigure(CanvasFigureLoop.Closed); + builder.BeginFigure(new Vector2(5, 5)); + builder.AddLine(new Vector2(6, 6)); + builder.EndFigure(CanvasFigureLoop.Open); + + var path = CanvasGeometry.CreatePath(builder); + var ellipse = CanvasGeometry.CreateEllipse(null, 1, 2, 3, 4); + var roundedRectangle = CanvasGeometry.CreateRoundedRectangle(null, 1, 2, 3, 4, 5, 6); + var transformed = ellipse.Transform(Matrix3x2.CreateScale(2)); + var combination = path.CombineWith( + roundedRectangle, + Matrix3x2.Identity, + CanvasGeometryCombine.Union); + + return CanvasGeometry.CreateGroup( + null, + new[] { combination, transformed }, + CanvasFilledRegionDetermination.Alternate); + } + + // Walks every CompositionObject reachable from a root. This deliberately does not + // reuse the serializer's walker, so that a node the serializer cannot see is + // still counted as uncovered. + static IEnumerable Collect(Visual root) + { + var seen = new HashSet(); + var result = new List(); + + void Add(CompositionObject? o) + { + if (o is null || !seen.Add(o)) + { + return; + } + + result.Add(o); + + Add(o.Properties); + + foreach (var animator in o.Animators) + { + Add(animator.Animation); + Add(animator.Controller); + } + + if (o is CompositionAnimation animation) + { + foreach (var parameter in animation.ReferenceParameters) + { + Add(parameter.Value); + } + + if (o is KeyFrameAnimation_ keyFrameAnimation) + { + foreach (var easing in keyFrameAnimation.KeyFrames.Select(f => f.Easing)) + { + Add(easing); + } + } + } + + switch (o) + { + case SpriteVisual sprite: + Add(sprite.Brush); + Add(sprite.Clip); + foreach (var child in sprite.Children) + { + Add(child); + } + + break; + case ContainerVisual container: + foreach (var child in container.Children) + { + Add(child); + } + + Add(container.Clip); + + if (container is LayerVisual layer) + { + Add(layer.Shadow); + } + else if (container is ShapeVisual shapeVisual) + { + Add(shapeVisual.ViewBox); + foreach (var shape in shapeVisual.Shapes) + { + Add(shape); + } + } + + break; + case CompositionContainerShape containerShape: + foreach (var shape in containerShape.Shapes) + { + Add(shape); + } + + break; + case CompositionSpriteShape spriteShape: + Add(spriteShape.Geometry); + Add(spriteShape.FillBrush); + Add(spriteShape.StrokeBrush); + break; + case CompositionGeometricClip geometricClip: + Add(geometricClip.Geometry); + break; + case CompositionGradientBrush gradient: + foreach (var stop in gradient.ColorStops) + { + Add(stop); + } + + break; + case CompositionMaskBrush mask: + Add(mask.Source); + Add(mask.Mask); + break; + case CompositionSurfaceBrush surfaceBrush: + Add(surfaceBrush.Surface as CompositionObject); + break; + case CompositionEffectBrush effectBrush: + Add(effectBrush.GetEffectFactory()); + foreach (var source in effectBrush.GetEffectFactory().Effect.Sources) + { + Add(effectBrush.GetSourceParameter(source.Name)); + } + + break; + case CompositionVisualSurface visualSurface: + Add(visualSurface.SourceVisual); + break; + case DropShadow dropShadow: + Add(dropShadow.Mask); + break; + } + } + + Add(root); + return result; + } + } +} From bf8e9db75e6ebe34360af4247f85417e32b21003 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 31 Aug 2026 18:46:58 +0000 Subject: [PATCH 10/39] Add native C++/WinRT runtime interpreter for serialized compositions Co-authored-by: jonwis <18537118+jonwis@users.noreply.github.com> --- .../Generated/lottie_comp_generated.h | 4301 +++++++++++++++++ dlls/LottieRuntime/LottieRuntime.cpp | 2014 ++++++++ dlls/LottieRuntime/LottieRuntime.h | 54 + dlls/LottieRuntime/LottieRuntime.vcxproj | 81 + dlls/LottieRuntime/README.md | 135 + 5 files changed, 6585 insertions(+) create mode 100644 dlls/LottieRuntime/Generated/lottie_comp_generated.h create mode 100644 dlls/LottieRuntime/LottieRuntime.cpp create mode 100644 dlls/LottieRuntime/LottieRuntime.h create mode 100644 dlls/LottieRuntime/LottieRuntime.vcxproj create mode 100644 dlls/LottieRuntime/README.md diff --git a/dlls/LottieRuntime/Generated/lottie_comp_generated.h b/dlls/LottieRuntime/Generated/lottie_comp_generated.h new file mode 100644 index 00000000..b2eba1b4 --- /dev/null +++ b/dlls/LottieRuntime/Generated/lottie_comp_generated.h @@ -0,0 +1,4301 @@ +// automatically generated by the FlatBuffers compiler, do not modify + + +#ifndef FLATBUFFERS_GENERATED_LOTTIECOMP_COMMUNITYTOOLKIT_WINUI_LOTTIE_COMPDATAFLATBUFFER_SCHEMA_H_ +#define FLATBUFFERS_GENERATED_LOTTIECOMP_COMMUNITYTOOLKIT_WINUI_LOTTIE_COMPDATAFLATBUFFER_SCHEMA_H_ + +#include "flatbuffers/flatbuffers.h" + +// Ensure the included flatbuffers.h is the same version as when this file was +// generated, otherwise it may not be compatible. +static_assert(FLATBUFFERS_VERSION_MAJOR == 25 && + FLATBUFFERS_VERSION_MINOR == 2 && + FLATBUFFERS_VERSION_REVISION == 10, + "Non-compatible flatbuffers version included"); + +namespace CommunityToolkit { +namespace WinUI { +namespace Lottie { +namespace CompDataFlatbuffer { +namespace Schema { + +struct Vec2; + +struct Vec3; + +struct Vec4; + +struct Mat3x2; + +struct Mat4x4; + +struct Color; + +struct Animator; +struct AnimatorBuilder; + +struct CompObj; +struct CompObjBuilder; + +struct PropertyValue; +struct PropertyValueBuilder; + +struct ReferenceParameter; +struct ReferenceParameterBuilder; + +struct SourceParameter; +struct SourceParameterBuilder; + +struct KeyFrame; +struct KeyFrameBuilder; + +struct Visual; +struct VisualBuilder; + +struct Shape; +struct ShapeBuilder; + +struct Geometry; +struct GeometryBuilder; + +struct Brush; +struct BrushBuilder; + +struct GradientStop; +struct GradientStopBuilder; + +struct ViewBox; +struct ViewBoxBuilder; + +struct Clip; +struct ClipBuilder; + +struct Shadow; +struct ShadowBuilder; + +struct Surface; +struct SurfaceBuilder; + +struct Effect; +struct EffectBuilder; + +struct Easing; +struct EasingBuilder; + +struct Animation; +struct AnimationBuilder; + +struct PropertySet; +struct PropertySetBuilder; + +struct Controller; +struct ControllerBuilder; + +struct PropertyBinding; +struct PropertyBindingBuilder; + +struct Marker; +struct MarkerBuilder; + +struct Metadata; +struct MetadataBuilder; + +struct LottieComposition; +struct LottieCompositionBuilder; + +struct CanvasGeometry; +struct CanvasGeometryBuilder; + +enum class ObjectCategory : uint8_t { + Visual = 0, + Shape = 1, + Geometry = 2, + Brush = 3, + Animation = 4, + Easing = 5, + PropertySet = 6, + Surface = 7, + Clip = 8, + Controller = 9, + Shadow = 10, + Effect = 11, + GradientStop = 12, + ViewBox = 13, + MIN = Visual, + MAX = ViewBox +}; + +inline const ObjectCategory (&EnumValuesObjectCategory())[14] { + static const ObjectCategory values[] = { + ObjectCategory::Visual, + ObjectCategory::Shape, + ObjectCategory::Geometry, + ObjectCategory::Brush, + ObjectCategory::Animation, + ObjectCategory::Easing, + ObjectCategory::PropertySet, + ObjectCategory::Surface, + ObjectCategory::Clip, + ObjectCategory::Controller, + ObjectCategory::Shadow, + ObjectCategory::Effect, + ObjectCategory::GradientStop, + ObjectCategory::ViewBox + }; + return values; +} + +inline const char * const *EnumNamesObjectCategory() { + static const char * const names[15] = { + "Visual", + "Shape", + "Geometry", + "Brush", + "Animation", + "Easing", + "PropertySet", + "Surface", + "Clip", + "Controller", + "Shadow", + "Effect", + "GradientStop", + "ViewBox", + nullptr + }; + return names; +} + +inline const char *EnumNameObjectCategory(ObjectCategory e) { + if (::flatbuffers::IsOutRange(e, ObjectCategory::Visual, ObjectCategory::ViewBox)) return ""; + const size_t index = static_cast(e); + return EnumNamesObjectCategory()[index]; +} + +enum class VisualKind : uint8_t { + Container = 0, + Sprite = 1, + Shape = 2, + Layer = 3, + MIN = Container, + MAX = Layer +}; + +inline const VisualKind (&EnumValuesVisualKind())[4] { + static const VisualKind values[] = { + VisualKind::Container, + VisualKind::Sprite, + VisualKind::Shape, + VisualKind::Layer + }; + return values; +} + +inline const char * const *EnumNamesVisualKind() { + static const char * const names[5] = { + "Container", + "Sprite", + "Shape", + "Layer", + nullptr + }; + return names; +} + +inline const char *EnumNameVisualKind(VisualKind e) { + if (::flatbuffers::IsOutRange(e, VisualKind::Container, VisualKind::Layer)) return ""; + const size_t index = static_cast(e); + return EnumNamesVisualKind()[index]; +} + +enum class ShapeKind : uint8_t { + Container = 0, + Sprite = 1, + MIN = Container, + MAX = Sprite +}; + +inline const ShapeKind (&EnumValuesShapeKind())[2] { + static const ShapeKind values[] = { + ShapeKind::Container, + ShapeKind::Sprite + }; + return values; +} + +inline const char * const *EnumNamesShapeKind() { + static const char * const names[3] = { + "Container", + "Sprite", + nullptr + }; + return names; +} + +inline const char *EnumNameShapeKind(ShapeKind e) { + if (::flatbuffers::IsOutRange(e, ShapeKind::Container, ShapeKind::Sprite)) return ""; + const size_t index = static_cast(e); + return EnumNamesShapeKind()[index]; +} + +enum class GeometryKind : uint8_t { + Path = 0, + Rectangle = 1, + RoundedRectangle = 2, + Ellipse = 3, + MIN = Path, + MAX = Ellipse +}; + +inline const GeometryKind (&EnumValuesGeometryKind())[4] { + static const GeometryKind values[] = { + GeometryKind::Path, + GeometryKind::Rectangle, + GeometryKind::RoundedRectangle, + GeometryKind::Ellipse + }; + return values; +} + +inline const char * const *EnumNamesGeometryKind() { + static const char * const names[5] = { + "Path", + "Rectangle", + "RoundedRectangle", + "Ellipse", + nullptr + }; + return names; +} + +inline const char *EnumNameGeometryKind(GeometryKind e) { + if (::flatbuffers::IsOutRange(e, GeometryKind::Path, GeometryKind::Ellipse)) return ""; + const size_t index = static_cast(e); + return EnumNamesGeometryKind()[index]; +} + +enum class BrushKind : uint8_t { + Color = 0, + LinearGradient = 1, + RadialGradient = 2, + Surface = 3, + Mask = 4, + Effect = 5, + MIN = Color, + MAX = Effect +}; + +inline const BrushKind (&EnumValuesBrushKind())[6] { + static const BrushKind values[] = { + BrushKind::Color, + BrushKind::LinearGradient, + BrushKind::RadialGradient, + BrushKind::Surface, + BrushKind::Mask, + BrushKind::Effect + }; + return values; +} + +inline const char * const *EnumNamesBrushKind() { + static const char * const names[7] = { + "Color", + "LinearGradient", + "RadialGradient", + "Surface", + "Mask", + "Effect", + nullptr + }; + return names; +} + +inline const char *EnumNameBrushKind(BrushKind e) { + if (::flatbuffers::IsOutRange(e, BrushKind::Color, BrushKind::Effect)) return ""; + const size_t index = static_cast(e); + return EnumNamesBrushKind()[index]; +} + +enum class ClipKind : uint8_t { + Inset = 0, + Geometric = 1, + MIN = Inset, + MAX = Geometric +}; + +inline const ClipKind (&EnumValuesClipKind())[2] { + static const ClipKind values[] = { + ClipKind::Inset, + ClipKind::Geometric + }; + return values; +} + +inline const char * const *EnumNamesClipKind() { + static const char * const names[3] = { + "Inset", + "Geometric", + nullptr + }; + return names; +} + +inline const char *EnumNameClipKind(ClipKind e) { + if (::flatbuffers::IsOutRange(e, ClipKind::Inset, ClipKind::Geometric)) return ""; + const size_t index = static_cast(e); + return EnumNamesClipKind()[index]; +} + +enum class ShadowKind : uint8_t { + Drop = 0, + MIN = Drop, + MAX = Drop +}; + +inline const ShadowKind (&EnumValuesShadowKind())[1] { + static const ShadowKind values[] = { + ShadowKind::Drop + }; + return values; +} + +inline const char * const *EnumNamesShadowKind() { + static const char * const names[2] = { + "Drop", + nullptr + }; + return names; +} + +inline const char *EnumNameShadowKind(ShadowKind e) { + if (::flatbuffers::IsOutRange(e, ShadowKind::Drop, ShadowKind::Drop)) return ""; + const size_t index = static_cast(e); + return EnumNamesShadowKind()[index]; +} + +enum class SurfaceKind : uint8_t { + VisualSurface = 0, + LoadedImageFromUri = 1, + LoadedImageFromStream = 2, + MIN = VisualSurface, + MAX = LoadedImageFromStream +}; + +inline const SurfaceKind (&EnumValuesSurfaceKind())[3] { + static const SurfaceKind values[] = { + SurfaceKind::VisualSurface, + SurfaceKind::LoadedImageFromUri, + SurfaceKind::LoadedImageFromStream + }; + return values; +} + +inline const char * const *EnumNamesSurfaceKind() { + static const char * const names[4] = { + "VisualSurface", + "LoadedImageFromUri", + "LoadedImageFromStream", + nullptr + }; + return names; +} + +inline const char *EnumNameSurfaceKind(SurfaceKind e) { + if (::flatbuffers::IsOutRange(e, SurfaceKind::VisualSurface, SurfaceKind::LoadedImageFromStream)) return ""; + const size_t index = static_cast(e); + return EnumNamesSurfaceKind()[index]; +} + +enum class EffectKind : uint8_t { + Composite = 0, + GaussianBlur = 1, + MIN = Composite, + MAX = GaussianBlur +}; + +inline const EffectKind (&EnumValuesEffectKind())[2] { + static const EffectKind values[] = { + EffectKind::Composite, + EffectKind::GaussianBlur + }; + return values; +} + +inline const char * const *EnumNamesEffectKind() { + static const char * const names[3] = { + "Composite", + "GaussianBlur", + nullptr + }; + return names; +} + +inline const char *EnumNameEffectKind(EffectKind e) { + if (::flatbuffers::IsOutRange(e, EffectKind::Composite, EffectKind::GaussianBlur)) return ""; + const size_t index = static_cast(e); + return EnumNamesEffectKind()[index]; +} + +enum class EasingKind : uint8_t { + Linear = 0, + CubicBezier = 1, + Step = 2, + MIN = Linear, + MAX = Step +}; + +inline const EasingKind (&EnumValuesEasingKind())[3] { + static const EasingKind values[] = { + EasingKind::Linear, + EasingKind::CubicBezier, + EasingKind::Step + }; + return values; +} + +inline const char * const *EnumNamesEasingKind() { + static const char * const names[4] = { + "Linear", + "CubicBezier", + "Step", + nullptr + }; + return names; +} + +inline const char *EnumNameEasingKind(EasingKind e) { + if (::flatbuffers::IsOutRange(e, EasingKind::Linear, EasingKind::Step)) return ""; + const size_t index = static_cast(e); + return EnumNamesEasingKind()[index]; +} + +enum class AnimationKind : uint8_t { + Scalar = 0, + Vector2 = 1, + Vector3 = 2, + Vector4 = 3, + Color = 4, + Boolean = 5, + Path = 6, + Expression = 7, + MIN = Scalar, + MAX = Expression +}; + +inline const AnimationKind (&EnumValuesAnimationKind())[8] { + static const AnimationKind values[] = { + AnimationKind::Scalar, + AnimationKind::Vector2, + AnimationKind::Vector3, + AnimationKind::Vector4, + AnimationKind::Color, + AnimationKind::Boolean, + AnimationKind::Path, + AnimationKind::Expression + }; + return values; +} + +inline const char * const *EnumNamesAnimationKind() { + static const char * const names[9] = { + "Scalar", + "Vector2", + "Vector3", + "Vector4", + "Color", + "Boolean", + "Path", + "Expression", + nullptr + }; + return names; +} + +inline const char *EnumNameAnimationKind(AnimationKind e) { + if (::flatbuffers::IsOutRange(e, AnimationKind::Scalar, AnimationKind::Expression)) return ""; + const size_t index = static_cast(e); + return EnumNamesAnimationKind()[index]; +} + +enum class KeyFrameKind : uint8_t { + Value = 0, + Expression = 1, + MIN = Value, + MAX = Expression +}; + +inline const KeyFrameKind (&EnumValuesKeyFrameKind())[2] { + static const KeyFrameKind values[] = { + KeyFrameKind::Value, + KeyFrameKind::Expression + }; + return values; +} + +inline const char * const *EnumNamesKeyFrameKind() { + static const char * const names[3] = { + "Value", + "Expression", + nullptr + }; + return names; +} + +inline const char *EnumNameKeyFrameKind(KeyFrameKind e) { + if (::flatbuffers::IsOutRange(e, KeyFrameKind::Value, KeyFrameKind::Expression)) return ""; + const size_t index = static_cast(e); + return EnumNamesKeyFrameKind()[index]; +} + +enum class CanvasGeometryKind : uint8_t { + Combination = 0, + Ellipse = 1, + Group = 2, + Path = 3, + RoundedRectangle = 4, + TransformedGeometry = 5, + MIN = Combination, + MAX = TransformedGeometry +}; + +inline const CanvasGeometryKind (&EnumValuesCanvasGeometryKind())[6] { + static const CanvasGeometryKind values[] = { + CanvasGeometryKind::Combination, + CanvasGeometryKind::Ellipse, + CanvasGeometryKind::Group, + CanvasGeometryKind::Path, + CanvasGeometryKind::RoundedRectangle, + CanvasGeometryKind::TransformedGeometry + }; + return values; +} + +inline const char * const *EnumNamesCanvasGeometryKind() { + static const char * const names[7] = { + "Combination", + "Ellipse", + "Group", + "Path", + "RoundedRectangle", + "TransformedGeometry", + nullptr + }; + return names; +} + +inline const char *EnumNameCanvasGeometryKind(CanvasGeometryKind e) { + if (::flatbuffers::IsOutRange(e, CanvasGeometryKind::Combination, CanvasGeometryKind::TransformedGeometry)) return ""; + const size_t index = static_cast(e); + return EnumNamesCanvasGeometryKind()[index]; +} + +enum class PathOp : uint8_t { + BeginFigure = 0, + EndFigure = 1, + AddLine = 2, + AddCubicBezier = 3, + MIN = BeginFigure, + MAX = AddCubicBezier +}; + +inline const PathOp (&EnumValuesPathOp())[4] { + static const PathOp values[] = { + PathOp::BeginFigure, + PathOp::EndFigure, + PathOp::AddLine, + PathOp::AddCubicBezier + }; + return values; +} + +inline const char * const *EnumNamesPathOp() { + static const char * const names[5] = { + "BeginFigure", + "EndFigure", + "AddLine", + "AddCubicBezier", + nullptr + }; + return names; +} + +inline const char *EnumNamePathOp(PathOp e) { + if (::flatbuffers::IsOutRange(e, PathOp::BeginFigure, PathOp::AddCubicBezier)) return ""; + const size_t index = static_cast(e); + return EnumNamesPathOp()[index]; +} + +enum class PropertyValueType : uint8_t { + None = 0, + Color = 1, + Scalar = 2, + Vector2 = 3, + Vector3 = 4, + Vector4 = 5, + MIN = None, + MAX = Vector4 +}; + +inline const PropertyValueType (&EnumValuesPropertyValueType())[6] { + static const PropertyValueType values[] = { + PropertyValueType::None, + PropertyValueType::Color, + PropertyValueType::Scalar, + PropertyValueType::Vector2, + PropertyValueType::Vector3, + PropertyValueType::Vector4 + }; + return values; +} + +inline const char * const *EnumNamesPropertyValueType() { + static const char * const names[7] = { + "None", + "Color", + "Scalar", + "Vector2", + "Vector3", + "Vector4", + nullptr + }; + return names; +} + +inline const char *EnumNamePropertyValueType(PropertyValueType e) { + if (::flatbuffers::IsOutRange(e, PropertyValueType::None, PropertyValueType::Vector4)) return ""; + const size_t index = static_cast(e); + return EnumNamesPropertyValueType()[index]; +} + +FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) Vec2 FLATBUFFERS_FINAL_CLASS { + private: + float x_; + float y_; + + public: + Vec2() + : x_(0), + y_(0) { + } + Vec2(float _x, float _y) + : x_(::flatbuffers::EndianScalar(_x)), + y_(::flatbuffers::EndianScalar(_y)) { + } + float x() const { + return ::flatbuffers::EndianScalar(x_); + } + float y() const { + return ::flatbuffers::EndianScalar(y_); + } +}; +FLATBUFFERS_STRUCT_END(Vec2, 8); + +FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) Vec3 FLATBUFFERS_FINAL_CLASS { + private: + float x_; + float y_; + float z_; + + public: + Vec3() + : x_(0), + y_(0), + z_(0) { + } + Vec3(float _x, float _y, float _z) + : x_(::flatbuffers::EndianScalar(_x)), + y_(::flatbuffers::EndianScalar(_y)), + z_(::flatbuffers::EndianScalar(_z)) { + } + float x() const { + return ::flatbuffers::EndianScalar(x_); + } + float y() const { + return ::flatbuffers::EndianScalar(y_); + } + float z() const { + return ::flatbuffers::EndianScalar(z_); + } +}; +FLATBUFFERS_STRUCT_END(Vec3, 12); + +FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) Vec4 FLATBUFFERS_FINAL_CLASS { + private: + float x_; + float y_; + float z_; + float w_; + + public: + Vec4() + : x_(0), + y_(0), + z_(0), + w_(0) { + } + Vec4(float _x, float _y, float _z, float _w) + : x_(::flatbuffers::EndianScalar(_x)), + y_(::flatbuffers::EndianScalar(_y)), + z_(::flatbuffers::EndianScalar(_z)), + w_(::flatbuffers::EndianScalar(_w)) { + } + float x() const { + return ::flatbuffers::EndianScalar(x_); + } + float y() const { + return ::flatbuffers::EndianScalar(y_); + } + float z() const { + return ::flatbuffers::EndianScalar(z_); + } + float w() const { + return ::flatbuffers::EndianScalar(w_); + } +}; +FLATBUFFERS_STRUCT_END(Vec4, 16); + +FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) Mat3x2 FLATBUFFERS_FINAL_CLASS { + private: + float m11_; + float m12_; + float m21_; + float m22_; + float m31_; + float m32_; + + public: + Mat3x2() + : m11_(0), + m12_(0), + m21_(0), + m22_(0), + m31_(0), + m32_(0) { + } + Mat3x2(float _m11, float _m12, float _m21, float _m22, float _m31, float _m32) + : m11_(::flatbuffers::EndianScalar(_m11)), + m12_(::flatbuffers::EndianScalar(_m12)), + m21_(::flatbuffers::EndianScalar(_m21)), + m22_(::flatbuffers::EndianScalar(_m22)), + m31_(::flatbuffers::EndianScalar(_m31)), + m32_(::flatbuffers::EndianScalar(_m32)) { + } + float m11() const { + return ::flatbuffers::EndianScalar(m11_); + } + float m12() const { + return ::flatbuffers::EndianScalar(m12_); + } + float m21() const { + return ::flatbuffers::EndianScalar(m21_); + } + float m22() const { + return ::flatbuffers::EndianScalar(m22_); + } + float m31() const { + return ::flatbuffers::EndianScalar(m31_); + } + float m32() const { + return ::flatbuffers::EndianScalar(m32_); + } +}; +FLATBUFFERS_STRUCT_END(Mat3x2, 24); + +FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) Mat4x4 FLATBUFFERS_FINAL_CLASS { + private: + float m11_; + float m12_; + float m13_; + float m14_; + float m21_; + float m22_; + float m23_; + float m24_; + float m31_; + float m32_; + float m33_; + float m34_; + float m41_; + float m42_; + float m43_; + float m44_; + + public: + Mat4x4() + : m11_(0), + m12_(0), + m13_(0), + m14_(0), + m21_(0), + m22_(0), + m23_(0), + m24_(0), + m31_(0), + m32_(0), + m33_(0), + m34_(0), + m41_(0), + m42_(0), + m43_(0), + m44_(0) { + } + Mat4x4(float _m11, float _m12, float _m13, float _m14, float _m21, float _m22, float _m23, float _m24, float _m31, float _m32, float _m33, float _m34, float _m41, float _m42, float _m43, float _m44) + : m11_(::flatbuffers::EndianScalar(_m11)), + m12_(::flatbuffers::EndianScalar(_m12)), + m13_(::flatbuffers::EndianScalar(_m13)), + m14_(::flatbuffers::EndianScalar(_m14)), + m21_(::flatbuffers::EndianScalar(_m21)), + m22_(::flatbuffers::EndianScalar(_m22)), + m23_(::flatbuffers::EndianScalar(_m23)), + m24_(::flatbuffers::EndianScalar(_m24)), + m31_(::flatbuffers::EndianScalar(_m31)), + m32_(::flatbuffers::EndianScalar(_m32)), + m33_(::flatbuffers::EndianScalar(_m33)), + m34_(::flatbuffers::EndianScalar(_m34)), + m41_(::flatbuffers::EndianScalar(_m41)), + m42_(::flatbuffers::EndianScalar(_m42)), + m43_(::flatbuffers::EndianScalar(_m43)), + m44_(::flatbuffers::EndianScalar(_m44)) { + } + float m11() const { + return ::flatbuffers::EndianScalar(m11_); + } + float m12() const { + return ::flatbuffers::EndianScalar(m12_); + } + float m13() const { + return ::flatbuffers::EndianScalar(m13_); + } + float m14() const { + return ::flatbuffers::EndianScalar(m14_); + } + float m21() const { + return ::flatbuffers::EndianScalar(m21_); + } + float m22() const { + return ::flatbuffers::EndianScalar(m22_); + } + float m23() const { + return ::flatbuffers::EndianScalar(m23_); + } + float m24() const { + return ::flatbuffers::EndianScalar(m24_); + } + float m31() const { + return ::flatbuffers::EndianScalar(m31_); + } + float m32() const { + return ::flatbuffers::EndianScalar(m32_); + } + float m33() const { + return ::flatbuffers::EndianScalar(m33_); + } + float m34() const { + return ::flatbuffers::EndianScalar(m34_); + } + float m41() const { + return ::flatbuffers::EndianScalar(m41_); + } + float m42() const { + return ::flatbuffers::EndianScalar(m42_); + } + float m43() const { + return ::flatbuffers::EndianScalar(m43_); + } + float m44() const { + return ::flatbuffers::EndianScalar(m44_); + } +}; +FLATBUFFERS_STRUCT_END(Mat4x4, 64); + +FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(1) Color FLATBUFFERS_FINAL_CLASS { + private: + uint8_t a_; + uint8_t r_; + uint8_t g_; + uint8_t b_; + + public: + Color() + : a_(0), + r_(0), + g_(0), + b_(0) { + } + Color(uint8_t _a, uint8_t _r, uint8_t _g, uint8_t _b) + : a_(::flatbuffers::EndianScalar(_a)), + r_(::flatbuffers::EndianScalar(_r)), + g_(::flatbuffers::EndianScalar(_g)), + b_(::flatbuffers::EndianScalar(_b)) { + } + uint8_t a() const { + return ::flatbuffers::EndianScalar(a_); + } + uint8_t r() const { + return ::flatbuffers::EndianScalar(r_); + } + uint8_t g() const { + return ::flatbuffers::EndianScalar(g_); + } + uint8_t b() const { + return ::flatbuffers::EndianScalar(b_); + } +}; +FLATBUFFERS_STRUCT_END(Color, 4); + +struct Animator FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef AnimatorBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_PROPERTY = 4, + VT_ANIMATION = 6, + VT_CONTROLLER = 8 + }; + uint32_t property() const { + return GetField(VT_PROPERTY, 0); + } + uint32_t animation() const { + return GetField(VT_ANIMATION, 0); + } + uint32_t controller() const { + return GetField(VT_CONTROLLER, 4294967295); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyField(verifier, VT_PROPERTY, 4) && + VerifyField(verifier, VT_ANIMATION, 4) && + VerifyField(verifier, VT_CONTROLLER, 4) && + verifier.EndTable(); + } +}; + +struct AnimatorBuilder { + typedef Animator Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_property(uint32_t property) { + fbb_.AddElement(Animator::VT_PROPERTY, property, 0); + } + void add_animation(uint32_t animation) { + fbb_.AddElement(Animator::VT_ANIMATION, animation, 0); + } + void add_controller(uint32_t controller) { + fbb_.AddElement(Animator::VT_CONTROLLER, controller, 4294967295); + } + explicit AnimatorBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateAnimator( + ::flatbuffers::FlatBufferBuilder &_fbb, + uint32_t property = 0, + uint32_t animation = 0, + uint32_t controller = 4294967295) { + AnimatorBuilder builder_(_fbb); + builder_.add_controller(controller); + builder_.add_animation(animation); + builder_.add_property(property); + return builder_.Finish(); +} + +struct CompObj FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef CompObjBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_COMMENT = 4, + VT_ANIMATORS = 6, + VT_PROPERTIES = 8 + }; + uint32_t comment() const { + return GetField(VT_COMMENT, 4294967295); + } + const ::flatbuffers::Vector<::flatbuffers::Offset> *animators() const { + return GetPointer> *>(VT_ANIMATORS); + } + uint32_t properties() const { + return GetField(VT_PROPERTIES, 4294967295); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyField(verifier, VT_COMMENT, 4) && + VerifyOffset(verifier, VT_ANIMATORS) && + verifier.VerifyVector(animators()) && + verifier.VerifyVectorOfTables(animators()) && + VerifyField(verifier, VT_PROPERTIES, 4) && + verifier.EndTable(); + } +}; + +struct CompObjBuilder { + typedef CompObj Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_comment(uint32_t comment) { + fbb_.AddElement(CompObj::VT_COMMENT, comment, 4294967295); + } + void add_animators(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> animators) { + fbb_.AddOffset(CompObj::VT_ANIMATORS, animators); + } + void add_properties(uint32_t properties) { + fbb_.AddElement(CompObj::VT_PROPERTIES, properties, 4294967295); + } + explicit CompObjBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateCompObj( + ::flatbuffers::FlatBufferBuilder &_fbb, + uint32_t comment = 4294967295, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> animators = 0, + uint32_t properties = 4294967295) { + CompObjBuilder builder_(_fbb); + builder_.add_properties(properties); + builder_.add_animators(animators); + builder_.add_comment(comment); + return builder_.Finish(); +} + +inline ::flatbuffers::Offset CreateCompObjDirect( + ::flatbuffers::FlatBufferBuilder &_fbb, + uint32_t comment = 4294967295, + const std::vector<::flatbuffers::Offset> *animators = nullptr, + uint32_t properties = 4294967295) { + auto animators__ = animators ? _fbb.CreateVector<::flatbuffers::Offset>(*animators) : 0; + return CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CreateCompObj( + _fbb, + comment, + animators__, + properties); +} + +struct PropertyValue FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef PropertyValueBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_NAME = 4, + VT_TYPE = 6, + VT_SCALAR = 8, + VT_VECTOR = 10, + VT_COLOR = 12 + }; + uint32_t name() const { + return GetField(VT_NAME, 0); + } + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::PropertyValueType type() const { + return static_cast(GetField(VT_TYPE, 0)); + } + float scalar() const { + return GetField(VT_SCALAR, 0.0f); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec4 *vector() const { + return GetStruct(VT_VECTOR); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Color *color() const { + return GetStruct(VT_COLOR); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyField(verifier, VT_NAME, 4) && + VerifyField(verifier, VT_TYPE, 1) && + VerifyField(verifier, VT_SCALAR, 4) && + VerifyField(verifier, VT_VECTOR, 4) && + VerifyField(verifier, VT_COLOR, 1) && + verifier.EndTable(); + } +}; + +struct PropertyValueBuilder { + typedef PropertyValue Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_name(uint32_t name) { + fbb_.AddElement(PropertyValue::VT_NAME, name, 0); + } + void add_type(CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::PropertyValueType type) { + fbb_.AddElement(PropertyValue::VT_TYPE, static_cast(type), 0); + } + void add_scalar(float scalar) { + fbb_.AddElement(PropertyValue::VT_SCALAR, scalar, 0.0f); + } + void add_vector(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec4 *vector) { + fbb_.AddStruct(PropertyValue::VT_VECTOR, vector); + } + void add_color(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Color *color) { + fbb_.AddStruct(PropertyValue::VT_COLOR, color); + } + explicit PropertyValueBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreatePropertyValue( + ::flatbuffers::FlatBufferBuilder &_fbb, + uint32_t name = 0, + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::PropertyValueType type = CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::PropertyValueType::None, + float scalar = 0.0f, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec4 *vector = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Color *color = nullptr) { + PropertyValueBuilder builder_(_fbb); + builder_.add_color(color); + builder_.add_vector(vector); + builder_.add_scalar(scalar); + builder_.add_name(name); + builder_.add_type(type); + return builder_.Finish(); +} + +struct ReferenceParameter FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef ReferenceParameterBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_NAME = 4, + VT_TARGET = 6 + }; + uint32_t name() const { + return GetField(VT_NAME, 0); + } + uint32_t target() const { + return GetField(VT_TARGET, 0); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyField(verifier, VT_NAME, 4) && + VerifyField(verifier, VT_TARGET, 4) && + verifier.EndTable(); + } +}; + +struct ReferenceParameterBuilder { + typedef ReferenceParameter Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_name(uint32_t name) { + fbb_.AddElement(ReferenceParameter::VT_NAME, name, 0); + } + void add_target(uint32_t target) { + fbb_.AddElement(ReferenceParameter::VT_TARGET, target, 0); + } + explicit ReferenceParameterBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateReferenceParameter( + ::flatbuffers::FlatBufferBuilder &_fbb, + uint32_t name = 0, + uint32_t target = 0) { + ReferenceParameterBuilder builder_(_fbb); + builder_.add_target(target); + builder_.add_name(name); + return builder_.Finish(); +} + +struct SourceParameter FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef SourceParameterBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_NAME = 4, + VT_BRUSH = 6 + }; + uint32_t name() const { + return GetField(VT_NAME, 0); + } + uint32_t brush() const { + return GetField(VT_BRUSH, 0); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyField(verifier, VT_NAME, 4) && + VerifyField(verifier, VT_BRUSH, 4) && + verifier.EndTable(); + } +}; + +struct SourceParameterBuilder { + typedef SourceParameter Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_name(uint32_t name) { + fbb_.AddElement(SourceParameter::VT_NAME, name, 0); + } + void add_brush(uint32_t brush) { + fbb_.AddElement(SourceParameter::VT_BRUSH, brush, 0); + } + explicit SourceParameterBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateSourceParameter( + ::flatbuffers::FlatBufferBuilder &_fbb, + uint32_t name = 0, + uint32_t brush = 0) { + SourceParameterBuilder builder_(_fbb); + builder_.add_brush(brush); + builder_.add_name(name); + return builder_.Finish(); +} + +struct KeyFrame FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef KeyFrameBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_PROGRESS = 4, + VT_EASING = 6, + VT_KIND = 8, + VT_EXPRESSION = 10, + VT_SCALAR = 12, + VT_VECTOR = 14, + VT_COLOR = 16, + VT_PATH = 18 + }; + float progress() const { + return GetField(VT_PROGRESS, 0.0f); + } + uint32_t easing() const { + return GetField(VT_EASING, 4294967295); + } + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::KeyFrameKind kind() const { + return static_cast(GetField(VT_KIND, 0)); + } + uint32_t expression() const { + return GetField(VT_EXPRESSION, 4294967295); + } + float scalar() const { + return GetField(VT_SCALAR, 0.0f); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec4 *vector() const { + return GetStruct(VT_VECTOR); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Color *color() const { + return GetStruct(VT_COLOR); + } + uint32_t path() const { + return GetField(VT_PATH, 4294967295); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyField(verifier, VT_PROGRESS, 4) && + VerifyField(verifier, VT_EASING, 4) && + VerifyField(verifier, VT_KIND, 1) && + VerifyField(verifier, VT_EXPRESSION, 4) && + VerifyField(verifier, VT_SCALAR, 4) && + VerifyField(verifier, VT_VECTOR, 4) && + VerifyField(verifier, VT_COLOR, 1) && + VerifyField(verifier, VT_PATH, 4) && + verifier.EndTable(); + } +}; + +struct KeyFrameBuilder { + typedef KeyFrame Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_progress(float progress) { + fbb_.AddElement(KeyFrame::VT_PROGRESS, progress, 0.0f); + } + void add_easing(uint32_t easing) { + fbb_.AddElement(KeyFrame::VT_EASING, easing, 4294967295); + } + void add_kind(CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::KeyFrameKind kind) { + fbb_.AddElement(KeyFrame::VT_KIND, static_cast(kind), 0); + } + void add_expression(uint32_t expression) { + fbb_.AddElement(KeyFrame::VT_EXPRESSION, expression, 4294967295); + } + void add_scalar(float scalar) { + fbb_.AddElement(KeyFrame::VT_SCALAR, scalar, 0.0f); + } + void add_vector(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec4 *vector) { + fbb_.AddStruct(KeyFrame::VT_VECTOR, vector); + } + void add_color(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Color *color) { + fbb_.AddStruct(KeyFrame::VT_COLOR, color); + } + void add_path(uint32_t path) { + fbb_.AddElement(KeyFrame::VT_PATH, path, 4294967295); + } + explicit KeyFrameBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateKeyFrame( + ::flatbuffers::FlatBufferBuilder &_fbb, + float progress = 0.0f, + uint32_t easing = 4294967295, + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::KeyFrameKind kind = CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::KeyFrameKind::Value, + uint32_t expression = 4294967295, + float scalar = 0.0f, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec4 *vector = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Color *color = nullptr, + uint32_t path = 4294967295) { + KeyFrameBuilder builder_(_fbb); + builder_.add_path(path); + builder_.add_color(color); + builder_.add_vector(vector); + builder_.add_scalar(scalar); + builder_.add_expression(expression); + builder_.add_easing(easing); + builder_.add_progress(progress); + builder_.add_kind(kind); + return builder_.Finish(); +} + +struct Visual FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef VisualBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_BASE = 4, + VT_KIND = 6, + VT_BORDER_MODE = 8, + VT_CENTER_POINT = 10, + VT_CLIP = 12, + VT_IS_VISIBLE = 14, + VT_OFFSET = 16, + VT_OPACITY = 18, + VT_ROTATION_ANGLE_IN_DEGREES = 20, + VT_ROTATION_AXIS = 22, + VT_SCALE = 24, + VT_SIZE = 26, + VT_TRANSFORM_MATRIX = 28, + VT_CHILDREN = 30, + VT_SHAPES = 32, + VT_VIEW_BOX = 34, + VT_BRUSH = 36, + VT_SHADOW = 38 + }; + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CompObj *base() const { + return GetPointer(VT_BASE); + } + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::VisualKind kind() const { + return static_cast(GetField(VT_KIND, 0)); + } + ::flatbuffers::Optional border_mode() const { + return GetOptional(VT_BORDER_MODE); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec3 *center_point() const { + return GetStruct(VT_CENTER_POINT); + } + uint32_t clip() const { + return GetField(VT_CLIP, 4294967295); + } + ::flatbuffers::Optional is_visible() const { + return GetOptional(VT_IS_VISIBLE); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec3 *offset() const { + return GetStruct(VT_OFFSET); + } + ::flatbuffers::Optional opacity() const { + return GetOptional(VT_OPACITY); + } + ::flatbuffers::Optional rotation_angle_in_degrees() const { + return GetOptional(VT_ROTATION_ANGLE_IN_DEGREES); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec3 *rotation_axis() const { + return GetStruct(VT_ROTATION_AXIS); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec3 *scale() const { + return GetStruct(VT_SCALE); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *size() const { + return GetStruct(VT_SIZE); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Mat4x4 *transform_matrix() const { + return GetStruct(VT_TRANSFORM_MATRIX); + } + const ::flatbuffers::Vector *children() const { + return GetPointer *>(VT_CHILDREN); + } + const ::flatbuffers::Vector *shapes() const { + return GetPointer *>(VT_SHAPES); + } + uint32_t view_box() const { + return GetField(VT_VIEW_BOX, 4294967295); + } + uint32_t brush() const { + return GetField(VT_BRUSH, 4294967295); + } + uint32_t shadow() const { + return GetField(VT_SHADOW, 4294967295); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyOffset(verifier, VT_BASE) && + verifier.VerifyTable(base()) && + VerifyField(verifier, VT_KIND, 1) && + VerifyField(verifier, VT_BORDER_MODE, 1) && + VerifyField(verifier, VT_CENTER_POINT, 4) && + VerifyField(verifier, VT_CLIP, 4) && + VerifyField(verifier, VT_IS_VISIBLE, 1) && + VerifyField(verifier, VT_OFFSET, 4) && + VerifyField(verifier, VT_OPACITY, 4) && + VerifyField(verifier, VT_ROTATION_ANGLE_IN_DEGREES, 4) && + VerifyField(verifier, VT_ROTATION_AXIS, 4) && + VerifyField(verifier, VT_SCALE, 4) && + VerifyField(verifier, VT_SIZE, 4) && + VerifyField(verifier, VT_TRANSFORM_MATRIX, 4) && + VerifyOffset(verifier, VT_CHILDREN) && + verifier.VerifyVector(children()) && + VerifyOffset(verifier, VT_SHAPES) && + verifier.VerifyVector(shapes()) && + VerifyField(verifier, VT_VIEW_BOX, 4) && + VerifyField(verifier, VT_BRUSH, 4) && + VerifyField(verifier, VT_SHADOW, 4) && + verifier.EndTable(); + } +}; + +struct VisualBuilder { + typedef Visual Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_base(::flatbuffers::Offset base) { + fbb_.AddOffset(Visual::VT_BASE, base); + } + void add_kind(CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::VisualKind kind) { + fbb_.AddElement(Visual::VT_KIND, static_cast(kind), 0); + } + void add_border_mode(uint8_t border_mode) { + fbb_.AddElement(Visual::VT_BORDER_MODE, border_mode); + } + void add_center_point(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec3 *center_point) { + fbb_.AddStruct(Visual::VT_CENTER_POINT, center_point); + } + void add_clip(uint32_t clip) { + fbb_.AddElement(Visual::VT_CLIP, clip, 4294967295); + } + void add_is_visible(bool is_visible) { + fbb_.AddElement(Visual::VT_IS_VISIBLE, static_cast(is_visible)); + } + void add_offset(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec3 *offset) { + fbb_.AddStruct(Visual::VT_OFFSET, offset); + } + void add_opacity(float opacity) { + fbb_.AddElement(Visual::VT_OPACITY, opacity); + } + void add_rotation_angle_in_degrees(float rotation_angle_in_degrees) { + fbb_.AddElement(Visual::VT_ROTATION_ANGLE_IN_DEGREES, rotation_angle_in_degrees); + } + void add_rotation_axis(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec3 *rotation_axis) { + fbb_.AddStruct(Visual::VT_ROTATION_AXIS, rotation_axis); + } + void add_scale(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec3 *scale) { + fbb_.AddStruct(Visual::VT_SCALE, scale); + } + void add_size(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *size) { + fbb_.AddStruct(Visual::VT_SIZE, size); + } + void add_transform_matrix(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Mat4x4 *transform_matrix) { + fbb_.AddStruct(Visual::VT_TRANSFORM_MATRIX, transform_matrix); + } + void add_children(::flatbuffers::Offset<::flatbuffers::Vector> children) { + fbb_.AddOffset(Visual::VT_CHILDREN, children); + } + void add_shapes(::flatbuffers::Offset<::flatbuffers::Vector> shapes) { + fbb_.AddOffset(Visual::VT_SHAPES, shapes); + } + void add_view_box(uint32_t view_box) { + fbb_.AddElement(Visual::VT_VIEW_BOX, view_box, 4294967295); + } + void add_brush(uint32_t brush) { + fbb_.AddElement(Visual::VT_BRUSH, brush, 4294967295); + } + void add_shadow(uint32_t shadow) { + fbb_.AddElement(Visual::VT_SHADOW, shadow, 4294967295); + } + explicit VisualBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateVisual( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset base = 0, + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::VisualKind kind = CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::VisualKind::Container, + ::flatbuffers::Optional border_mode = ::flatbuffers::nullopt, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec3 *center_point = nullptr, + uint32_t clip = 4294967295, + ::flatbuffers::Optional is_visible = ::flatbuffers::nullopt, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec3 *offset = nullptr, + ::flatbuffers::Optional opacity = ::flatbuffers::nullopt, + ::flatbuffers::Optional rotation_angle_in_degrees = ::flatbuffers::nullopt, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec3 *rotation_axis = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec3 *scale = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *size = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Mat4x4 *transform_matrix = nullptr, + ::flatbuffers::Offset<::flatbuffers::Vector> children = 0, + ::flatbuffers::Offset<::flatbuffers::Vector> shapes = 0, + uint32_t view_box = 4294967295, + uint32_t brush = 4294967295, + uint32_t shadow = 4294967295) { + VisualBuilder builder_(_fbb); + builder_.add_shadow(shadow); + builder_.add_brush(brush); + builder_.add_view_box(view_box); + builder_.add_shapes(shapes); + builder_.add_children(children); + builder_.add_transform_matrix(transform_matrix); + builder_.add_size(size); + builder_.add_scale(scale); + builder_.add_rotation_axis(rotation_axis); + if(rotation_angle_in_degrees) { builder_.add_rotation_angle_in_degrees(*rotation_angle_in_degrees); } + if(opacity) { builder_.add_opacity(*opacity); } + builder_.add_offset(offset); + builder_.add_clip(clip); + builder_.add_center_point(center_point); + builder_.add_base(base); + if(is_visible) { builder_.add_is_visible(*is_visible); } + if(border_mode) { builder_.add_border_mode(*border_mode); } + builder_.add_kind(kind); + return builder_.Finish(); +} + +inline ::flatbuffers::Offset CreateVisualDirect( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset base = 0, + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::VisualKind kind = CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::VisualKind::Container, + ::flatbuffers::Optional border_mode = ::flatbuffers::nullopt, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec3 *center_point = nullptr, + uint32_t clip = 4294967295, + ::flatbuffers::Optional is_visible = ::flatbuffers::nullopt, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec3 *offset = nullptr, + ::flatbuffers::Optional opacity = ::flatbuffers::nullopt, + ::flatbuffers::Optional rotation_angle_in_degrees = ::flatbuffers::nullopt, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec3 *rotation_axis = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec3 *scale = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *size = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Mat4x4 *transform_matrix = nullptr, + const std::vector *children = nullptr, + const std::vector *shapes = nullptr, + uint32_t view_box = 4294967295, + uint32_t brush = 4294967295, + uint32_t shadow = 4294967295) { + auto children__ = children ? _fbb.CreateVector(*children) : 0; + auto shapes__ = shapes ? _fbb.CreateVector(*shapes) : 0; + return CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CreateVisual( + _fbb, + base, + kind, + border_mode, + center_point, + clip, + is_visible, + offset, + opacity, + rotation_angle_in_degrees, + rotation_axis, + scale, + size, + transform_matrix, + children__, + shapes__, + view_box, + brush, + shadow); +} + +struct Shape FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef ShapeBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_BASE = 4, + VT_KIND = 6, + VT_CENTER_POINT = 8, + VT_OFFSET = 10, + VT_ROTATION_ANGLE_IN_DEGREES = 12, + VT_SCALE = 14, + VT_TRANSFORM_MATRIX = 16, + VT_SHAPES = 18, + VT_FILL_BRUSH = 20, + VT_STROKE_BRUSH = 22, + VT_GEOMETRY = 24, + VT_IS_STROKE_NON_SCALING = 26, + VT_STROKE_DASH_OFFSET = 28, + VT_STROKE_DASH_ARRAY = 30, + VT_STROKE_DASH_CAP = 32, + VT_STROKE_START_CAP = 34, + VT_STROKE_END_CAP = 36, + VT_STROKE_LINE_JOIN = 38, + VT_STROKE_MITER_LIMIT = 40, + VT_STROKE_THICKNESS = 42 + }; + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CompObj *base() const { + return GetPointer(VT_BASE); + } + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::ShapeKind kind() const { + return static_cast(GetField(VT_KIND, 0)); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *center_point() const { + return GetStruct(VT_CENTER_POINT); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *offset() const { + return GetStruct(VT_OFFSET); + } + ::flatbuffers::Optional rotation_angle_in_degrees() const { + return GetOptional(VT_ROTATION_ANGLE_IN_DEGREES); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *scale() const { + return GetStruct(VT_SCALE); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Mat3x2 *transform_matrix() const { + return GetStruct(VT_TRANSFORM_MATRIX); + } + const ::flatbuffers::Vector *shapes() const { + return GetPointer *>(VT_SHAPES); + } + uint32_t fill_brush() const { + return GetField(VT_FILL_BRUSH, 4294967295); + } + uint32_t stroke_brush() const { + return GetField(VT_STROKE_BRUSH, 4294967295); + } + uint32_t geometry() const { + return GetField(VT_GEOMETRY, 4294967295); + } + ::flatbuffers::Optional is_stroke_non_scaling() const { + return GetOptional(VT_IS_STROKE_NON_SCALING); + } + ::flatbuffers::Optional stroke_dash_offset() const { + return GetOptional(VT_STROKE_DASH_OFFSET); + } + const ::flatbuffers::Vector *stroke_dash_array() const { + return GetPointer *>(VT_STROKE_DASH_ARRAY); + } + ::flatbuffers::Optional stroke_dash_cap() const { + return GetOptional(VT_STROKE_DASH_CAP); + } + ::flatbuffers::Optional stroke_start_cap() const { + return GetOptional(VT_STROKE_START_CAP); + } + ::flatbuffers::Optional stroke_end_cap() const { + return GetOptional(VT_STROKE_END_CAP); + } + ::flatbuffers::Optional stroke_line_join() const { + return GetOptional(VT_STROKE_LINE_JOIN); + } + ::flatbuffers::Optional stroke_miter_limit() const { + return GetOptional(VT_STROKE_MITER_LIMIT); + } + ::flatbuffers::Optional stroke_thickness() const { + return GetOptional(VT_STROKE_THICKNESS); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyOffset(verifier, VT_BASE) && + verifier.VerifyTable(base()) && + VerifyField(verifier, VT_KIND, 1) && + VerifyField(verifier, VT_CENTER_POINT, 4) && + VerifyField(verifier, VT_OFFSET, 4) && + VerifyField(verifier, VT_ROTATION_ANGLE_IN_DEGREES, 4) && + VerifyField(verifier, VT_SCALE, 4) && + VerifyField(verifier, VT_TRANSFORM_MATRIX, 4) && + VerifyOffset(verifier, VT_SHAPES) && + verifier.VerifyVector(shapes()) && + VerifyField(verifier, VT_FILL_BRUSH, 4) && + VerifyField(verifier, VT_STROKE_BRUSH, 4) && + VerifyField(verifier, VT_GEOMETRY, 4) && + VerifyField(verifier, VT_IS_STROKE_NON_SCALING, 1) && + VerifyField(verifier, VT_STROKE_DASH_OFFSET, 4) && + VerifyOffset(verifier, VT_STROKE_DASH_ARRAY) && + verifier.VerifyVector(stroke_dash_array()) && + VerifyField(verifier, VT_STROKE_DASH_CAP, 1) && + VerifyField(verifier, VT_STROKE_START_CAP, 1) && + VerifyField(verifier, VT_STROKE_END_CAP, 1) && + VerifyField(verifier, VT_STROKE_LINE_JOIN, 1) && + VerifyField(verifier, VT_STROKE_MITER_LIMIT, 4) && + VerifyField(verifier, VT_STROKE_THICKNESS, 4) && + verifier.EndTable(); + } +}; + +struct ShapeBuilder { + typedef Shape Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_base(::flatbuffers::Offset base) { + fbb_.AddOffset(Shape::VT_BASE, base); + } + void add_kind(CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::ShapeKind kind) { + fbb_.AddElement(Shape::VT_KIND, static_cast(kind), 0); + } + void add_center_point(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *center_point) { + fbb_.AddStruct(Shape::VT_CENTER_POINT, center_point); + } + void add_offset(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *offset) { + fbb_.AddStruct(Shape::VT_OFFSET, offset); + } + void add_rotation_angle_in_degrees(float rotation_angle_in_degrees) { + fbb_.AddElement(Shape::VT_ROTATION_ANGLE_IN_DEGREES, rotation_angle_in_degrees); + } + void add_scale(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *scale) { + fbb_.AddStruct(Shape::VT_SCALE, scale); + } + void add_transform_matrix(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Mat3x2 *transform_matrix) { + fbb_.AddStruct(Shape::VT_TRANSFORM_MATRIX, transform_matrix); + } + void add_shapes(::flatbuffers::Offset<::flatbuffers::Vector> shapes) { + fbb_.AddOffset(Shape::VT_SHAPES, shapes); + } + void add_fill_brush(uint32_t fill_brush) { + fbb_.AddElement(Shape::VT_FILL_BRUSH, fill_brush, 4294967295); + } + void add_stroke_brush(uint32_t stroke_brush) { + fbb_.AddElement(Shape::VT_STROKE_BRUSH, stroke_brush, 4294967295); + } + void add_geometry(uint32_t geometry) { + fbb_.AddElement(Shape::VT_GEOMETRY, geometry, 4294967295); + } + void add_is_stroke_non_scaling(bool is_stroke_non_scaling) { + fbb_.AddElement(Shape::VT_IS_STROKE_NON_SCALING, static_cast(is_stroke_non_scaling)); + } + void add_stroke_dash_offset(float stroke_dash_offset) { + fbb_.AddElement(Shape::VT_STROKE_DASH_OFFSET, stroke_dash_offset); + } + void add_stroke_dash_array(::flatbuffers::Offset<::flatbuffers::Vector> stroke_dash_array) { + fbb_.AddOffset(Shape::VT_STROKE_DASH_ARRAY, stroke_dash_array); + } + void add_stroke_dash_cap(uint8_t stroke_dash_cap) { + fbb_.AddElement(Shape::VT_STROKE_DASH_CAP, stroke_dash_cap); + } + void add_stroke_start_cap(uint8_t stroke_start_cap) { + fbb_.AddElement(Shape::VT_STROKE_START_CAP, stroke_start_cap); + } + void add_stroke_end_cap(uint8_t stroke_end_cap) { + fbb_.AddElement(Shape::VT_STROKE_END_CAP, stroke_end_cap); + } + void add_stroke_line_join(uint8_t stroke_line_join) { + fbb_.AddElement(Shape::VT_STROKE_LINE_JOIN, stroke_line_join); + } + void add_stroke_miter_limit(float stroke_miter_limit) { + fbb_.AddElement(Shape::VT_STROKE_MITER_LIMIT, stroke_miter_limit); + } + void add_stroke_thickness(float stroke_thickness) { + fbb_.AddElement(Shape::VT_STROKE_THICKNESS, stroke_thickness); + } + explicit ShapeBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateShape( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset base = 0, + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::ShapeKind kind = CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::ShapeKind::Container, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *center_point = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *offset = nullptr, + ::flatbuffers::Optional rotation_angle_in_degrees = ::flatbuffers::nullopt, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *scale = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Mat3x2 *transform_matrix = nullptr, + ::flatbuffers::Offset<::flatbuffers::Vector> shapes = 0, + uint32_t fill_brush = 4294967295, + uint32_t stroke_brush = 4294967295, + uint32_t geometry = 4294967295, + ::flatbuffers::Optional is_stroke_non_scaling = ::flatbuffers::nullopt, + ::flatbuffers::Optional stroke_dash_offset = ::flatbuffers::nullopt, + ::flatbuffers::Offset<::flatbuffers::Vector> stroke_dash_array = 0, + ::flatbuffers::Optional stroke_dash_cap = ::flatbuffers::nullopt, + ::flatbuffers::Optional stroke_start_cap = ::flatbuffers::nullopt, + ::flatbuffers::Optional stroke_end_cap = ::flatbuffers::nullopt, + ::flatbuffers::Optional stroke_line_join = ::flatbuffers::nullopt, + ::flatbuffers::Optional stroke_miter_limit = ::flatbuffers::nullopt, + ::flatbuffers::Optional stroke_thickness = ::flatbuffers::nullopt) { + ShapeBuilder builder_(_fbb); + if(stroke_thickness) { builder_.add_stroke_thickness(*stroke_thickness); } + if(stroke_miter_limit) { builder_.add_stroke_miter_limit(*stroke_miter_limit); } + builder_.add_stroke_dash_array(stroke_dash_array); + if(stroke_dash_offset) { builder_.add_stroke_dash_offset(*stroke_dash_offset); } + builder_.add_geometry(geometry); + builder_.add_stroke_brush(stroke_brush); + builder_.add_fill_brush(fill_brush); + builder_.add_shapes(shapes); + builder_.add_transform_matrix(transform_matrix); + builder_.add_scale(scale); + if(rotation_angle_in_degrees) { builder_.add_rotation_angle_in_degrees(*rotation_angle_in_degrees); } + builder_.add_offset(offset); + builder_.add_center_point(center_point); + builder_.add_base(base); + if(stroke_line_join) { builder_.add_stroke_line_join(*stroke_line_join); } + if(stroke_end_cap) { builder_.add_stroke_end_cap(*stroke_end_cap); } + if(stroke_start_cap) { builder_.add_stroke_start_cap(*stroke_start_cap); } + if(stroke_dash_cap) { builder_.add_stroke_dash_cap(*stroke_dash_cap); } + if(is_stroke_non_scaling) { builder_.add_is_stroke_non_scaling(*is_stroke_non_scaling); } + builder_.add_kind(kind); + return builder_.Finish(); +} + +inline ::flatbuffers::Offset CreateShapeDirect( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset base = 0, + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::ShapeKind kind = CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::ShapeKind::Container, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *center_point = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *offset = nullptr, + ::flatbuffers::Optional rotation_angle_in_degrees = ::flatbuffers::nullopt, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *scale = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Mat3x2 *transform_matrix = nullptr, + const std::vector *shapes = nullptr, + uint32_t fill_brush = 4294967295, + uint32_t stroke_brush = 4294967295, + uint32_t geometry = 4294967295, + ::flatbuffers::Optional is_stroke_non_scaling = ::flatbuffers::nullopt, + ::flatbuffers::Optional stroke_dash_offset = ::flatbuffers::nullopt, + const std::vector *stroke_dash_array = nullptr, + ::flatbuffers::Optional stroke_dash_cap = ::flatbuffers::nullopt, + ::flatbuffers::Optional stroke_start_cap = ::flatbuffers::nullopt, + ::flatbuffers::Optional stroke_end_cap = ::flatbuffers::nullopt, + ::flatbuffers::Optional stroke_line_join = ::flatbuffers::nullopt, + ::flatbuffers::Optional stroke_miter_limit = ::flatbuffers::nullopt, + ::flatbuffers::Optional stroke_thickness = ::flatbuffers::nullopt) { + auto shapes__ = shapes ? _fbb.CreateVector(*shapes) : 0; + auto stroke_dash_array__ = stroke_dash_array ? _fbb.CreateVector(*stroke_dash_array) : 0; + return CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CreateShape( + _fbb, + base, + kind, + center_point, + offset, + rotation_angle_in_degrees, + scale, + transform_matrix, + shapes__, + fill_brush, + stroke_brush, + geometry, + is_stroke_non_scaling, + stroke_dash_offset, + stroke_dash_array__, + stroke_dash_cap, + stroke_start_cap, + stroke_end_cap, + stroke_line_join, + stroke_miter_limit, + stroke_thickness); +} + +struct Geometry FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef GeometryBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_BASE = 4, + VT_KIND = 6, + VT_TRIM_START = 8, + VT_TRIM_END = 10, + VT_TRIM_OFFSET = 12, + VT_PATH = 14, + VT_OFFSET = 16, + VT_SIZE = 18, + VT_CORNER_RADIUS = 20, + VT_CENTER = 22, + VT_RADIUS = 24 + }; + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CompObj *base() const { + return GetPointer(VT_BASE); + } + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::GeometryKind kind() const { + return static_cast(GetField(VT_KIND, 0)); + } + ::flatbuffers::Optional trim_start() const { + return GetOptional(VT_TRIM_START); + } + ::flatbuffers::Optional trim_end() const { + return GetOptional(VT_TRIM_END); + } + ::flatbuffers::Optional trim_offset() const { + return GetOptional(VT_TRIM_OFFSET); + } + uint32_t path() const { + return GetField(VT_PATH, 4294967295); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *offset() const { + return GetStruct(VT_OFFSET); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *size() const { + return GetStruct(VT_SIZE); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *corner_radius() const { + return GetStruct(VT_CORNER_RADIUS); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *center() const { + return GetStruct(VT_CENTER); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *radius() const { + return GetStruct(VT_RADIUS); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyOffset(verifier, VT_BASE) && + verifier.VerifyTable(base()) && + VerifyField(verifier, VT_KIND, 1) && + VerifyField(verifier, VT_TRIM_START, 4) && + VerifyField(verifier, VT_TRIM_END, 4) && + VerifyField(verifier, VT_TRIM_OFFSET, 4) && + VerifyField(verifier, VT_PATH, 4) && + VerifyField(verifier, VT_OFFSET, 4) && + VerifyField(verifier, VT_SIZE, 4) && + VerifyField(verifier, VT_CORNER_RADIUS, 4) && + VerifyField(verifier, VT_CENTER, 4) && + VerifyField(verifier, VT_RADIUS, 4) && + verifier.EndTable(); + } +}; + +struct GeometryBuilder { + typedef Geometry Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_base(::flatbuffers::Offset base) { + fbb_.AddOffset(Geometry::VT_BASE, base); + } + void add_kind(CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::GeometryKind kind) { + fbb_.AddElement(Geometry::VT_KIND, static_cast(kind), 0); + } + void add_trim_start(float trim_start) { + fbb_.AddElement(Geometry::VT_TRIM_START, trim_start); + } + void add_trim_end(float trim_end) { + fbb_.AddElement(Geometry::VT_TRIM_END, trim_end); + } + void add_trim_offset(float trim_offset) { + fbb_.AddElement(Geometry::VT_TRIM_OFFSET, trim_offset); + } + void add_path(uint32_t path) { + fbb_.AddElement(Geometry::VT_PATH, path, 4294967295); + } + void add_offset(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *offset) { + fbb_.AddStruct(Geometry::VT_OFFSET, offset); + } + void add_size(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *size) { + fbb_.AddStruct(Geometry::VT_SIZE, size); + } + void add_corner_radius(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *corner_radius) { + fbb_.AddStruct(Geometry::VT_CORNER_RADIUS, corner_radius); + } + void add_center(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *center) { + fbb_.AddStruct(Geometry::VT_CENTER, center); + } + void add_radius(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *radius) { + fbb_.AddStruct(Geometry::VT_RADIUS, radius); + } + explicit GeometryBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateGeometry( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset base = 0, + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::GeometryKind kind = CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::GeometryKind::Path, + ::flatbuffers::Optional trim_start = ::flatbuffers::nullopt, + ::flatbuffers::Optional trim_end = ::flatbuffers::nullopt, + ::flatbuffers::Optional trim_offset = ::flatbuffers::nullopt, + uint32_t path = 4294967295, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *offset = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *size = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *corner_radius = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *center = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *radius = nullptr) { + GeometryBuilder builder_(_fbb); + builder_.add_radius(radius); + builder_.add_center(center); + builder_.add_corner_radius(corner_radius); + builder_.add_size(size); + builder_.add_offset(offset); + builder_.add_path(path); + if(trim_offset) { builder_.add_trim_offset(*trim_offset); } + if(trim_end) { builder_.add_trim_end(*trim_end); } + if(trim_start) { builder_.add_trim_start(*trim_start); } + builder_.add_base(base); + builder_.add_kind(kind); + return builder_.Finish(); +} + +struct Brush FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef BrushBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_BASE = 4, + VT_KIND = 6, + VT_COLOR = 8, + VT_ANCHOR_POINT = 10, + VT_CENTER_POINT = 12, + VT_COLOR_STOPS = 14, + VT_EXTEND_MODE = 16, + VT_INTERPOLATION_SPACE = 18, + VT_MAPPING_MODE = 20, + VT_OFFSET = 22, + VT_ROTATION_ANGLE_IN_DEGREES = 24, + VT_SCALE = 26, + VT_TRANSFORM_MATRIX = 28, + VT_START_POINT = 30, + VT_END_POINT = 32, + VT_ELLIPSE_CENTER = 34, + VT_ELLIPSE_RADIUS = 36, + VT_GRADIENT_ORIGIN_OFFSET = 38, + VT_SURFACE = 40, + VT_SOURCE = 42, + VT_MASK = 44, + VT_EFFECT = 46, + VT_SOURCE_PARAMETERS = 48 + }; + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CompObj *base() const { + return GetPointer(VT_BASE); + } + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::BrushKind kind() const { + return static_cast(GetField(VT_KIND, 0)); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Color *color() const { + return GetStruct(VT_COLOR); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *anchor_point() const { + return GetStruct(VT_ANCHOR_POINT); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *center_point() const { + return GetStruct(VT_CENTER_POINT); + } + const ::flatbuffers::Vector *color_stops() const { + return GetPointer *>(VT_COLOR_STOPS); + } + ::flatbuffers::Optional extend_mode() const { + return GetOptional(VT_EXTEND_MODE); + } + ::flatbuffers::Optional interpolation_space() const { + return GetOptional(VT_INTERPOLATION_SPACE); + } + ::flatbuffers::Optional mapping_mode() const { + return GetOptional(VT_MAPPING_MODE); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *offset() const { + return GetStruct(VT_OFFSET); + } + ::flatbuffers::Optional rotation_angle_in_degrees() const { + return GetOptional(VT_ROTATION_ANGLE_IN_DEGREES); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *scale() const { + return GetStruct(VT_SCALE); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Mat3x2 *transform_matrix() const { + return GetStruct(VT_TRANSFORM_MATRIX); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *start_point() const { + return GetStruct(VT_START_POINT); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *end_point() const { + return GetStruct(VT_END_POINT); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *ellipse_center() const { + return GetStruct(VT_ELLIPSE_CENTER); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *ellipse_radius() const { + return GetStruct(VT_ELLIPSE_RADIUS); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *gradient_origin_offset() const { + return GetStruct(VT_GRADIENT_ORIGIN_OFFSET); + } + uint32_t surface() const { + return GetField(VT_SURFACE, 4294967295); + } + uint32_t source() const { + return GetField(VT_SOURCE, 4294967295); + } + uint32_t mask() const { + return GetField(VT_MASK, 4294967295); + } + uint32_t effect() const { + return GetField(VT_EFFECT, 4294967295); + } + const ::flatbuffers::Vector<::flatbuffers::Offset> *source_parameters() const { + return GetPointer> *>(VT_SOURCE_PARAMETERS); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyOffset(verifier, VT_BASE) && + verifier.VerifyTable(base()) && + VerifyField(verifier, VT_KIND, 1) && + VerifyField(verifier, VT_COLOR, 1) && + VerifyField(verifier, VT_ANCHOR_POINT, 4) && + VerifyField(verifier, VT_CENTER_POINT, 4) && + VerifyOffset(verifier, VT_COLOR_STOPS) && + verifier.VerifyVector(color_stops()) && + VerifyField(verifier, VT_EXTEND_MODE, 1) && + VerifyField(verifier, VT_INTERPOLATION_SPACE, 1) && + VerifyField(verifier, VT_MAPPING_MODE, 1) && + VerifyField(verifier, VT_OFFSET, 4) && + VerifyField(verifier, VT_ROTATION_ANGLE_IN_DEGREES, 4) && + VerifyField(verifier, VT_SCALE, 4) && + VerifyField(verifier, VT_TRANSFORM_MATRIX, 4) && + VerifyField(verifier, VT_START_POINT, 4) && + VerifyField(verifier, VT_END_POINT, 4) && + VerifyField(verifier, VT_ELLIPSE_CENTER, 4) && + VerifyField(verifier, VT_ELLIPSE_RADIUS, 4) && + VerifyField(verifier, VT_GRADIENT_ORIGIN_OFFSET, 4) && + VerifyField(verifier, VT_SURFACE, 4) && + VerifyField(verifier, VT_SOURCE, 4) && + VerifyField(verifier, VT_MASK, 4) && + VerifyField(verifier, VT_EFFECT, 4) && + VerifyOffset(verifier, VT_SOURCE_PARAMETERS) && + verifier.VerifyVector(source_parameters()) && + verifier.VerifyVectorOfTables(source_parameters()) && + verifier.EndTable(); + } +}; + +struct BrushBuilder { + typedef Brush Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_base(::flatbuffers::Offset base) { + fbb_.AddOffset(Brush::VT_BASE, base); + } + void add_kind(CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::BrushKind kind) { + fbb_.AddElement(Brush::VT_KIND, static_cast(kind), 0); + } + void add_color(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Color *color) { + fbb_.AddStruct(Brush::VT_COLOR, color); + } + void add_anchor_point(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *anchor_point) { + fbb_.AddStruct(Brush::VT_ANCHOR_POINT, anchor_point); + } + void add_center_point(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *center_point) { + fbb_.AddStruct(Brush::VT_CENTER_POINT, center_point); + } + void add_color_stops(::flatbuffers::Offset<::flatbuffers::Vector> color_stops) { + fbb_.AddOffset(Brush::VT_COLOR_STOPS, color_stops); + } + void add_extend_mode(uint8_t extend_mode) { + fbb_.AddElement(Brush::VT_EXTEND_MODE, extend_mode); + } + void add_interpolation_space(uint8_t interpolation_space) { + fbb_.AddElement(Brush::VT_INTERPOLATION_SPACE, interpolation_space); + } + void add_mapping_mode(uint8_t mapping_mode) { + fbb_.AddElement(Brush::VT_MAPPING_MODE, mapping_mode); + } + void add_offset(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *offset) { + fbb_.AddStruct(Brush::VT_OFFSET, offset); + } + void add_rotation_angle_in_degrees(float rotation_angle_in_degrees) { + fbb_.AddElement(Brush::VT_ROTATION_ANGLE_IN_DEGREES, rotation_angle_in_degrees); + } + void add_scale(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *scale) { + fbb_.AddStruct(Brush::VT_SCALE, scale); + } + void add_transform_matrix(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Mat3x2 *transform_matrix) { + fbb_.AddStruct(Brush::VT_TRANSFORM_MATRIX, transform_matrix); + } + void add_start_point(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *start_point) { + fbb_.AddStruct(Brush::VT_START_POINT, start_point); + } + void add_end_point(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *end_point) { + fbb_.AddStruct(Brush::VT_END_POINT, end_point); + } + void add_ellipse_center(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *ellipse_center) { + fbb_.AddStruct(Brush::VT_ELLIPSE_CENTER, ellipse_center); + } + void add_ellipse_radius(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *ellipse_radius) { + fbb_.AddStruct(Brush::VT_ELLIPSE_RADIUS, ellipse_radius); + } + void add_gradient_origin_offset(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *gradient_origin_offset) { + fbb_.AddStruct(Brush::VT_GRADIENT_ORIGIN_OFFSET, gradient_origin_offset); + } + void add_surface(uint32_t surface) { + fbb_.AddElement(Brush::VT_SURFACE, surface, 4294967295); + } + void add_source(uint32_t source) { + fbb_.AddElement(Brush::VT_SOURCE, source, 4294967295); + } + void add_mask(uint32_t mask) { + fbb_.AddElement(Brush::VT_MASK, mask, 4294967295); + } + void add_effect(uint32_t effect) { + fbb_.AddElement(Brush::VT_EFFECT, effect, 4294967295); + } + void add_source_parameters(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> source_parameters) { + fbb_.AddOffset(Brush::VT_SOURCE_PARAMETERS, source_parameters); + } + explicit BrushBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateBrush( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset base = 0, + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::BrushKind kind = CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::BrushKind::Color, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Color *color = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *anchor_point = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *center_point = nullptr, + ::flatbuffers::Offset<::flatbuffers::Vector> color_stops = 0, + ::flatbuffers::Optional extend_mode = ::flatbuffers::nullopt, + ::flatbuffers::Optional interpolation_space = ::flatbuffers::nullopt, + ::flatbuffers::Optional mapping_mode = ::flatbuffers::nullopt, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *offset = nullptr, + ::flatbuffers::Optional rotation_angle_in_degrees = ::flatbuffers::nullopt, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *scale = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Mat3x2 *transform_matrix = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *start_point = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *end_point = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *ellipse_center = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *ellipse_radius = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *gradient_origin_offset = nullptr, + uint32_t surface = 4294967295, + uint32_t source = 4294967295, + uint32_t mask = 4294967295, + uint32_t effect = 4294967295, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> source_parameters = 0) { + BrushBuilder builder_(_fbb); + builder_.add_source_parameters(source_parameters); + builder_.add_effect(effect); + builder_.add_mask(mask); + builder_.add_source(source); + builder_.add_surface(surface); + builder_.add_gradient_origin_offset(gradient_origin_offset); + builder_.add_ellipse_radius(ellipse_radius); + builder_.add_ellipse_center(ellipse_center); + builder_.add_end_point(end_point); + builder_.add_start_point(start_point); + builder_.add_transform_matrix(transform_matrix); + builder_.add_scale(scale); + if(rotation_angle_in_degrees) { builder_.add_rotation_angle_in_degrees(*rotation_angle_in_degrees); } + builder_.add_offset(offset); + builder_.add_color_stops(color_stops); + builder_.add_center_point(center_point); + builder_.add_anchor_point(anchor_point); + builder_.add_color(color); + builder_.add_base(base); + if(mapping_mode) { builder_.add_mapping_mode(*mapping_mode); } + if(interpolation_space) { builder_.add_interpolation_space(*interpolation_space); } + if(extend_mode) { builder_.add_extend_mode(*extend_mode); } + builder_.add_kind(kind); + return builder_.Finish(); +} + +inline ::flatbuffers::Offset CreateBrushDirect( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset base = 0, + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::BrushKind kind = CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::BrushKind::Color, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Color *color = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *anchor_point = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *center_point = nullptr, + const std::vector *color_stops = nullptr, + ::flatbuffers::Optional extend_mode = ::flatbuffers::nullopt, + ::flatbuffers::Optional interpolation_space = ::flatbuffers::nullopt, + ::flatbuffers::Optional mapping_mode = ::flatbuffers::nullopt, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *offset = nullptr, + ::flatbuffers::Optional rotation_angle_in_degrees = ::flatbuffers::nullopt, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *scale = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Mat3x2 *transform_matrix = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *start_point = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *end_point = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *ellipse_center = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *ellipse_radius = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *gradient_origin_offset = nullptr, + uint32_t surface = 4294967295, + uint32_t source = 4294967295, + uint32_t mask = 4294967295, + uint32_t effect = 4294967295, + const std::vector<::flatbuffers::Offset> *source_parameters = nullptr) { + auto color_stops__ = color_stops ? _fbb.CreateVector(*color_stops) : 0; + auto source_parameters__ = source_parameters ? _fbb.CreateVector<::flatbuffers::Offset>(*source_parameters) : 0; + return CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CreateBrush( + _fbb, + base, + kind, + color, + anchor_point, + center_point, + color_stops__, + extend_mode, + interpolation_space, + mapping_mode, + offset, + rotation_angle_in_degrees, + scale, + transform_matrix, + start_point, + end_point, + ellipse_center, + ellipse_radius, + gradient_origin_offset, + surface, + source, + mask, + effect, + source_parameters__); +} + +struct GradientStop FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef GradientStopBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_BASE = 4, + VT_COLOR = 6, + VT_OFFSET = 8 + }; + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CompObj *base() const { + return GetPointer(VT_BASE); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Color *color() const { + return GetStruct(VT_COLOR); + } + float offset() const { + return GetField(VT_OFFSET, 0.0f); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyOffset(verifier, VT_BASE) && + verifier.VerifyTable(base()) && + VerifyField(verifier, VT_COLOR, 1) && + VerifyField(verifier, VT_OFFSET, 4) && + verifier.EndTable(); + } +}; + +struct GradientStopBuilder { + typedef GradientStop Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_base(::flatbuffers::Offset base) { + fbb_.AddOffset(GradientStop::VT_BASE, base); + } + void add_color(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Color *color) { + fbb_.AddStruct(GradientStop::VT_COLOR, color); + } + void add_offset(float offset) { + fbb_.AddElement(GradientStop::VT_OFFSET, offset, 0.0f); + } + explicit GradientStopBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateGradientStop( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset base = 0, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Color *color = nullptr, + float offset = 0.0f) { + GradientStopBuilder builder_(_fbb); + builder_.add_offset(offset); + builder_.add_color(color); + builder_.add_base(base); + return builder_.Finish(); +} + +struct ViewBox FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef ViewBoxBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_BASE = 4, + VT_SIZE = 6 + }; + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CompObj *base() const { + return GetPointer(VT_BASE); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *size() const { + return GetStruct(VT_SIZE); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyOffset(verifier, VT_BASE) && + verifier.VerifyTable(base()) && + VerifyField(verifier, VT_SIZE, 4) && + verifier.EndTable(); + } +}; + +struct ViewBoxBuilder { + typedef ViewBox Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_base(::flatbuffers::Offset base) { + fbb_.AddOffset(ViewBox::VT_BASE, base); + } + void add_size(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *size) { + fbb_.AddStruct(ViewBox::VT_SIZE, size); + } + explicit ViewBoxBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateViewBox( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset base = 0, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *size = nullptr) { + ViewBoxBuilder builder_(_fbb); + builder_.add_size(size); + builder_.add_base(base); + return builder_.Finish(); +} + +struct Clip FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef ClipBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_BASE = 4, + VT_KIND = 6, + VT_CENTER_POINT = 8, + VT_SCALE = 10, + VT_LEFT_INSET = 12, + VT_RIGHT_INSET = 14, + VT_TOP_INSET = 16, + VT_BOTTOM_INSET = 18, + VT_GEOMETRY = 20 + }; + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CompObj *base() const { + return GetPointer(VT_BASE); + } + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::ClipKind kind() const { + return static_cast(GetField(VT_KIND, 0)); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *center_point() const { + return GetStruct(VT_CENTER_POINT); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *scale() const { + return GetStruct(VT_SCALE); + } + ::flatbuffers::Optional left_inset() const { + return GetOptional(VT_LEFT_INSET); + } + ::flatbuffers::Optional right_inset() const { + return GetOptional(VT_RIGHT_INSET); + } + ::flatbuffers::Optional top_inset() const { + return GetOptional(VT_TOP_INSET); + } + ::flatbuffers::Optional bottom_inset() const { + return GetOptional(VT_BOTTOM_INSET); + } + uint32_t geometry() const { + return GetField(VT_GEOMETRY, 4294967295); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyOffset(verifier, VT_BASE) && + verifier.VerifyTable(base()) && + VerifyField(verifier, VT_KIND, 1) && + VerifyField(verifier, VT_CENTER_POINT, 4) && + VerifyField(verifier, VT_SCALE, 4) && + VerifyField(verifier, VT_LEFT_INSET, 4) && + VerifyField(verifier, VT_RIGHT_INSET, 4) && + VerifyField(verifier, VT_TOP_INSET, 4) && + VerifyField(verifier, VT_BOTTOM_INSET, 4) && + VerifyField(verifier, VT_GEOMETRY, 4) && + verifier.EndTable(); + } +}; + +struct ClipBuilder { + typedef Clip Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_base(::flatbuffers::Offset base) { + fbb_.AddOffset(Clip::VT_BASE, base); + } + void add_kind(CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::ClipKind kind) { + fbb_.AddElement(Clip::VT_KIND, static_cast(kind), 0); + } + void add_center_point(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *center_point) { + fbb_.AddStruct(Clip::VT_CENTER_POINT, center_point); + } + void add_scale(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *scale) { + fbb_.AddStruct(Clip::VT_SCALE, scale); + } + void add_left_inset(float left_inset) { + fbb_.AddElement(Clip::VT_LEFT_INSET, left_inset); + } + void add_right_inset(float right_inset) { + fbb_.AddElement(Clip::VT_RIGHT_INSET, right_inset); + } + void add_top_inset(float top_inset) { + fbb_.AddElement(Clip::VT_TOP_INSET, top_inset); + } + void add_bottom_inset(float bottom_inset) { + fbb_.AddElement(Clip::VT_BOTTOM_INSET, bottom_inset); + } + void add_geometry(uint32_t geometry) { + fbb_.AddElement(Clip::VT_GEOMETRY, geometry, 4294967295); + } + explicit ClipBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateClip( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset base = 0, + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::ClipKind kind = CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::ClipKind::Inset, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *center_point = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *scale = nullptr, + ::flatbuffers::Optional left_inset = ::flatbuffers::nullopt, + ::flatbuffers::Optional right_inset = ::flatbuffers::nullopt, + ::flatbuffers::Optional top_inset = ::flatbuffers::nullopt, + ::flatbuffers::Optional bottom_inset = ::flatbuffers::nullopt, + uint32_t geometry = 4294967295) { + ClipBuilder builder_(_fbb); + builder_.add_geometry(geometry); + if(bottom_inset) { builder_.add_bottom_inset(*bottom_inset); } + if(top_inset) { builder_.add_top_inset(*top_inset); } + if(right_inset) { builder_.add_right_inset(*right_inset); } + if(left_inset) { builder_.add_left_inset(*left_inset); } + builder_.add_scale(scale); + builder_.add_center_point(center_point); + builder_.add_base(base); + builder_.add_kind(kind); + return builder_.Finish(); +} + +struct Shadow FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef ShadowBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_BASE = 4, + VT_KIND = 6, + VT_BLUR_RADIUS = 8, + VT_COLOR = 10, + VT_MASK = 12, + VT_OFFSET = 14, + VT_OPACITY = 16, + VT_SOURCE_POLICY = 18 + }; + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CompObj *base() const { + return GetPointer(VT_BASE); + } + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::ShadowKind kind() const { + return static_cast(GetField(VT_KIND, 0)); + } + ::flatbuffers::Optional blur_radius() const { + return GetOptional(VT_BLUR_RADIUS); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Color *color() const { + return GetStruct(VT_COLOR); + } + uint32_t mask() const { + return GetField(VT_MASK, 4294967295); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec3 *offset() const { + return GetStruct(VT_OFFSET); + } + ::flatbuffers::Optional opacity() const { + return GetOptional(VT_OPACITY); + } + ::flatbuffers::Optional source_policy() const { + return GetOptional(VT_SOURCE_POLICY); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyOffset(verifier, VT_BASE) && + verifier.VerifyTable(base()) && + VerifyField(verifier, VT_KIND, 1) && + VerifyField(verifier, VT_BLUR_RADIUS, 4) && + VerifyField(verifier, VT_COLOR, 1) && + VerifyField(verifier, VT_MASK, 4) && + VerifyField(verifier, VT_OFFSET, 4) && + VerifyField(verifier, VT_OPACITY, 4) && + VerifyField(verifier, VT_SOURCE_POLICY, 1) && + verifier.EndTable(); + } +}; + +struct ShadowBuilder { + typedef Shadow Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_base(::flatbuffers::Offset base) { + fbb_.AddOffset(Shadow::VT_BASE, base); + } + void add_kind(CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::ShadowKind kind) { + fbb_.AddElement(Shadow::VT_KIND, static_cast(kind), 0); + } + void add_blur_radius(float blur_radius) { + fbb_.AddElement(Shadow::VT_BLUR_RADIUS, blur_radius); + } + void add_color(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Color *color) { + fbb_.AddStruct(Shadow::VT_COLOR, color); + } + void add_mask(uint32_t mask) { + fbb_.AddElement(Shadow::VT_MASK, mask, 4294967295); + } + void add_offset(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec3 *offset) { + fbb_.AddStruct(Shadow::VT_OFFSET, offset); + } + void add_opacity(float opacity) { + fbb_.AddElement(Shadow::VT_OPACITY, opacity); + } + void add_source_policy(uint8_t source_policy) { + fbb_.AddElement(Shadow::VT_SOURCE_POLICY, source_policy); + } + explicit ShadowBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateShadow( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset base = 0, + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::ShadowKind kind = CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::ShadowKind::Drop, + ::flatbuffers::Optional blur_radius = ::flatbuffers::nullopt, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Color *color = nullptr, + uint32_t mask = 4294967295, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec3 *offset = nullptr, + ::flatbuffers::Optional opacity = ::flatbuffers::nullopt, + ::flatbuffers::Optional source_policy = ::flatbuffers::nullopt) { + ShadowBuilder builder_(_fbb); + if(opacity) { builder_.add_opacity(*opacity); } + builder_.add_offset(offset); + builder_.add_mask(mask); + builder_.add_color(color); + if(blur_radius) { builder_.add_blur_radius(*blur_radius); } + builder_.add_base(base); + if(source_policy) { builder_.add_source_policy(*source_policy); } + builder_.add_kind(kind); + return builder_.Finish(); +} + +struct Surface FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef SurfaceBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_BASE = 4, + VT_KIND = 6, + VT_SOURCE_VISUAL = 8, + VT_SOURCE_SIZE = 10, + VT_SOURCE_OFFSET = 12, + VT_URI = 14, + VT_BYTES = 16 + }; + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CompObj *base() const { + return GetPointer(VT_BASE); + } + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::SurfaceKind kind() const { + return static_cast(GetField(VT_KIND, 0)); + } + uint32_t source_visual() const { + return GetField(VT_SOURCE_VISUAL, 4294967295); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *source_size() const { + return GetStruct(VT_SOURCE_SIZE); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *source_offset() const { + return GetStruct(VT_SOURCE_OFFSET); + } + uint32_t uri() const { + return GetField(VT_URI, 4294967295); + } + const ::flatbuffers::Vector *bytes() const { + return GetPointer *>(VT_BYTES); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyOffset(verifier, VT_BASE) && + verifier.VerifyTable(base()) && + VerifyField(verifier, VT_KIND, 1) && + VerifyField(verifier, VT_SOURCE_VISUAL, 4) && + VerifyField(verifier, VT_SOURCE_SIZE, 4) && + VerifyField(verifier, VT_SOURCE_OFFSET, 4) && + VerifyField(verifier, VT_URI, 4) && + VerifyOffset(verifier, VT_BYTES) && + verifier.VerifyVector(bytes()) && + verifier.EndTable(); + } +}; + +struct SurfaceBuilder { + typedef Surface Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_base(::flatbuffers::Offset base) { + fbb_.AddOffset(Surface::VT_BASE, base); + } + void add_kind(CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::SurfaceKind kind) { + fbb_.AddElement(Surface::VT_KIND, static_cast(kind), 0); + } + void add_source_visual(uint32_t source_visual) { + fbb_.AddElement(Surface::VT_SOURCE_VISUAL, source_visual, 4294967295); + } + void add_source_size(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *source_size) { + fbb_.AddStruct(Surface::VT_SOURCE_SIZE, source_size); + } + void add_source_offset(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *source_offset) { + fbb_.AddStruct(Surface::VT_SOURCE_OFFSET, source_offset); + } + void add_uri(uint32_t uri) { + fbb_.AddElement(Surface::VT_URI, uri, 4294967295); + } + void add_bytes(::flatbuffers::Offset<::flatbuffers::Vector> bytes) { + fbb_.AddOffset(Surface::VT_BYTES, bytes); + } + explicit SurfaceBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateSurface( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset base = 0, + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::SurfaceKind kind = CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::SurfaceKind::VisualSurface, + uint32_t source_visual = 4294967295, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *source_size = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *source_offset = nullptr, + uint32_t uri = 4294967295, + ::flatbuffers::Offset<::flatbuffers::Vector> bytes = 0) { + SurfaceBuilder builder_(_fbb); + builder_.add_bytes(bytes); + builder_.add_uri(uri); + builder_.add_source_offset(source_offset); + builder_.add_source_size(source_size); + builder_.add_source_visual(source_visual); + builder_.add_base(base); + builder_.add_kind(kind); + return builder_.Finish(); +} + +inline ::flatbuffers::Offset CreateSurfaceDirect( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset base = 0, + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::SurfaceKind kind = CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::SurfaceKind::VisualSurface, + uint32_t source_visual = 4294967295, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *source_size = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *source_offset = nullptr, + uint32_t uri = 4294967295, + const std::vector *bytes = nullptr) { + auto bytes__ = bytes ? _fbb.CreateVector(*bytes) : 0; + return CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CreateSurface( + _fbb, + base, + kind, + source_visual, + source_size, + source_offset, + uri, + bytes__); +} + +struct Effect FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef EffectBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_BASE = 4, + VT_KIND = 6, + VT_SOURCES = 8, + VT_MODE = 10, + VT_BLUR_AMOUNT = 12 + }; + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CompObj *base() const { + return GetPointer(VT_BASE); + } + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::EffectKind kind() const { + return static_cast(GetField(VT_KIND, 0)); + } + const ::flatbuffers::Vector *sources() const { + return GetPointer *>(VT_SOURCES); + } + uint8_t mode() const { + return GetField(VT_MODE, 0); + } + float blur_amount() const { + return GetField(VT_BLUR_AMOUNT, 0.0f); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyOffset(verifier, VT_BASE) && + verifier.VerifyTable(base()) && + VerifyField(verifier, VT_KIND, 1) && + VerifyOffset(verifier, VT_SOURCES) && + verifier.VerifyVector(sources()) && + VerifyField(verifier, VT_MODE, 1) && + VerifyField(verifier, VT_BLUR_AMOUNT, 4) && + verifier.EndTable(); + } +}; + +struct EffectBuilder { + typedef Effect Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_base(::flatbuffers::Offset base) { + fbb_.AddOffset(Effect::VT_BASE, base); + } + void add_kind(CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::EffectKind kind) { + fbb_.AddElement(Effect::VT_KIND, static_cast(kind), 0); + } + void add_sources(::flatbuffers::Offset<::flatbuffers::Vector> sources) { + fbb_.AddOffset(Effect::VT_SOURCES, sources); + } + void add_mode(uint8_t mode) { + fbb_.AddElement(Effect::VT_MODE, mode, 0); + } + void add_blur_amount(float blur_amount) { + fbb_.AddElement(Effect::VT_BLUR_AMOUNT, blur_amount, 0.0f); + } + explicit EffectBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateEffect( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset base = 0, + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::EffectKind kind = CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::EffectKind::Composite, + ::flatbuffers::Offset<::flatbuffers::Vector> sources = 0, + uint8_t mode = 0, + float blur_amount = 0.0f) { + EffectBuilder builder_(_fbb); + builder_.add_blur_amount(blur_amount); + builder_.add_sources(sources); + builder_.add_base(base); + builder_.add_mode(mode); + builder_.add_kind(kind); + return builder_.Finish(); +} + +inline ::flatbuffers::Offset CreateEffectDirect( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset base = 0, + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::EffectKind kind = CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::EffectKind::Composite, + const std::vector *sources = nullptr, + uint8_t mode = 0, + float blur_amount = 0.0f) { + auto sources__ = sources ? _fbb.CreateVector(*sources) : 0; + return CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CreateEffect( + _fbb, + base, + kind, + sources__, + mode, + blur_amount); +} + +struct Easing FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef EasingBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_BASE = 4, + VT_KIND = 6, + VT_CONTROL_POINT_1 = 8, + VT_CONTROL_POINT_2 = 10, + VT_STEP_COUNT = 12, + VT_INITIAL_STEP = 14, + VT_FINAL_STEP = 16, + VT_IS_INITIAL_STEP_SINGLE_FRAME = 18, + VT_IS_FINAL_STEP_SINGLE_FRAME = 20 + }; + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CompObj *base() const { + return GetPointer(VT_BASE); + } + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::EasingKind kind() const { + return static_cast(GetField(VT_KIND, 0)); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *control_point_1() const { + return GetStruct(VT_CONTROL_POINT_1); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *control_point_2() const { + return GetStruct(VT_CONTROL_POINT_2); + } + ::flatbuffers::Optional step_count() const { + return GetOptional(VT_STEP_COUNT); + } + ::flatbuffers::Optional initial_step() const { + return GetOptional(VT_INITIAL_STEP); + } + ::flatbuffers::Optional final_step() const { + return GetOptional(VT_FINAL_STEP); + } + ::flatbuffers::Optional is_initial_step_single_frame() const { + return GetOptional(VT_IS_INITIAL_STEP_SINGLE_FRAME); + } + ::flatbuffers::Optional is_final_step_single_frame() const { + return GetOptional(VT_IS_FINAL_STEP_SINGLE_FRAME); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyOffset(verifier, VT_BASE) && + verifier.VerifyTable(base()) && + VerifyField(verifier, VT_KIND, 1) && + VerifyField(verifier, VT_CONTROL_POINT_1, 4) && + VerifyField(verifier, VT_CONTROL_POINT_2, 4) && + VerifyField(verifier, VT_STEP_COUNT, 4) && + VerifyField(verifier, VT_INITIAL_STEP, 4) && + VerifyField(verifier, VT_FINAL_STEP, 4) && + VerifyField(verifier, VT_IS_INITIAL_STEP_SINGLE_FRAME, 1) && + VerifyField(verifier, VT_IS_FINAL_STEP_SINGLE_FRAME, 1) && + verifier.EndTable(); + } +}; + +struct EasingBuilder { + typedef Easing Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_base(::flatbuffers::Offset base) { + fbb_.AddOffset(Easing::VT_BASE, base); + } + void add_kind(CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::EasingKind kind) { + fbb_.AddElement(Easing::VT_KIND, static_cast(kind), 0); + } + void add_control_point_1(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *control_point_1) { + fbb_.AddStruct(Easing::VT_CONTROL_POINT_1, control_point_1); + } + void add_control_point_2(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *control_point_2) { + fbb_.AddStruct(Easing::VT_CONTROL_POINT_2, control_point_2); + } + void add_step_count(int32_t step_count) { + fbb_.AddElement(Easing::VT_STEP_COUNT, step_count); + } + void add_initial_step(int32_t initial_step) { + fbb_.AddElement(Easing::VT_INITIAL_STEP, initial_step); + } + void add_final_step(int32_t final_step) { + fbb_.AddElement(Easing::VT_FINAL_STEP, final_step); + } + void add_is_initial_step_single_frame(bool is_initial_step_single_frame) { + fbb_.AddElement(Easing::VT_IS_INITIAL_STEP_SINGLE_FRAME, static_cast(is_initial_step_single_frame)); + } + void add_is_final_step_single_frame(bool is_final_step_single_frame) { + fbb_.AddElement(Easing::VT_IS_FINAL_STEP_SINGLE_FRAME, static_cast(is_final_step_single_frame)); + } + explicit EasingBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateEasing( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset base = 0, + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::EasingKind kind = CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::EasingKind::Linear, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *control_point_1 = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *control_point_2 = nullptr, + ::flatbuffers::Optional step_count = ::flatbuffers::nullopt, + ::flatbuffers::Optional initial_step = ::flatbuffers::nullopt, + ::flatbuffers::Optional final_step = ::flatbuffers::nullopt, + ::flatbuffers::Optional is_initial_step_single_frame = ::flatbuffers::nullopt, + ::flatbuffers::Optional is_final_step_single_frame = ::flatbuffers::nullopt) { + EasingBuilder builder_(_fbb); + if(final_step) { builder_.add_final_step(*final_step); } + if(initial_step) { builder_.add_initial_step(*initial_step); } + if(step_count) { builder_.add_step_count(*step_count); } + builder_.add_control_point_2(control_point_2); + builder_.add_control_point_1(control_point_1); + builder_.add_base(base); + if(is_final_step_single_frame) { builder_.add_is_final_step_single_frame(*is_final_step_single_frame); } + if(is_initial_step_single_frame) { builder_.add_is_initial_step_single_frame(*is_initial_step_single_frame); } + builder_.add_kind(kind); + return builder_.Finish(); +} + +struct Animation FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef AnimationBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_BASE = 4, + VT_KIND = 6, + VT_TARGET = 8, + VT_REFERENCE_PARAMETERS = 10, + VT_EXPRESSION = 12, + VT_DURATION_TICKS = 14, + VT_KEY_FRAMES = 16, + VT_INTERPOLATION_COLOR_SPACE = 18 + }; + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CompObj *base() const { + return GetPointer(VT_BASE); + } + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::AnimationKind kind() const { + return static_cast(GetField(VT_KIND, 0)); + } + uint32_t target() const { + return GetField(VT_TARGET, 4294967295); + } + const ::flatbuffers::Vector<::flatbuffers::Offset> *reference_parameters() const { + return GetPointer> *>(VT_REFERENCE_PARAMETERS); + } + uint32_t expression() const { + return GetField(VT_EXPRESSION, 4294967295); + } + int64_t duration_ticks() const { + return GetField(VT_DURATION_TICKS, 0); + } + const ::flatbuffers::Vector<::flatbuffers::Offset> *key_frames() const { + return GetPointer> *>(VT_KEY_FRAMES); + } + ::flatbuffers::Optional interpolation_color_space() const { + return GetOptional(VT_INTERPOLATION_COLOR_SPACE); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyOffset(verifier, VT_BASE) && + verifier.VerifyTable(base()) && + VerifyField(verifier, VT_KIND, 1) && + VerifyField(verifier, VT_TARGET, 4) && + VerifyOffset(verifier, VT_REFERENCE_PARAMETERS) && + verifier.VerifyVector(reference_parameters()) && + verifier.VerifyVectorOfTables(reference_parameters()) && + VerifyField(verifier, VT_EXPRESSION, 4) && + VerifyField(verifier, VT_DURATION_TICKS, 8) && + VerifyOffset(verifier, VT_KEY_FRAMES) && + verifier.VerifyVector(key_frames()) && + verifier.VerifyVectorOfTables(key_frames()) && + VerifyField(verifier, VT_INTERPOLATION_COLOR_SPACE, 1) && + verifier.EndTable(); + } +}; + +struct AnimationBuilder { + typedef Animation Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_base(::flatbuffers::Offset base) { + fbb_.AddOffset(Animation::VT_BASE, base); + } + void add_kind(CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::AnimationKind kind) { + fbb_.AddElement(Animation::VT_KIND, static_cast(kind), 0); + } + void add_target(uint32_t target) { + fbb_.AddElement(Animation::VT_TARGET, target, 4294967295); + } + void add_reference_parameters(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> reference_parameters) { + fbb_.AddOffset(Animation::VT_REFERENCE_PARAMETERS, reference_parameters); + } + void add_expression(uint32_t expression) { + fbb_.AddElement(Animation::VT_EXPRESSION, expression, 4294967295); + } + void add_duration_ticks(int64_t duration_ticks) { + fbb_.AddElement(Animation::VT_DURATION_TICKS, duration_ticks, 0); + } + void add_key_frames(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> key_frames) { + fbb_.AddOffset(Animation::VT_KEY_FRAMES, key_frames); + } + void add_interpolation_color_space(uint8_t interpolation_color_space) { + fbb_.AddElement(Animation::VT_INTERPOLATION_COLOR_SPACE, interpolation_color_space); + } + explicit AnimationBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateAnimation( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset base = 0, + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::AnimationKind kind = CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::AnimationKind::Scalar, + uint32_t target = 4294967295, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> reference_parameters = 0, + uint32_t expression = 4294967295, + int64_t duration_ticks = 0, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> key_frames = 0, + ::flatbuffers::Optional interpolation_color_space = ::flatbuffers::nullopt) { + AnimationBuilder builder_(_fbb); + builder_.add_duration_ticks(duration_ticks); + builder_.add_key_frames(key_frames); + builder_.add_expression(expression); + builder_.add_reference_parameters(reference_parameters); + builder_.add_target(target); + builder_.add_base(base); + if(interpolation_color_space) { builder_.add_interpolation_color_space(*interpolation_color_space); } + builder_.add_kind(kind); + return builder_.Finish(); +} + +inline ::flatbuffers::Offset CreateAnimationDirect( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset base = 0, + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::AnimationKind kind = CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::AnimationKind::Scalar, + uint32_t target = 4294967295, + const std::vector<::flatbuffers::Offset> *reference_parameters = nullptr, + uint32_t expression = 4294967295, + int64_t duration_ticks = 0, + const std::vector<::flatbuffers::Offset> *key_frames = nullptr, + ::flatbuffers::Optional interpolation_color_space = ::flatbuffers::nullopt) { + auto reference_parameters__ = reference_parameters ? _fbb.CreateVector<::flatbuffers::Offset>(*reference_parameters) : 0; + auto key_frames__ = key_frames ? _fbb.CreateVector<::flatbuffers::Offset>(*key_frames) : 0; + return CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CreateAnimation( + _fbb, + base, + kind, + target, + reference_parameters__, + expression, + duration_ticks, + key_frames__, + interpolation_color_space); +} + +struct PropertySet FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef PropertySetBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_BASE = 4, + VT_OWNER = 6, + VT_VALUES = 8 + }; + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CompObj *base() const { + return GetPointer(VT_BASE); + } + uint32_t owner() const { + return GetField(VT_OWNER, 4294967295); + } + const ::flatbuffers::Vector<::flatbuffers::Offset> *values() const { + return GetPointer> *>(VT_VALUES); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyOffset(verifier, VT_BASE) && + verifier.VerifyTable(base()) && + VerifyField(verifier, VT_OWNER, 4) && + VerifyOffset(verifier, VT_VALUES) && + verifier.VerifyVector(values()) && + verifier.VerifyVectorOfTables(values()) && + verifier.EndTable(); + } +}; + +struct PropertySetBuilder { + typedef PropertySet Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_base(::flatbuffers::Offset base) { + fbb_.AddOffset(PropertySet::VT_BASE, base); + } + void add_owner(uint32_t owner) { + fbb_.AddElement(PropertySet::VT_OWNER, owner, 4294967295); + } + void add_values(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> values) { + fbb_.AddOffset(PropertySet::VT_VALUES, values); + } + explicit PropertySetBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreatePropertySet( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset base = 0, + uint32_t owner = 4294967295, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> values = 0) { + PropertySetBuilder builder_(_fbb); + builder_.add_values(values); + builder_.add_owner(owner); + builder_.add_base(base); + return builder_.Finish(); +} + +inline ::flatbuffers::Offset CreatePropertySetDirect( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset base = 0, + uint32_t owner = 4294967295, + const std::vector<::flatbuffers::Offset> *values = nullptr) { + auto values__ = values ? _fbb.CreateVector<::flatbuffers::Offset>(*values) : 0; + return CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CreatePropertySet( + _fbb, + base, + owner, + values__); +} + +struct Controller FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef ControllerBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_BASE = 4, + VT_TARGET_OBJECT = 6, + VT_TARGET_PROPERTY = 8, + VT_IS_PAUSED = 10, + VT_IS_CUSTOM = 12 + }; + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CompObj *base() const { + return GetPointer(VT_BASE); + } + uint32_t target_object() const { + return GetField(VT_TARGET_OBJECT, 4294967295); + } + uint32_t target_property() const { + return GetField(VT_TARGET_PROPERTY, 4294967295); + } + bool is_paused() const { + return GetField(VT_IS_PAUSED, 0) != 0; + } + bool is_custom() const { + return GetField(VT_IS_CUSTOM, 0) != 0; + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyOffset(verifier, VT_BASE) && + verifier.VerifyTable(base()) && + VerifyField(verifier, VT_TARGET_OBJECT, 4) && + VerifyField(verifier, VT_TARGET_PROPERTY, 4) && + VerifyField(verifier, VT_IS_PAUSED, 1) && + VerifyField(verifier, VT_IS_CUSTOM, 1) && + verifier.EndTable(); + } +}; + +struct ControllerBuilder { + typedef Controller Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_base(::flatbuffers::Offset base) { + fbb_.AddOffset(Controller::VT_BASE, base); + } + void add_target_object(uint32_t target_object) { + fbb_.AddElement(Controller::VT_TARGET_OBJECT, target_object, 4294967295); + } + void add_target_property(uint32_t target_property) { + fbb_.AddElement(Controller::VT_TARGET_PROPERTY, target_property, 4294967295); + } + void add_is_paused(bool is_paused) { + fbb_.AddElement(Controller::VT_IS_PAUSED, static_cast(is_paused), 0); + } + void add_is_custom(bool is_custom) { + fbb_.AddElement(Controller::VT_IS_CUSTOM, static_cast(is_custom), 0); + } + explicit ControllerBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateController( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset base = 0, + uint32_t target_object = 4294967295, + uint32_t target_property = 4294967295, + bool is_paused = false, + bool is_custom = false) { + ControllerBuilder builder_(_fbb); + builder_.add_target_property(target_property); + builder_.add_target_object(target_object); + builder_.add_base(base); + builder_.add_is_custom(is_custom); + builder_.add_is_paused(is_paused); + return builder_.Finish(); +} + +struct PropertyBinding FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef PropertyBindingBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_NAME = 4, + VT_DISPLAY_NAME = 6, + VT_ACTUAL_TYPE = 8, + VT_EXPOSED_TYPE = 10, + VT_DEFAULT_SCALAR = 12, + VT_DEFAULT_VECTOR = 14, + VT_DEFAULT_COLOR = 16 + }; + uint32_t name() const { + return GetField(VT_NAME, 0); + } + uint32_t display_name() const { + return GetField(VT_DISPLAY_NAME, 0); + } + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::PropertyValueType actual_type() const { + return static_cast(GetField(VT_ACTUAL_TYPE, 0)); + } + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::PropertyValueType exposed_type() const { + return static_cast(GetField(VT_EXPOSED_TYPE, 0)); + } + float default_scalar() const { + return GetField(VT_DEFAULT_SCALAR, 0.0f); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec4 *default_vector() const { + return GetStruct(VT_DEFAULT_VECTOR); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Color *default_color() const { + return GetStruct(VT_DEFAULT_COLOR); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyField(verifier, VT_NAME, 4) && + VerifyField(verifier, VT_DISPLAY_NAME, 4) && + VerifyField(verifier, VT_ACTUAL_TYPE, 1) && + VerifyField(verifier, VT_EXPOSED_TYPE, 1) && + VerifyField(verifier, VT_DEFAULT_SCALAR, 4) && + VerifyField(verifier, VT_DEFAULT_VECTOR, 4) && + VerifyField(verifier, VT_DEFAULT_COLOR, 1) && + verifier.EndTable(); + } +}; + +struct PropertyBindingBuilder { + typedef PropertyBinding Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_name(uint32_t name) { + fbb_.AddElement(PropertyBinding::VT_NAME, name, 0); + } + void add_display_name(uint32_t display_name) { + fbb_.AddElement(PropertyBinding::VT_DISPLAY_NAME, display_name, 0); + } + void add_actual_type(CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::PropertyValueType actual_type) { + fbb_.AddElement(PropertyBinding::VT_ACTUAL_TYPE, static_cast(actual_type), 0); + } + void add_exposed_type(CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::PropertyValueType exposed_type) { + fbb_.AddElement(PropertyBinding::VT_EXPOSED_TYPE, static_cast(exposed_type), 0); + } + void add_default_scalar(float default_scalar) { + fbb_.AddElement(PropertyBinding::VT_DEFAULT_SCALAR, default_scalar, 0.0f); + } + void add_default_vector(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec4 *default_vector) { + fbb_.AddStruct(PropertyBinding::VT_DEFAULT_VECTOR, default_vector); + } + void add_default_color(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Color *default_color) { + fbb_.AddStruct(PropertyBinding::VT_DEFAULT_COLOR, default_color); + } + explicit PropertyBindingBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreatePropertyBinding( + ::flatbuffers::FlatBufferBuilder &_fbb, + uint32_t name = 0, + uint32_t display_name = 0, + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::PropertyValueType actual_type = CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::PropertyValueType::None, + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::PropertyValueType exposed_type = CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::PropertyValueType::None, + float default_scalar = 0.0f, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec4 *default_vector = nullptr, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Color *default_color = nullptr) { + PropertyBindingBuilder builder_(_fbb); + builder_.add_default_color(default_color); + builder_.add_default_vector(default_vector); + builder_.add_default_scalar(default_scalar); + builder_.add_display_name(display_name); + builder_.add_name(name); + builder_.add_exposed_type(exposed_type); + builder_.add_actual_type(actual_type); + return builder_.Finish(); +} + +struct Marker FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef MarkerBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_NAME = 4, + VT_PROGRESS = 6, + VT_DURATION_PROGRESS = 8 + }; + uint32_t name() const { + return GetField(VT_NAME, 0); + } + float progress() const { + return GetField(VT_PROGRESS, 0.0f); + } + float duration_progress() const { + return GetField(VT_DURATION_PROGRESS, 0.0f); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyField(verifier, VT_NAME, 4) && + VerifyField(verifier, VT_PROGRESS, 4) && + VerifyField(verifier, VT_DURATION_PROGRESS, 4) && + verifier.EndTable(); + } +}; + +struct MarkerBuilder { + typedef Marker Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_name(uint32_t name) { + fbb_.AddElement(Marker::VT_NAME, name, 0); + } + void add_progress(float progress) { + fbb_.AddElement(Marker::VT_PROGRESS, progress, 0.0f); + } + void add_duration_progress(float duration_progress) { + fbb_.AddElement(Marker::VT_DURATION_PROGRESS, duration_progress, 0.0f); + } + explicit MarkerBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateMarker( + ::flatbuffers::FlatBufferBuilder &_fbb, + uint32_t name = 0, + float progress = 0.0f, + float duration_progress = 0.0f) { + MarkerBuilder builder_(_fbb); + builder_.add_duration_progress(duration_progress); + builder_.add_progress(progress); + builder_.add_name(name); + return builder_.Finish(); +} + +struct Metadata FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef MetadataBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_NAME = 4, + VT_WIDTH = 6, + VT_HEIGHT = 8, + VT_DURATION_TICKS = 10, + VT_FRAMES_PER_SECOND = 12, + VT_PROPERTY_BINDINGS = 14, + VT_MARKERS = 16, + VT_THEMING_PROPERTY_SET = 18 + }; + uint32_t name() const { + return GetField(VT_NAME, 4294967295); + } + float width() const { + return GetField(VT_WIDTH, 0.0f); + } + float height() const { + return GetField(VT_HEIGHT, 0.0f); + } + int64_t duration_ticks() const { + return GetField(VT_DURATION_TICKS, 0); + } + float frames_per_second() const { + return GetField(VT_FRAMES_PER_SECOND, 0.0f); + } + const ::flatbuffers::Vector<::flatbuffers::Offset> *property_bindings() const { + return GetPointer> *>(VT_PROPERTY_BINDINGS); + } + const ::flatbuffers::Vector<::flatbuffers::Offset> *markers() const { + return GetPointer> *>(VT_MARKERS); + } + uint32_t theming_property_set() const { + return GetField(VT_THEMING_PROPERTY_SET, 4294967295); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyField(verifier, VT_NAME, 4) && + VerifyField(verifier, VT_WIDTH, 4) && + VerifyField(verifier, VT_HEIGHT, 4) && + VerifyField(verifier, VT_DURATION_TICKS, 8) && + VerifyField(verifier, VT_FRAMES_PER_SECOND, 4) && + VerifyOffset(verifier, VT_PROPERTY_BINDINGS) && + verifier.VerifyVector(property_bindings()) && + verifier.VerifyVectorOfTables(property_bindings()) && + VerifyOffset(verifier, VT_MARKERS) && + verifier.VerifyVector(markers()) && + verifier.VerifyVectorOfTables(markers()) && + VerifyField(verifier, VT_THEMING_PROPERTY_SET, 4) && + verifier.EndTable(); + } +}; + +struct MetadataBuilder { + typedef Metadata Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_name(uint32_t name) { + fbb_.AddElement(Metadata::VT_NAME, name, 4294967295); + } + void add_width(float width) { + fbb_.AddElement(Metadata::VT_WIDTH, width, 0.0f); + } + void add_height(float height) { + fbb_.AddElement(Metadata::VT_HEIGHT, height, 0.0f); + } + void add_duration_ticks(int64_t duration_ticks) { + fbb_.AddElement(Metadata::VT_DURATION_TICKS, duration_ticks, 0); + } + void add_frames_per_second(float frames_per_second) { + fbb_.AddElement(Metadata::VT_FRAMES_PER_SECOND, frames_per_second, 0.0f); + } + void add_property_bindings(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> property_bindings) { + fbb_.AddOffset(Metadata::VT_PROPERTY_BINDINGS, property_bindings); + } + void add_markers(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> markers) { + fbb_.AddOffset(Metadata::VT_MARKERS, markers); + } + void add_theming_property_set(uint32_t theming_property_set) { + fbb_.AddElement(Metadata::VT_THEMING_PROPERTY_SET, theming_property_set, 4294967295); + } + explicit MetadataBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateMetadata( + ::flatbuffers::FlatBufferBuilder &_fbb, + uint32_t name = 4294967295, + float width = 0.0f, + float height = 0.0f, + int64_t duration_ticks = 0, + float frames_per_second = 0.0f, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> property_bindings = 0, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> markers = 0, + uint32_t theming_property_set = 4294967295) { + MetadataBuilder builder_(_fbb); + builder_.add_duration_ticks(duration_ticks); + builder_.add_theming_property_set(theming_property_set); + builder_.add_markers(markers); + builder_.add_property_bindings(property_bindings); + builder_.add_frames_per_second(frames_per_second); + builder_.add_height(height); + builder_.add_width(width); + builder_.add_name(name); + return builder_.Finish(); +} + +inline ::flatbuffers::Offset CreateMetadataDirect( + ::flatbuffers::FlatBufferBuilder &_fbb, + uint32_t name = 4294967295, + float width = 0.0f, + float height = 0.0f, + int64_t duration_ticks = 0, + float frames_per_second = 0.0f, + const std::vector<::flatbuffers::Offset> *property_bindings = nullptr, + const std::vector<::flatbuffers::Offset> *markers = nullptr, + uint32_t theming_property_set = 4294967295) { + auto property_bindings__ = property_bindings ? _fbb.CreateVector<::flatbuffers::Offset>(*property_bindings) : 0; + auto markers__ = markers ? _fbb.CreateVector<::flatbuffers::Offset>(*markers) : 0; + return CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CreateMetadata( + _fbb, + name, + width, + height, + duration_ticks, + frames_per_second, + property_bindings__, + markers__, + theming_property_set); +} + +struct LottieComposition FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef LottieCompositionBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_SCHEMA_VERSION = 4, + VT_REQUIRED_UAP_VERSION = 6, + VT_METADATA = 8, + VT_ROOT_VISUAL = 10, + VT_STRINGS = 12, + VT_VISUALS = 14, + VT_SHAPES = 16, + VT_GEOMETRIES = 18, + VT_CANVAS_GEOMETRIES = 20, + VT_BRUSHES = 22, + VT_GRADIENT_STOPS = 24, + VT_VIEW_BOXES = 26, + VT_CLIPS = 28, + VT_SHADOWS = 30, + VT_SURFACES = 32, + VT_EFFECTS = 34, + VT_EASINGS = 36, + VT_ANIMATIONS = 38, + VT_PROPERTY_SETS = 40, + VT_CONTROLLERS = 42, + VT_CUSTOM_CONTROLLERS = 44 + }; + uint16_t schema_version() const { + return GetField(VT_SCHEMA_VERSION, 0); + } + uint16_t required_uap_version() const { + return GetField(VT_REQUIRED_UAP_VERSION, 0); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Metadata *metadata() const { + return GetPointer(VT_METADATA); + } + uint32_t root_visual() const { + return GetField(VT_ROOT_VISUAL, 4294967295); + } + const ::flatbuffers::Vector<::flatbuffers::Offset<::flatbuffers::String>> *strings() const { + return GetPointer> *>(VT_STRINGS); + } + const ::flatbuffers::Vector<::flatbuffers::Offset> *visuals() const { + return GetPointer> *>(VT_VISUALS); + } + const ::flatbuffers::Vector<::flatbuffers::Offset> *shapes() const { + return GetPointer> *>(VT_SHAPES); + } + const ::flatbuffers::Vector<::flatbuffers::Offset> *geometries() const { + return GetPointer> *>(VT_GEOMETRIES); + } + const ::flatbuffers::Vector<::flatbuffers::Offset> *canvas_geometries() const { + return GetPointer> *>(VT_CANVAS_GEOMETRIES); + } + const ::flatbuffers::Vector<::flatbuffers::Offset> *brushes() const { + return GetPointer> *>(VT_BRUSHES); + } + const ::flatbuffers::Vector<::flatbuffers::Offset> *gradient_stops() const { + return GetPointer> *>(VT_GRADIENT_STOPS); + } + const ::flatbuffers::Vector<::flatbuffers::Offset> *view_boxes() const { + return GetPointer> *>(VT_VIEW_BOXES); + } + const ::flatbuffers::Vector<::flatbuffers::Offset> *clips() const { + return GetPointer> *>(VT_CLIPS); + } + const ::flatbuffers::Vector<::flatbuffers::Offset> *shadows() const { + return GetPointer> *>(VT_SHADOWS); + } + const ::flatbuffers::Vector<::flatbuffers::Offset> *surfaces() const { + return GetPointer> *>(VT_SURFACES); + } + const ::flatbuffers::Vector<::flatbuffers::Offset> *effects() const { + return GetPointer> *>(VT_EFFECTS); + } + const ::flatbuffers::Vector<::flatbuffers::Offset> *easings() const { + return GetPointer> *>(VT_EASINGS); + } + const ::flatbuffers::Vector<::flatbuffers::Offset> *animations() const { + return GetPointer> *>(VT_ANIMATIONS); + } + const ::flatbuffers::Vector<::flatbuffers::Offset> *property_sets() const { + return GetPointer> *>(VT_PROPERTY_SETS); + } + const ::flatbuffers::Vector<::flatbuffers::Offset> *controllers() const { + return GetPointer> *>(VT_CONTROLLERS); + } + const ::flatbuffers::Vector *custom_controllers() const { + return GetPointer *>(VT_CUSTOM_CONTROLLERS); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyField(verifier, VT_SCHEMA_VERSION, 2) && + VerifyField(verifier, VT_REQUIRED_UAP_VERSION, 2) && + VerifyOffset(verifier, VT_METADATA) && + verifier.VerifyTable(metadata()) && + VerifyField(verifier, VT_ROOT_VISUAL, 4) && + VerifyOffset(verifier, VT_STRINGS) && + verifier.VerifyVector(strings()) && + verifier.VerifyVectorOfStrings(strings()) && + VerifyOffset(verifier, VT_VISUALS) && + verifier.VerifyVector(visuals()) && + verifier.VerifyVectorOfTables(visuals()) && + VerifyOffset(verifier, VT_SHAPES) && + verifier.VerifyVector(shapes()) && + verifier.VerifyVectorOfTables(shapes()) && + VerifyOffset(verifier, VT_GEOMETRIES) && + verifier.VerifyVector(geometries()) && + verifier.VerifyVectorOfTables(geometries()) && + VerifyOffset(verifier, VT_CANVAS_GEOMETRIES) && + verifier.VerifyVector(canvas_geometries()) && + verifier.VerifyVectorOfTables(canvas_geometries()) && + VerifyOffset(verifier, VT_BRUSHES) && + verifier.VerifyVector(brushes()) && + verifier.VerifyVectorOfTables(brushes()) && + VerifyOffset(verifier, VT_GRADIENT_STOPS) && + verifier.VerifyVector(gradient_stops()) && + verifier.VerifyVectorOfTables(gradient_stops()) && + VerifyOffset(verifier, VT_VIEW_BOXES) && + verifier.VerifyVector(view_boxes()) && + verifier.VerifyVectorOfTables(view_boxes()) && + VerifyOffset(verifier, VT_CLIPS) && + verifier.VerifyVector(clips()) && + verifier.VerifyVectorOfTables(clips()) && + VerifyOffset(verifier, VT_SHADOWS) && + verifier.VerifyVector(shadows()) && + verifier.VerifyVectorOfTables(shadows()) && + VerifyOffset(verifier, VT_SURFACES) && + verifier.VerifyVector(surfaces()) && + verifier.VerifyVectorOfTables(surfaces()) && + VerifyOffset(verifier, VT_EFFECTS) && + verifier.VerifyVector(effects()) && + verifier.VerifyVectorOfTables(effects()) && + VerifyOffset(verifier, VT_EASINGS) && + verifier.VerifyVector(easings()) && + verifier.VerifyVectorOfTables(easings()) && + VerifyOffset(verifier, VT_ANIMATIONS) && + verifier.VerifyVector(animations()) && + verifier.VerifyVectorOfTables(animations()) && + VerifyOffset(verifier, VT_PROPERTY_SETS) && + verifier.VerifyVector(property_sets()) && + verifier.VerifyVectorOfTables(property_sets()) && + VerifyOffset(verifier, VT_CONTROLLERS) && + verifier.VerifyVector(controllers()) && + verifier.VerifyVectorOfTables(controllers()) && + VerifyOffset(verifier, VT_CUSTOM_CONTROLLERS) && + verifier.VerifyVector(custom_controllers()) && + verifier.EndTable(); + } +}; + +struct LottieCompositionBuilder { + typedef LottieComposition Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_schema_version(uint16_t schema_version) { + fbb_.AddElement(LottieComposition::VT_SCHEMA_VERSION, schema_version, 0); + } + void add_required_uap_version(uint16_t required_uap_version) { + fbb_.AddElement(LottieComposition::VT_REQUIRED_UAP_VERSION, required_uap_version, 0); + } + void add_metadata(::flatbuffers::Offset metadata) { + fbb_.AddOffset(LottieComposition::VT_METADATA, metadata); + } + void add_root_visual(uint32_t root_visual) { + fbb_.AddElement(LottieComposition::VT_ROOT_VISUAL, root_visual, 4294967295); + } + void add_strings(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<::flatbuffers::String>>> strings) { + fbb_.AddOffset(LottieComposition::VT_STRINGS, strings); + } + void add_visuals(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> visuals) { + fbb_.AddOffset(LottieComposition::VT_VISUALS, visuals); + } + void add_shapes(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> shapes) { + fbb_.AddOffset(LottieComposition::VT_SHAPES, shapes); + } + void add_geometries(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> geometries) { + fbb_.AddOffset(LottieComposition::VT_GEOMETRIES, geometries); + } + void add_canvas_geometries(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> canvas_geometries) { + fbb_.AddOffset(LottieComposition::VT_CANVAS_GEOMETRIES, canvas_geometries); + } + void add_brushes(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> brushes) { + fbb_.AddOffset(LottieComposition::VT_BRUSHES, brushes); + } + void add_gradient_stops(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> gradient_stops) { + fbb_.AddOffset(LottieComposition::VT_GRADIENT_STOPS, gradient_stops); + } + void add_view_boxes(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> view_boxes) { + fbb_.AddOffset(LottieComposition::VT_VIEW_BOXES, view_boxes); + } + void add_clips(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> clips) { + fbb_.AddOffset(LottieComposition::VT_CLIPS, clips); + } + void add_shadows(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> shadows) { + fbb_.AddOffset(LottieComposition::VT_SHADOWS, shadows); + } + void add_surfaces(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> surfaces) { + fbb_.AddOffset(LottieComposition::VT_SURFACES, surfaces); + } + void add_effects(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> effects) { + fbb_.AddOffset(LottieComposition::VT_EFFECTS, effects); + } + void add_easings(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> easings) { + fbb_.AddOffset(LottieComposition::VT_EASINGS, easings); + } + void add_animations(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> animations) { + fbb_.AddOffset(LottieComposition::VT_ANIMATIONS, animations); + } + void add_property_sets(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> property_sets) { + fbb_.AddOffset(LottieComposition::VT_PROPERTY_SETS, property_sets); + } + void add_controllers(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> controllers) { + fbb_.AddOffset(LottieComposition::VT_CONTROLLERS, controllers); + } + void add_custom_controllers(::flatbuffers::Offset<::flatbuffers::Vector> custom_controllers) { + fbb_.AddOffset(LottieComposition::VT_CUSTOM_CONTROLLERS, custom_controllers); + } + explicit LottieCompositionBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateLottieComposition( + ::flatbuffers::FlatBufferBuilder &_fbb, + uint16_t schema_version = 0, + uint16_t required_uap_version = 0, + ::flatbuffers::Offset metadata = 0, + uint32_t root_visual = 4294967295, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<::flatbuffers::String>>> strings = 0, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> visuals = 0, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> shapes = 0, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> geometries = 0, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> canvas_geometries = 0, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> brushes = 0, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> gradient_stops = 0, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> view_boxes = 0, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> clips = 0, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> shadows = 0, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> surfaces = 0, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> effects = 0, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> easings = 0, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> animations = 0, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> property_sets = 0, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> controllers = 0, + ::flatbuffers::Offset<::flatbuffers::Vector> custom_controllers = 0) { + LottieCompositionBuilder builder_(_fbb); + builder_.add_custom_controllers(custom_controllers); + builder_.add_controllers(controllers); + builder_.add_property_sets(property_sets); + builder_.add_animations(animations); + builder_.add_easings(easings); + builder_.add_effects(effects); + builder_.add_surfaces(surfaces); + builder_.add_shadows(shadows); + builder_.add_clips(clips); + builder_.add_view_boxes(view_boxes); + builder_.add_gradient_stops(gradient_stops); + builder_.add_brushes(brushes); + builder_.add_canvas_geometries(canvas_geometries); + builder_.add_geometries(geometries); + builder_.add_shapes(shapes); + builder_.add_visuals(visuals); + builder_.add_strings(strings); + builder_.add_root_visual(root_visual); + builder_.add_metadata(metadata); + builder_.add_required_uap_version(required_uap_version); + builder_.add_schema_version(schema_version); + return builder_.Finish(); +} + +inline ::flatbuffers::Offset CreateLottieCompositionDirect( + ::flatbuffers::FlatBufferBuilder &_fbb, + uint16_t schema_version = 0, + uint16_t required_uap_version = 0, + ::flatbuffers::Offset metadata = 0, + uint32_t root_visual = 4294967295, + const std::vector<::flatbuffers::Offset<::flatbuffers::String>> *strings = nullptr, + const std::vector<::flatbuffers::Offset> *visuals = nullptr, + const std::vector<::flatbuffers::Offset> *shapes = nullptr, + const std::vector<::flatbuffers::Offset> *geometries = nullptr, + const std::vector<::flatbuffers::Offset> *canvas_geometries = nullptr, + const std::vector<::flatbuffers::Offset> *brushes = nullptr, + const std::vector<::flatbuffers::Offset> *gradient_stops = nullptr, + const std::vector<::flatbuffers::Offset> *view_boxes = nullptr, + const std::vector<::flatbuffers::Offset> *clips = nullptr, + const std::vector<::flatbuffers::Offset> *shadows = nullptr, + const std::vector<::flatbuffers::Offset> *surfaces = nullptr, + const std::vector<::flatbuffers::Offset> *effects = nullptr, + const std::vector<::flatbuffers::Offset> *easings = nullptr, + const std::vector<::flatbuffers::Offset> *animations = nullptr, + const std::vector<::flatbuffers::Offset> *property_sets = nullptr, + const std::vector<::flatbuffers::Offset> *controllers = nullptr, + const std::vector *custom_controllers = nullptr) { + auto strings__ = strings ? _fbb.CreateVector<::flatbuffers::Offset<::flatbuffers::String>>(*strings) : 0; + auto visuals__ = visuals ? _fbb.CreateVector<::flatbuffers::Offset>(*visuals) : 0; + auto shapes__ = shapes ? _fbb.CreateVector<::flatbuffers::Offset>(*shapes) : 0; + auto geometries__ = geometries ? _fbb.CreateVector<::flatbuffers::Offset>(*geometries) : 0; + auto canvas_geometries__ = canvas_geometries ? _fbb.CreateVector<::flatbuffers::Offset>(*canvas_geometries) : 0; + auto brushes__ = brushes ? _fbb.CreateVector<::flatbuffers::Offset>(*brushes) : 0; + auto gradient_stops__ = gradient_stops ? _fbb.CreateVector<::flatbuffers::Offset>(*gradient_stops) : 0; + auto view_boxes__ = view_boxes ? _fbb.CreateVector<::flatbuffers::Offset>(*view_boxes) : 0; + auto clips__ = clips ? _fbb.CreateVector<::flatbuffers::Offset>(*clips) : 0; + auto shadows__ = shadows ? _fbb.CreateVector<::flatbuffers::Offset>(*shadows) : 0; + auto surfaces__ = surfaces ? _fbb.CreateVector<::flatbuffers::Offset>(*surfaces) : 0; + auto effects__ = effects ? _fbb.CreateVector<::flatbuffers::Offset>(*effects) : 0; + auto easings__ = easings ? _fbb.CreateVector<::flatbuffers::Offset>(*easings) : 0; + auto animations__ = animations ? _fbb.CreateVector<::flatbuffers::Offset>(*animations) : 0; + auto property_sets__ = property_sets ? _fbb.CreateVector<::flatbuffers::Offset>(*property_sets) : 0; + auto controllers__ = controllers ? _fbb.CreateVector<::flatbuffers::Offset>(*controllers) : 0; + auto custom_controllers__ = custom_controllers ? _fbb.CreateVector(*custom_controllers) : 0; + return CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CreateLottieComposition( + _fbb, + schema_version, + required_uap_version, + metadata, + root_visual, + strings__, + visuals__, + shapes__, + geometries__, + canvas_geometries__, + brushes__, + gradient_stops__, + view_boxes__, + clips__, + shadows__, + surfaces__, + effects__, + easings__, + animations__, + property_sets__, + controllers__, + custom_controllers__); +} + +struct CanvasGeometry FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef CanvasGeometryBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_KIND = 4, + VT_A = 6, + VT_B = 8, + VT_COMBINE_MODE = 10, + VT_MATRIX = 12, + VT_SOURCE = 14, + VT_GEOMETRIES = 16, + VT_FILL_RULE = 18, + VT_OPS = 20, + VT_OPERANDS = 22, + VT_X = 24, + VT_Y = 26, + VT_W = 28, + VT_H = 30, + VT_RADIUS_X = 32, + VT_RADIUS_Y = 34 + }; + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CanvasGeometryKind kind() const { + return static_cast(GetField(VT_KIND, 0)); + } + uint32_t a() const { + return GetField(VT_A, 4294967295); + } + uint32_t b() const { + return GetField(VT_B, 4294967295); + } + uint8_t combine_mode() const { + return GetField(VT_COMBINE_MODE, 0); + } + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Mat3x2 *matrix() const { + return GetStruct(VT_MATRIX); + } + uint32_t source() const { + return GetField(VT_SOURCE, 4294967295); + } + const ::flatbuffers::Vector *geometries() const { + return GetPointer *>(VT_GEOMETRIES); + } + uint8_t fill_rule() const { + return GetField(VT_FILL_RULE, 0); + } + const ::flatbuffers::Vector *ops() const { + return GetPointer *>(VT_OPS); + } + const ::flatbuffers::Vector *operands() const { + return GetPointer *>(VT_OPERANDS); + } + float x() const { + return GetField(VT_X, 0.0f); + } + float y() const { + return GetField(VT_Y, 0.0f); + } + float w() const { + return GetField(VT_W, 0.0f); + } + float h() const { + return GetField(VT_H, 0.0f); + } + float radius_x() const { + return GetField(VT_RADIUS_X, 0.0f); + } + float radius_y() const { + return GetField(VT_RADIUS_Y, 0.0f); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyField(verifier, VT_KIND, 1) && + VerifyField(verifier, VT_A, 4) && + VerifyField(verifier, VT_B, 4) && + VerifyField(verifier, VT_COMBINE_MODE, 1) && + VerifyField(verifier, VT_MATRIX, 4) && + VerifyField(verifier, VT_SOURCE, 4) && + VerifyOffset(verifier, VT_GEOMETRIES) && + verifier.VerifyVector(geometries()) && + VerifyField(verifier, VT_FILL_RULE, 1) && + VerifyOffset(verifier, VT_OPS) && + verifier.VerifyVector(ops()) && + VerifyOffset(verifier, VT_OPERANDS) && + verifier.VerifyVector(operands()) && + VerifyField(verifier, VT_X, 4) && + VerifyField(verifier, VT_Y, 4) && + VerifyField(verifier, VT_W, 4) && + VerifyField(verifier, VT_H, 4) && + VerifyField(verifier, VT_RADIUS_X, 4) && + VerifyField(verifier, VT_RADIUS_Y, 4) && + verifier.EndTable(); + } +}; + +struct CanvasGeometryBuilder { + typedef CanvasGeometry Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_kind(CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CanvasGeometryKind kind) { + fbb_.AddElement(CanvasGeometry::VT_KIND, static_cast(kind), 0); + } + void add_a(uint32_t a) { + fbb_.AddElement(CanvasGeometry::VT_A, a, 4294967295); + } + void add_b(uint32_t b) { + fbb_.AddElement(CanvasGeometry::VT_B, b, 4294967295); + } + void add_combine_mode(uint8_t combine_mode) { + fbb_.AddElement(CanvasGeometry::VT_COMBINE_MODE, combine_mode, 0); + } + void add_matrix(const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Mat3x2 *matrix) { + fbb_.AddStruct(CanvasGeometry::VT_MATRIX, matrix); + } + void add_source(uint32_t source) { + fbb_.AddElement(CanvasGeometry::VT_SOURCE, source, 4294967295); + } + void add_geometries(::flatbuffers::Offset<::flatbuffers::Vector> geometries) { + fbb_.AddOffset(CanvasGeometry::VT_GEOMETRIES, geometries); + } + void add_fill_rule(uint8_t fill_rule) { + fbb_.AddElement(CanvasGeometry::VT_FILL_RULE, fill_rule, 0); + } + void add_ops(::flatbuffers::Offset<::flatbuffers::Vector> ops) { + fbb_.AddOffset(CanvasGeometry::VT_OPS, ops); + } + void add_operands(::flatbuffers::Offset<::flatbuffers::Vector> operands) { + fbb_.AddOffset(CanvasGeometry::VT_OPERANDS, operands); + } + void add_x(float x) { + fbb_.AddElement(CanvasGeometry::VT_X, x, 0.0f); + } + void add_y(float y) { + fbb_.AddElement(CanvasGeometry::VT_Y, y, 0.0f); + } + void add_w(float w) { + fbb_.AddElement(CanvasGeometry::VT_W, w, 0.0f); + } + void add_h(float h) { + fbb_.AddElement(CanvasGeometry::VT_H, h, 0.0f); + } + void add_radius_x(float radius_x) { + fbb_.AddElement(CanvasGeometry::VT_RADIUS_X, radius_x, 0.0f); + } + void add_radius_y(float radius_y) { + fbb_.AddElement(CanvasGeometry::VT_RADIUS_Y, radius_y, 0.0f); + } + explicit CanvasGeometryBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateCanvasGeometry( + ::flatbuffers::FlatBufferBuilder &_fbb, + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CanvasGeometryKind kind = CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CanvasGeometryKind::Combination, + uint32_t a = 4294967295, + uint32_t b = 4294967295, + uint8_t combine_mode = 0, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Mat3x2 *matrix = nullptr, + uint32_t source = 4294967295, + ::flatbuffers::Offset<::flatbuffers::Vector> geometries = 0, + uint8_t fill_rule = 0, + ::flatbuffers::Offset<::flatbuffers::Vector> ops = 0, + ::flatbuffers::Offset<::flatbuffers::Vector> operands = 0, + float x = 0.0f, + float y = 0.0f, + float w = 0.0f, + float h = 0.0f, + float radius_x = 0.0f, + float radius_y = 0.0f) { + CanvasGeometryBuilder builder_(_fbb); + builder_.add_radius_y(radius_y); + builder_.add_radius_x(radius_x); + builder_.add_h(h); + builder_.add_w(w); + builder_.add_y(y); + builder_.add_x(x); + builder_.add_operands(operands); + builder_.add_ops(ops); + builder_.add_geometries(geometries); + builder_.add_source(source); + builder_.add_matrix(matrix); + builder_.add_b(b); + builder_.add_a(a); + builder_.add_fill_rule(fill_rule); + builder_.add_combine_mode(combine_mode); + builder_.add_kind(kind); + return builder_.Finish(); +} + +inline ::flatbuffers::Offset CreateCanvasGeometryDirect( + ::flatbuffers::FlatBufferBuilder &_fbb, + CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CanvasGeometryKind kind = CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CanvasGeometryKind::Combination, + uint32_t a = 4294967295, + uint32_t b = 4294967295, + uint8_t combine_mode = 0, + const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Mat3x2 *matrix = nullptr, + uint32_t source = 4294967295, + const std::vector *geometries = nullptr, + uint8_t fill_rule = 0, + const std::vector *ops = nullptr, + const std::vector *operands = nullptr, + float x = 0.0f, + float y = 0.0f, + float w = 0.0f, + float h = 0.0f, + float radius_x = 0.0f, + float radius_y = 0.0f) { + auto geometries__ = geometries ? _fbb.CreateVector(*geometries) : 0; + auto ops__ = ops ? _fbb.CreateVector(*ops) : 0; + auto operands__ = operands ? _fbb.CreateVector(*operands) : 0; + return CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::CreateCanvasGeometry( + _fbb, + kind, + a, + b, + combine_mode, + matrix, + source, + geometries__, + fill_rule, + ops__, + operands__, + x, + y, + w, + h, + radius_x, + radius_y); +} + +inline const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::LottieComposition *GetLottieComposition(const void *buf) { + return ::flatbuffers::GetRoot(buf); +} + +inline const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::LottieComposition *GetSizePrefixedLottieComposition(const void *buf) { + return ::flatbuffers::GetSizePrefixedRoot(buf); +} + +inline const char *LottieCompositionIdentifier() { + return "LCMP"; +} + +inline bool LottieCompositionBufferHasIdentifier(const void *buf) { + return ::flatbuffers::BufferHasIdentifier( + buf, LottieCompositionIdentifier()); +} + +inline bool SizePrefixedLottieCompositionBufferHasIdentifier(const void *buf) { + return ::flatbuffers::BufferHasIdentifier( + buf, LottieCompositionIdentifier(), true); +} + +inline bool VerifyLottieCompositionBuffer( + ::flatbuffers::Verifier &verifier) { + return verifier.VerifyBuffer(LottieCompositionIdentifier()); +} + +inline bool VerifySizePrefixedLottieCompositionBuffer( + ::flatbuffers::Verifier &verifier) { + return verifier.VerifySizePrefixedBuffer(LottieCompositionIdentifier()); +} + +inline const char *LottieCompositionExtension() { + return "lcomp"; +} + +inline void FinishLottieCompositionBuffer( + ::flatbuffers::FlatBufferBuilder &fbb, + ::flatbuffers::Offset root) { + fbb.Finish(root, LottieCompositionIdentifier()); +} + +inline void FinishSizePrefixedLottieCompositionBuffer( + ::flatbuffers::FlatBufferBuilder &fbb, + ::flatbuffers::Offset root) { + fbb.FinishSizePrefixed(root, LottieCompositionIdentifier()); +} + +} // namespace Schema +} // namespace CompDataFlatbuffer +} // namespace Lottie +} // namespace WinUI +} // namespace CommunityToolkit + +#endif // FLATBUFFERS_GENERATED_LOTTIECOMP_COMMUNITYTOOLKIT_WINUI_LOTTIE_COMPDATAFLATBUFFER_SCHEMA_H_ diff --git a/dlls/LottieRuntime/LottieRuntime.cpp b/dlls/LottieRuntime/LottieRuntime.cpp new file mode 100644 index 00000000..b8368738 --- /dev/null +++ b/dlls/LottieRuntime/LottieRuntime.cpp @@ -0,0 +1,2014 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// A runtime interpreter for serialized Lottie compositions. +// +// This is the native counterpart of Instantiator.cs. It walks the FlatBuffer produced +// by CompositionSerializer and issues the same sequence of Compositor calls that the +// managed instantiator and the generated code issue, so the three produce the same +// visual tree. +// +// Three things shape the code: +// +// * Size first. There is one function per node category rather than one per node +// type, and the shared state of every node is applied by a single function that +// takes the least derived type. Everything lives in one translation unit so that +// the linker can discard what an application does not reach. +// +// * Runtime cost second. Every node is realized at most once and is then found by +// indexing an array, never by hashing. The buffer is read in place; nothing is +// copied out of it. +// +// * Transient memory third. The only allocations are the realization caches, which +// are exactly as large as the node vectors in the buffer, and the D2D geometries, +// which are released as soon as they have been handed to a CompositionPath. +// +// Calls are made through the least derived interface that has the member being used. +// A node is only ever known by its concrete type inside the function that creates it; +// everywhere else it is held as Visual, CompositionShape, CompositionBrush and so on. +// That is what keeps the interpreter free of casts: there is no point at which it has +// to ask what a node actually is, because the buffer already said, and the answer was +// consumed at the moment of creation. + +#include "LottieRuntime.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "Generated/lottie_comp_generated.h" + +namespace Fb = CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema; + +using namespace winrt::Windows::Foundation::Numerics; +using namespace winrt::Windows::UI::Composition; + +namespace +{ + // The index that the format uses for "no node". It is the same value in every + // field that can hold an index. + constexpr uint32_t NullIndex = 0xFFFFFFFFu; + + // The schema version this build understands. A buffer that declares a higher + // version is rejected, because it may use fields that are not read here. + constexpr uint16_t SupportedSchemaVersion = 1; + + // Object references that can point at more than one category pack the category + // into the top four bits. + constexpr uint32_t CategoryShift = 28; + constexpr uint32_t IndexMask = (1u << CategoryShift) - 1u; + + [[noreturn]] void ThrowMalformed() + { + throw winrt::hresult_invalid_argument(L"The buffer is not a well formed Lottie composition."); + } + + [[noreturn]] void ThrowUnsupported() + { + throw winrt::hresult_not_implemented(L"The composition uses a feature this build does not support."); + } + + void Check(bool condition) + { + if (!condition) + { + ThrowMalformed(); + } + } + + // Reads an entry from one of the buffer's node vectors. A vector that is absent + // is treated as empty, so an index into it is out of range rather than a crash. + template + T const* At(::flatbuffers::Vector<::flatbuffers::Offset> const* vector, uint32_t index) + { + Check(vector != nullptr && index < vector->size()); + auto const* result = vector->Get(index); + Check(result != nullptr); + return result; + } + + float3 ToFloat3(Fb::Vec3 const* value) + { + return { value->x(), value->y(), value->z() }; + } + + float2 ToFloat2(Fb::Vec2 const* value) + { + return { value->x(), value->y() }; + } + + float3x2 ToFloat3x2(Fb::Mat3x2 const* value) + { + return { + value->m11(), value->m12(), + value->m21(), value->m22(), + value->m31(), value->m32(), + }; + } + + float4x4 ToFloat4x4(Fb::Mat4x4 const* value) + { + return { + value->m11(), value->m12(), value->m13(), value->m14(), + value->m21(), value->m22(), value->m23(), value->m24(), + value->m31(), value->m32(), value->m33(), value->m34(), + value->m41(), value->m42(), value->m43(), value->m44(), + }; + } + + winrt::Windows::UI::Color ToColor(Fb::Color const* value) + { + if (value == nullptr) + { + return { 0, 0, 0, 0 }; + } + + return { value->a(), value->r(), value->g(), value->b() }; + } + + // CanvasGeometryCombine and D2D1_COMBINE_MODE do not agree on the order of their + // members, so the value from the buffer cannot simply be cast. + D2D1_COMBINE_MODE ToCombineMode(uint8_t value) + { + switch (value) + { + case 0: return D2D1_COMBINE_MODE_UNION; + case 1: return D2D1_COMBINE_MODE_EXCLUDE; + case 2: return D2D1_COMBINE_MODE_INTERSECT; + case 3: return D2D1_COMBINE_MODE_XOR; + default: ThrowMalformed(); + } + } + + // --------------------------------------------------------------------------- + // Geometry. + // + // A CompositionPath is built from anything that implements IGeometrySource2D, + // and the only way to give one real content is the interop interface that hands + // out a D2D geometry. This is the smallest object that does that. + // --------------------------------------------------------------------------- + struct GeometrySource : winrt::implements< + GeometrySource, + winrt::Windows::Graphics::IGeometrySource2D, + ABI::Windows::Graphics::IGeometrySource2DInterop> + { + explicit GeometrySource(winrt::com_ptr geometry) : + m_geometry(std::move(geometry)) + { + } + + IFACEMETHODIMP GetGeometry(ID2D1Geometry** value) noexcept + { + m_geometry.copy_to(value); + return S_OK; + } + + IFACEMETHODIMP TryGetGeometryUsingFactory(ID2D1Factory*, ID2D1Geometry** value) noexcept + { + // The geometry is device independent and was made by this component's own + // factory, so there is nothing useful to return for another factory. + *value = nullptr; + return E_NOTIMPL; + } + + private: + winrt::com_ptr m_geometry; + }; + + ID2D1Factory* D2DFactory() + { + // One factory for the lifetime of the process. The geometries it makes are + // device independent, so nothing here depends on a device and the factory + // never has to be recreated. + static ID2D1Factory* const factory = [] + { + ID2D1Factory* result = nullptr; + winrt::check_hresult(D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, &result)); + return result; + }(); + + return factory; + } + + // --------------------------------------------------------------------------- + // Effects. + // + // Windows.UI.Composition accepts any IGraphicsEffect that also implements the + // D2D interop interface, which is how it learns which D2D effect to instantiate + // and what to set on it. Implementing that directly avoids taking a dependency + // on Win2D for the two effects the translator emits. + // --------------------------------------------------------------------------- + struct Effect : winrt::implements< + Effect, + winrt::Windows::Graphics::Effects::IGraphicsEffect, + winrt::Windows::Graphics::Effects::IGraphicsEffectSource, + ABI::Windows::Graphics::Effects::IGraphicsEffectD2D1Interop> + { + // Both effects the translator emits have exactly one property, and in both + // cases it is property zero, so one class covers both and there is no need + // for a property table. + Effect(GUID const& id, winrt::Windows::Foundation::IInspectable property) : + m_id(id), + m_property(std::move(property)) + { + } + + winrt::hstring Name() const noexcept + { + return m_name; + } + + void Name(winrt::hstring const& value) + { + m_name = value; + } + + void AddSource(winrt::Windows::Graphics::Effects::IGraphicsEffectSource const& source) + { + m_sources.push_back(source); + } + + IFACEMETHODIMP GetEffectId(GUID* id) noexcept + { + *id = m_id; + return S_OK; + } + + IFACEMETHODIMP GetPropertyCount(UINT* count) noexcept + { + *count = 1; + return S_OK; + } + + IFACEMETHODIMP GetProperty(UINT index, ABI::Windows::Foundation::IPropertyValue** value) noexcept + { + *value = nullptr; + + if (index != 0) + { + return E_BOUNDS; + } + + return m_property.as() + ->QueryInterface(__uuidof(ABI::Windows::Foundation::IPropertyValue), reinterpret_cast(value)); + } + + IFACEMETHODIMP GetSourceCount(UINT* count) noexcept + { + *count = static_cast(m_sources.size()); + return S_OK; + } + + IFACEMETHODIMP GetSource(UINT index, ABI::Windows::Graphics::Effects::IGraphicsEffectSource** source) noexcept + { + *source = nullptr; + + if (index >= m_sources.size()) + { + return E_BOUNDS; + } + + auto value = m_sources[index]; + *source = reinterpret_cast( + winrt::detach_abi(value)); + return S_OK; + } + + IFACEMETHODIMP GetNamedPropertyMapping( + LPCWSTR, + UINT*, + ABI::Windows::Graphics::Effects::GRAPHICS_EFFECT_PROPERTY_MAPPING*) noexcept + { + // Nothing animates an effect property by name, so there is nothing to map. + return E_INVALIDARG; + } + + private: + GUID m_id; + winrt::Windows::Foundation::IInspectable m_property; + winrt::hstring m_name; + std::vector m_sources; + }; +} + +namespace +{ + // --------------------------------------------------------------------------- + // The interpreter. + // + // One instance exists for the duration of one call to LoadComposition. It owns + // the realization caches, which is the only state the walk needs: every other + // question is answered by the buffer. + // --------------------------------------------------------------------------- + class Interpreter + { + public: + Interpreter(Compositor const& compositor, Fb::LottieComposition const& root) : + m_compositor(compositor), + m_root(root), + m_visuals(Count(root.visuals())), + m_shapes(Count(root.shapes())), + m_geometries(Count(root.geometries())), + m_paths(Count(root.canvas_geometries())), + m_brushes(Count(root.brushes())), + m_gradientStops(Count(root.gradient_stops())), + m_viewBoxes(Count(root.view_boxes())), + m_clips(Count(root.clips())), + m_shadows(Count(root.shadows())), + m_surfaces(Count(root.surfaces())), + m_effects(Count(root.effects())), + m_easings(Count(root.easings())), + m_animations(Count(root.animations())), + m_propertySets(Count(root.property_sets())), + m_controllers(Count(root.controllers())) + { + } + + Visual Run() + { + auto result = GetVisual(m_root.root_visual()); + Check(result != nullptr); + return result; + } + + private: + template + static size_t Count(T const* vector) + { + return vector == nullptr ? 0 : vector->size(); + } + + winrt::hstring GetString(uint32_t index) const + { + if (index == NullIndex) + { + return {}; + } + + auto const* strings = m_root.strings(); + Check(strings != nullptr && index < strings->size()); + auto const* value = strings->Get(index); + Check(value != nullptr); + + // The buffer holds UTF-8 and WinRT wants UTF-16, so this is the one place + // where text is copied out of the buffer. + return winrt::to_hstring(std::string_view(value->c_str(), value->size())); + } + + // ----------------------------------------------------------------------- + // Shared state. + // + // This is the reason the caches hold base types: everything an interpreter + // does to every node is available on CompositionObject. + // ----------------------------------------------------------------------- + void Initialize(CompositionObject const& target, Fb::CompObj const* source) + { + if (source == nullptr) + { + return; + } + + if (auto comment = source->comment(); comment != NullIndex) + { + target.Comment(GetString(comment)); + } + + // The property set is realized through its owner rather than on its own, + // so that a property set and the object that owns it cannot both create + // it. Doing it here also breaks the cycle between the two. + if (auto properties = source->properties(); properties != NullIndex) + { + RealizePropertySet(properties, target.Properties()); + } + + StartAnimations(target, source); + } + + void StartAnimations(CompositionObject const& target, Fb::CompObj const* source) + { + auto const* animators = source->animators(); + if (animators == nullptr) + { + return; + } + + for (uint32_t i = 0; i != animators->size(); ++i) + { + auto const* animator = animators->Get(i); + Check(animator != nullptr); + + auto property = GetString(animator->property()); + auto animation = GetAnimation(animator->animation()); + Check(animation != nullptr); + + // A custom controller is one that drives a property that is not its + // own, which the composition engine has no way to express. The + // managed instantiator rejects these too. + auto controllerIndex = animator->controller(); + if (controllerIndex != NullIndex && IsCustomController(controllerIndex)) + { + ThrowUnsupported(); + } + + target.StartAnimation(property, animation); + + if (controllerIndex != NullIndex) + { + RealizeController(controllerIndex, target, property); + } + } + } + + bool IsCustomController(uint32_t index) const + { + auto const* controller = At(m_root.controllers(), index); + return controller->is_custom(); + } + + // ----------------------------------------------------------------------- + // Property sets. + // ----------------------------------------------------------------------- + void RealizePropertySet(uint32_t index, CompositionPropertySet const& target) + { + Check(index < m_propertySets.size()); + + if (m_propertySets[index] != nullptr) + { + return; + } + + // Cached before the values are applied, because a value may be an + // expression that refers back to this property set. + m_propertySets[index] = target; + + auto const* source = At(m_root.property_sets(), index); + + if (auto const* values = source->values(); values != nullptr) + { + for (uint32_t i = 0; i != values->size(); ++i) + { + auto const* value = values->Get(i); + Check(value != nullptr); + + auto name = GetString(value->name()); + + switch (value->type()) + { + case Fb::PropertyValueType::Color: + target.InsertColor(name, ToColor(value->color())); + break; + case Fb::PropertyValueType::Scalar: + target.InsertScalar(name, value->scalar()); + break; + case Fb::PropertyValueType::Vector2: + target.InsertVector2(name, { Vector(value)->x(), Vector(value)->y() }); + break; + case Fb::PropertyValueType::Vector3: + target.InsertVector3(name, { Vector(value)->x(), Vector(value)->y(), Vector(value)->z() }); + break; + case Fb::PropertyValueType::Vector4: + target.InsertVector4(name, { Vector(value)->x(), Vector(value)->y(), Vector(value)->z(), Vector(value)->w() }); + break; + case Fb::PropertyValueType::None: + break; + default: + ThrowMalformed(); + } + } + } + + StartAnimations(target, source->base()); + } + + static Fb::Vec4 const* Vector(Fb::PropertyValue const* value) + { + auto const* result = value->vector(); + Check(result != nullptr); + return result; + } + + CompositionPropertySet GetPropertySet(uint32_t index) + { + Check(index < m_propertySets.size()); + + if (m_propertySets[index] == nullptr) + { + auto const* source = At(m_root.property_sets(), index); + + if (auto owner = source->owner(); owner != NullIndex) + { + // Realizing the owner populates the cache as a side effect, + // because Initialize realizes the owner's property set. + GetObjectReference(owner); + } + else + { + // A property set with no owner is a standalone one, which is how + // the translator exposes theming properties. + RealizePropertySet(index, m_compositor.CreatePropertySet()); + } + } + + Check(m_propertySets[index] != nullptr); + return m_propertySets[index]; + } + + // ----------------------------------------------------------------------- + // Controllers. + // ----------------------------------------------------------------------- + void RealizeController(uint32_t index, CompositionObject const& target, winrt::hstring const& property) + { + Check(index < m_controllers.size()); + + if (m_controllers[index] != nullptr) + { + return; + } + + auto controller = target.TryGetAnimationController(property); + if (controller == nullptr) + { + return; + } + + m_controllers[index] = controller; + + auto const* source = At(m_root.controllers(), index); + + if (source->is_paused()) + { + controller.Pause(); + } + + Initialize(controller, source->base()); + } + + // ----------------------------------------------------------------------- + // Object references. + // + // Only the three fields that can point at more than one kind of node are + // packed this way, so this is the only place a category has to be decoded. + // ----------------------------------------------------------------------- + CompositionObject GetObjectReference(uint32_t reference) + { + if (reference == NullIndex) + { + return nullptr; + } + + auto const index = reference & IndexMask; + + switch (static_cast(reference >> CategoryShift)) + { + case Fb::ObjectCategory::Visual: return GetVisual(index); + case Fb::ObjectCategory::Shape: return GetShape(index); + case Fb::ObjectCategory::Geometry: return GetGeometry(index); + case Fb::ObjectCategory::Brush: return GetBrush(index); + case Fb::ObjectCategory::Animation: return GetAnimation(index); + case Fb::ObjectCategory::Easing: return GetEasing(index); + case Fb::ObjectCategory::PropertySet: return GetPropertySet(index); + case Fb::ObjectCategory::Clip: return GetClip(index); + case Fb::ObjectCategory::Shadow: return GetShadow(index); + case Fb::ObjectCategory::GradientStop: return GetGradientStop(index); + case Fb::ObjectCategory::ViewBox: return GetViewBox(index); + case Fb::ObjectCategory::Surface: + { + // Only a visual surface is a CompositionObject; an image surface is + // not, and nothing can legitimately reference one this way. + auto surface = GetSurface(index); + auto result = surface.try_as(); + Check(result != nullptr); + return result; + } + case Fb::ObjectCategory::Controller: + // A controller is realized by the animator that produces it, so it + // can never be the target of a reference. + ThrowMalformed(); + default: + ThrowMalformed(); + } + } + + // ----------------------------------------------------------------------- + // Visuals. + // ----------------------------------------------------------------------- + Visual GetVisual(uint32_t index) + { + if (index == NullIndex) + { + return nullptr; + } + + Check(index < m_visuals.size()); + + if (m_visuals[index] != nullptr) + { + return m_visuals[index]; + } + + auto const* source = At(m_root.visuals(), index); + + // The concrete type is known only here. Everything below this point, + // including the recursive walk into the children, uses the base type. + ContainerVisual container{ nullptr }; + + switch (source->kind()) + { + case Fb::VisualKind::Container: + container = m_compositor.CreateContainerVisual(); + break; + case Fb::VisualKind::Sprite: + { + auto sprite = m_compositor.CreateSpriteVisual(); + m_visuals[index] = sprite; + if (auto brush = source->brush(); brush != NullIndex) + { + sprite.Brush(GetBrush(brush)); + } + + container = sprite; + break; + } + case Fb::VisualKind::Shape: + { + auto shapeVisual = m_compositor.CreateShapeVisual(); + m_visuals[index] = shapeVisual; + + if (auto viewBox = source->view_box(); viewBox != NullIndex) + { + shapeVisual.ViewBox(GetViewBox(viewBox)); + } + + if (auto const* shapes = source->shapes(); shapes != nullptr) + { + auto collection = shapeVisual.Shapes(); + for (uint32_t i = 0; i != shapes->size(); ++i) + { + auto shape = GetShape(shapes->Get(i)); + Check(shape != nullptr); + collection.Append(shape); + } + } + + container = shapeVisual; + break; + } + case Fb::VisualKind::Layer: + { + auto layer = m_compositor.CreateLayerVisual(); + m_visuals[index] = layer; + + if (auto shadow = source->shadow(); shadow != NullIndex) + { + layer.Shadow(GetShadow(shadow)); + } + + container = layer; + break; + } + default: + ThrowMalformed(); + } + + // Cached before the children are walked, so that a cycle terminates. + m_visuals[index] = container; + + ApplyVisualProperties(container, source); + + if (auto const* children = source->children(); children != nullptr) + { + auto collection = container.Children(); + for (uint32_t i = 0; i != children->size(); ++i) + { + auto child = GetVisual(children->Get(i)); + Check(child != nullptr); + collection.InsertAtTop(child); + } + } + + Initialize(container, source->base()); + + return container; + } + + // Takes a Visual rather than any of the four concrete kinds, because every + // property here is declared on Visual. + void ApplyVisualProperties(Visual const& target, Fb::Visual const* source) + { + if (auto value = source->border_mode()) + { + target.BorderMode(static_cast(*value)); + } + + if (auto const* value = source->center_point()) + { + target.CenterPoint(ToFloat3(value)); + } + + if (auto value = source->clip(); value != NullIndex) + { + target.Clip(GetClip(value)); + } + + if (auto value = source->is_visible()) + { + target.IsVisible(*value); + } + + if (auto const* value = source->offset()) + { + target.Offset(ToFloat3(value)); + } + + if (auto value = source->opacity()) + { + target.Opacity(*value); + } + + if (auto value = source->rotation_angle_in_degrees()) + { + target.RotationAngleInDegrees(*value); + } + + if (auto const* value = source->rotation_axis()) + { + target.RotationAxis(ToFloat3(value)); + } + + if (auto const* value = source->scale()) + { + target.Scale(ToFloat3(value)); + } + + if (auto const* value = source->size()) + { + target.Size(ToFloat2(value)); + } + + if (auto const* value = source->transform_matrix()) + { + target.TransformMatrix(ToFloat4x4(value)); + } + } + + // ----------------------------------------------------------------------- + // Shapes. + // ----------------------------------------------------------------------- + CompositionShape GetShape(uint32_t index) + { + if (index == NullIndex) + { + return nullptr; + } + + Check(index < m_shapes.size()); + + if (m_shapes[index] != nullptr) + { + return m_shapes[index]; + } + + auto const* source = At(m_root.shapes(), index); + + CompositionShape result{ nullptr }; + + switch (source->kind()) + { + case Fb::ShapeKind::Container: + { + auto container = m_compositor.CreateContainerShape(); + m_shapes[index] = container; + + if (auto const* shapes = source->shapes(); shapes != nullptr) + { + auto collection = container.Shapes(); + for (uint32_t i = 0; i != shapes->size(); ++i) + { + auto shape = GetShape(shapes->Get(i)); + Check(shape != nullptr); + collection.Append(shape); + } + } + + result = container; + break; + } + case Fb::ShapeKind::Sprite: + { + auto sprite = m_compositor.CreateSpriteShape(); + m_shapes[index] = sprite; + ApplySpriteShapeProperties(sprite, source); + result = sprite; + break; + } + default: + ThrowMalformed(); + } + + m_shapes[index] = result; + + ApplyShapeProperties(result, source); + Initialize(result, source->base()); + + return result; + } + + void ApplyShapeProperties(CompositionShape const& target, Fb::Shape const* source) + { + if (auto const* value = source->center_point()) + { + target.CenterPoint(ToFloat2(value)); + } + + if (auto const* value = source->offset()) + { + target.Offset(ToFloat2(value)); + } + + if (auto value = source->rotation_angle_in_degrees()) + { + target.RotationAngleInDegrees(*value); + } + + if (auto const* value = source->scale()) + { + target.Scale(ToFloat2(value)); + } + + if (auto const* value = source->transform_matrix()) + { + target.TransformMatrix(ToFloat3x2(value)); + } + } + + void ApplySpriteShapeProperties(CompositionSpriteShape const& target, Fb::Shape const* source) + { + if (auto value = source->geometry(); value != NullIndex) + { + target.Geometry(GetGeometry(value)); + } + + if (auto value = source->fill_brush(); value != NullIndex) + { + target.FillBrush(GetBrush(value)); + } + + if (auto value = source->stroke_brush(); value != NullIndex) + { + target.StrokeBrush(GetBrush(value)); + } + + if (auto value = source->is_stroke_non_scaling()) + { + target.IsStrokeNonScaling(*value); + } + + if (auto value = source->stroke_dash_offset()) + { + target.StrokeDashOffset(*value); + } + + if (auto const* values = source->stroke_dash_array(); values != nullptr) + { + auto dashes = target.StrokeDashArray(); + for (uint32_t i = 0; i != values->size(); ++i) + { + dashes.Append(values->Get(i)); + } + } + + if (auto value = source->stroke_dash_cap()) + { + target.StrokeDashCap(static_cast(*value)); + } + + if (auto value = source->stroke_start_cap()) + { + target.StrokeStartCap(static_cast(*value)); + } + + if (auto value = source->stroke_end_cap()) + { + target.StrokeEndCap(static_cast(*value)); + } + + if (auto value = source->stroke_line_join()) + { + target.StrokeLineJoin(static_cast(*value)); + } + + if (auto value = source->stroke_miter_limit()) + { + target.StrokeMiterLimit(static_cast(*value)); + } + + if (auto value = source->stroke_thickness()) + { + target.StrokeThickness(*value); + } + } + + // ----------------------------------------------------------------------- + // Geometries. + // ----------------------------------------------------------------------- + CompositionGeometry GetGeometry(uint32_t index) + { + if (index == NullIndex) + { + return nullptr; + } + + Check(index < m_geometries.size()); + + if (m_geometries[index] != nullptr) + { + return m_geometries[index]; + } + + auto const* source = At(m_root.geometries(), index); + + CompositionGeometry result{ nullptr }; + + switch (source->kind()) + { + case Fb::GeometryKind::Path: + { + auto path = source->path(); + result = m_compositor.CreatePathGeometry( + path == NullIndex ? nullptr : GetPath(path)); + break; + } + case Fb::GeometryKind::Rectangle: + { + auto rectangle = m_compositor.CreateRectangleGeometry(); + if (auto const* value = source->offset()) + { + rectangle.Offset(ToFloat2(value)); + } + + if (auto const* value = source->size()) + { + rectangle.Size(ToFloat2(value)); + } + + result = rectangle; + break; + } + case Fb::GeometryKind::RoundedRectangle: + { + auto rounded = m_compositor.CreateRoundedRectangleGeometry(); + if (auto const* value = source->offset()) + { + rounded.Offset(ToFloat2(value)); + } + + if (auto const* value = source->size()) + { + rounded.Size(ToFloat2(value)); + } + + if (auto const* value = source->corner_radius()) + { + rounded.CornerRadius(ToFloat2(value)); + } + + result = rounded; + break; + } + case Fb::GeometryKind::Ellipse: + { + auto ellipse = m_compositor.CreateEllipseGeometry(); + if (auto const* value = source->center()) + { + ellipse.Center(ToFloat2(value)); + } + + if (auto const* value = source->radius()) + { + ellipse.Radius(ToFloat2(value)); + } + + result = ellipse; + break; + } + default: + ThrowMalformed(); + } + + m_geometries[index] = result; + + if (auto value = source->trim_start()) + { + result.TrimStart(*value); + } + + if (auto value = source->trim_end()) + { + result.TrimEnd(*value); + } + + if (auto value = source->trim_offset()) + { + result.TrimOffset(*value); + } + + Initialize(result, source->base()); + + return result; + } + + // ----------------------------------------------------------------------- + // Paths. + // + // A path is not a CompositionObject, so it has no shared state and no + // animators; it is only ever a value. + // ----------------------------------------------------------------------- + CompositionPath GetPath(uint32_t index) + { + Check(index < m_paths.size()); + + if (m_paths[index] == nullptr) + { + auto geometry = BuildGeometry(index); + m_paths[index] = CompositionPath(winrt::make(std::move(geometry))); + } + + return m_paths[index]; + } + + winrt::com_ptr BuildGeometry(uint32_t index) + { + auto const* source = At(m_root.canvas_geometries(), index); + + switch (source->kind()) + { + case Fb::CanvasGeometryKind::Combination: + { + auto a = BuildGeometry(Required(source->a())); + auto b = BuildGeometry(Required(source->b())); + + winrt::com_ptr result; + winrt::check_hresult(D2DFactory()->CreatePathGeometry(result.put())); + + winrt::com_ptr sink; + winrt::check_hresult(result->Open(sink.put())); + + auto const matrix = source->matrix(); + auto const transform = matrix == nullptr + ? D2D1::Matrix3x2F::Identity() + : D2D1::Matrix3x2F( + matrix->m11(), matrix->m12(), + matrix->m21(), matrix->m22(), + matrix->m31(), matrix->m32()); + + winrt::check_hresult(a->CombineWithGeometry( + b.get(), + ToCombineMode(source->combine_mode()), + transform, + sink.get())); + + winrt::check_hresult(sink->Close()); + return result; + } + case Fb::CanvasGeometryKind::Ellipse: + { + winrt::com_ptr result; + winrt::check_hresult(D2DFactory()->CreateEllipseGeometry( + D2D1::Ellipse( + D2D1::Point2F(source->x(), source->y()), + source->radius_x(), + source->radius_y()), + result.put())); + return result; + } + case Fb::CanvasGeometryKind::RoundedRectangle: + { + winrt::com_ptr result; + winrt::check_hresult(D2DFactory()->CreateRoundedRectangleGeometry( + D2D1::RoundedRect( + D2D1::RectF( + source->x(), + source->y(), + source->x() + source->w(), + source->y() + source->h()), + source->radius_x(), + source->radius_y()), + result.put())); + return result; + } + case Fb::CanvasGeometryKind::TransformedGeometry: + { + auto inner = BuildGeometry(Required(source->source())); + + auto const* matrix = source->matrix(); + Check(matrix != nullptr); + + winrt::com_ptr result; + winrt::check_hresult(D2DFactory()->CreateTransformedGeometry( + inner.get(), + D2D1::Matrix3x2F( + matrix->m11(), matrix->m12(), + matrix->m21(), matrix->m22(), + matrix->m31(), matrix->m32()), + result.put())); + return result; + } + case Fb::CanvasGeometryKind::Group: + { + auto const* geometries = source->geometries(); + Check(geometries != nullptr); + + std::vector> owned; + std::vector raw; + owned.reserve(geometries->size()); + raw.reserve(geometries->size()); + + for (uint32_t i = 0; i != geometries->size(); ++i) + { + owned.push_back(BuildGeometry(geometries->Get(i))); + raw.push_back(owned.back().get()); + } + + winrt::com_ptr result; + winrt::check_hresult(D2DFactory()->CreateGeometryGroup( + static_cast(source->fill_rule()), + raw.data(), + static_cast(raw.size()), + result.put())); + return result; + } + case Fb::CanvasGeometryKind::Path: + return BuildPath(source); + default: + ThrowMalformed(); + } + } + + // Replays the flattened command stream. The operands of every command are + // consecutive in one array, which is why there is no table per command. + winrt::com_ptr BuildPath(Fb::CanvasGeometry const* source) + { + winrt::com_ptr result; + winrt::check_hresult(D2DFactory()->CreatePathGeometry(result.put())); + + winrt::com_ptr sink; + winrt::check_hresult(result->Open(sink.put())); + + sink->SetFillMode(static_cast(source->fill_rule())); + + auto const* ops = source->ops(); + auto const* operands = source->operands(); + + uint32_t next = 0; + bool inFigure = false; + + auto take = [&](uint32_t count) -> float const* + { + Check(operands != nullptr && next + count <= operands->size()); + auto const* values = operands->data() + next; + next += count; + return values; + }; + + for (uint32_t i = 0; ops != nullptr && i != ops->size(); ++i) + { + switch (static_cast(ops->Get(i))) + { + case Fb::PathOp::BeginFigure: + { + Check(!inFigure); + auto const* v = take(2); + + // Every figure the translator emits is filled; a hollow figure + // would need a begin flag in the format, which it does not have + // because the object model has no way to express one. + sink->BeginFigure(D2D1::Point2F(v[0], v[1]), D2D1_FIGURE_BEGIN_FILLED); + inFigure = true; + break; + } + case Fb::PathOp::EndFigure: + { + Check(inFigure); + auto const* v = take(1); + sink->EndFigure(v[0] != 0 ? D2D1_FIGURE_END_CLOSED : D2D1_FIGURE_END_OPEN); + inFigure = false; + break; + } + case Fb::PathOp::AddLine: + { + Check(inFigure); + auto const* v = take(2); + sink->AddLine(D2D1::Point2F(v[0], v[1])); + break; + } + case Fb::PathOp::AddCubicBezier: + { + Check(inFigure); + auto const* v = take(6); + sink->AddBezier(D2D1::BezierSegment( + D2D1::Point2F(v[0], v[1]), + D2D1::Point2F(v[2], v[3]), + D2D1::Point2F(v[4], v[5]))); + break; + } + default: + ThrowMalformed(); + } + } + + Check(!inFigure); + winrt::check_hresult(sink->Close()); + + return result; + } + + static uint32_t Required(uint32_t index) + { + Check(index != NullIndex); + return index; + } + + // ----------------------------------------------------------------------- + // Brushes. + // ----------------------------------------------------------------------- + CompositionBrush GetBrush(uint32_t index) + { + if (index == NullIndex) + { + return nullptr; + } + + Check(index < m_brushes.size()); + + if (m_brushes[index] != nullptr) + { + return m_brushes[index]; + } + + auto const* source = At(m_root.brushes(), index); + + CompositionBrush result{ nullptr }; + + switch (source->kind()) + { + case Fb::BrushKind::Color: + result = m_compositor.CreateColorBrush(ToColor(source->color())); + break; + case Fb::BrushKind::LinearGradient: + { + auto brush = m_compositor.CreateLinearGradientBrush(); + m_brushes[index] = brush; + + if (auto const* value = source->start_point()) + { + brush.StartPoint(ToFloat2(value)); + } + + if (auto const* value = source->end_point()) + { + brush.EndPoint(ToFloat2(value)); + } + + ApplyGradientBrushProperties(brush, source); + result = brush; + break; + } + case Fb::BrushKind::RadialGradient: + { + auto brush = m_compositor.CreateRadialGradientBrush(); + m_brushes[index] = brush; + + if (auto const* value = source->ellipse_center()) + { + brush.EllipseCenter(ToFloat2(value)); + } + + if (auto const* value = source->ellipse_radius()) + { + brush.EllipseRadius(ToFloat2(value)); + } + + if (auto const* value = source->gradient_origin_offset()) + { + brush.GradientOriginOffset(ToFloat2(value)); + } + + ApplyGradientBrushProperties(brush, source); + result = brush; + break; + } + case Fb::BrushKind::Surface: + result = m_compositor.CreateSurfaceBrush(GetSurface(Required(source->surface()))); + break; + case Fb::BrushKind::Mask: + { + auto brush = m_compositor.CreateMaskBrush(); + m_brushes[index] = brush; + brush.Source(GetBrush(source->source())); + brush.Mask(GetBrush(source->mask())); + result = brush; + break; + } + case Fb::BrushKind::Effect: + { + auto effect = GetEffect(Required(source->effect())); + auto brush = m_compositor.CreateEffectFactory(effect).CreateBrush(); + m_brushes[index] = brush; + + if (auto const* parameters = source->source_parameters(); parameters != nullptr) + { + for (uint32_t i = 0; i != parameters->size(); ++i) + { + auto const* parameter = parameters->Get(i); + Check(parameter != nullptr); + brush.SetSourceParameter( + GetString(parameter->name()), + GetBrush(parameter->brush())); + } + } + + result = brush; + break; + } + default: + ThrowMalformed(); + } + + m_brushes[index] = result; + + Initialize(result, source->base()); + + return result; + } + + void ApplyGradientBrushProperties(CompositionGradientBrush const& target, Fb::Brush const* source) + { + if (auto const* value = source->anchor_point()) + { + target.AnchorPoint(ToFloat2(value)); + } + + if (auto const* value = source->center_point()) + { + target.CenterPoint(ToFloat2(value)); + } + + if (auto value = source->extend_mode()) + { + target.ExtendMode(static_cast(*value)); + } + + if (auto value = source->interpolation_space()) + { + target.InterpolationSpace(static_cast(*value)); + } + + if (auto value = source->mapping_mode()) + { + target.MappingMode(static_cast(*value)); + } + + if (auto const* value = source->offset()) + { + target.Offset(ToFloat2(value)); + } + + if (auto value = source->rotation_angle_in_degrees()) + { + target.RotationAngleInDegrees(*value); + } + + if (auto const* value = source->scale()) + { + target.Scale(ToFloat2(value)); + } + + if (auto const* value = source->transform_matrix()) + { + target.TransformMatrix(ToFloat3x2(value)); + } + + if (auto const* stops = source->color_stops(); stops != nullptr) + { + auto collection = target.ColorStops(); + for (uint32_t i = 0; i != stops->size(); ++i) + { + collection.Append(GetGradientStop(stops->Get(i))); + } + } + } + + CompositionColorGradientStop GetGradientStop(uint32_t index) + { + Check(index < m_gradientStops.size()); + + if (m_gradientStops[index] == nullptr) + { + auto const* source = At(m_root.gradient_stops(), index); + + auto stop = m_compositor.CreateColorGradientStop( + source->offset(), + ToColor(source->color())); + + m_gradientStops[index] = stop; + Initialize(stop, source->base()); + } + + return m_gradientStops[index]; + } + + // ----------------------------------------------------------------------- + // Clips, view boxes, shadows and surfaces. + // ----------------------------------------------------------------------- + CompositionClip GetClip(uint32_t index) + { + Check(index < m_clips.size()); + + if (m_clips[index] != nullptr) + { + return m_clips[index]; + } + + auto const* source = At(m_root.clips(), index); + + CompositionClip result{ nullptr }; + + switch (source->kind()) + { + case Fb::ClipKind::Inset: + { + auto clip = m_compositor.CreateInsetClip(); + + if (auto value = source->left_inset()) + { + clip.LeftInset(*value); + } + + if (auto value = source->right_inset()) + { + clip.RightInset(*value); + } + + if (auto value = source->top_inset()) + { + clip.TopInset(*value); + } + + if (auto value = source->bottom_inset()) + { + clip.BottomInset(*value); + } + + result = clip; + break; + } + case Fb::ClipKind::Geometric: + { + auto clip = m_compositor.CreateGeometricClip(); + m_clips[index] = clip; + clip.Geometry(GetGeometry(source->geometry())); + result = clip; + break; + } + default: + ThrowMalformed(); + } + + m_clips[index] = result; + + if (auto const* value = source->center_point()) + { + result.CenterPoint(ToFloat2(value)); + } + + if (auto const* value = source->scale()) + { + result.Scale(ToFloat2(value)); + } + + Initialize(result, source->base()); + + return result; + } + + CompositionViewBox GetViewBox(uint32_t index) + { + Check(index < m_viewBoxes.size()); + + if (m_viewBoxes[index] == nullptr) + { + auto const* source = At(m_root.view_boxes(), index); + + auto viewBox = m_compositor.CreateViewBox(); + m_viewBoxes[index] = viewBox; + + if (auto const* value = source->size()) + { + viewBox.Size(ToFloat2(value)); + } + + Initialize(viewBox, source->base()); + } + + return m_viewBoxes[index]; + } + + CompositionShadow GetShadow(uint32_t index) + { + Check(index < m_shadows.size()); + + if (m_shadows[index] != nullptr) + { + return m_shadows[index]; + } + + auto const* source = At(m_root.shadows(), index); + Check(source->kind() == Fb::ShadowKind::Drop); + + auto shadow = m_compositor.CreateDropShadow(); + m_shadows[index] = shadow; + + if (auto value = source->blur_radius()) + { + shadow.BlurRadius(*value); + } + + if (auto const* value = source->color()) + { + shadow.Color(ToColor(value)); + } + + if (auto mask = source->mask(); mask != NullIndex) + { + shadow.Mask(GetBrush(mask)); + } + + if (auto const* value = source->offset()) + { + shadow.Offset(ToFloat3(value)); + } + + if (auto value = source->opacity()) + { + shadow.Opacity(*value); + } + + if (auto value = source->source_policy()) + { + shadow.SourcePolicy(static_cast(*value)); + } + + Initialize(shadow, source->base()); + + return shadow; + } + + ICompositionSurface GetSurface(uint32_t index) + { + Check(index < m_surfaces.size()); + + if (m_surfaces[index] != nullptr) + { + return m_surfaces[index]; + } + + auto const* source = At(m_root.surfaces(), index); + + switch (source->kind()) + { + case Fb::SurfaceKind::VisualSurface: + { + auto surface = m_compositor.CreateVisualSurface(); + m_surfaces[index] = surface; + + if (auto value = source->source_visual(); value != NullIndex) + { + surface.SourceVisual(GetVisual(value)); + } + + if (auto const* value = source->source_size()) + { + surface.SourceSize(ToFloat2(value)); + } + + if (auto const* value = source->source_offset()) + { + surface.SourceOffset(ToFloat2(value)); + } + + Initialize(surface, source->base()); + break; + } + case Fb::SurfaceKind::LoadedImageFromUri: + case Fb::SurfaceKind::LoadedImageFromStream: + // LoadedImageSurface lives in Windows.UI.Xaml.Media, so an + // interpreter that is usable outside XAML cannot create one. The + // translator only emits these for animations that embed an image, + // which the Lottie feature set this interpreter targets excludes. + ThrowUnsupported(); + default: + ThrowMalformed(); + } + + return m_surfaces[index]; + } + + winrt::Windows::Graphics::Effects::IGraphicsEffect GetEffect(uint32_t index) + { + Check(index < m_effects.size()); + + if (m_effects[index] != nullptr) + { + return m_effects[index]; + } + + auto const* source = At(m_root.effects(), index); + + winrt::com_ptr effect; + + switch (source->kind()) + { + case Fb::EffectKind::Composite: + // CanvasComposite and D2D1_COMPOSITE_MODE agree on every member, so + // the mode from the buffer is the D2D value. + effect = winrt::make_self( + CLSID_D2D1Composite, + winrt::box_value(static_cast(source->mode()))); + break; + case Fb::EffectKind::GaussianBlur: + effect = winrt::make_self( + CLSID_D2D1GaussianBlur, + winrt::box_value(source->blur_amount())); + break; + default: + ThrowMalformed(); + } + + // The sources are the names the brush later binds real brushes to. The + // composition engine matches them up by name. + if (auto const* sources = source->sources(); sources != nullptr) + { + for (uint32_t i = 0; i != sources->size(); ++i) + { + effect->AddSource(CompositionEffectSourceParameter(GetString(sources->Get(i)))); + } + } + + auto result = effect.as(); + m_effects[index] = result; + return result; + } + + // ----------------------------------------------------------------------- + // Easings. + // ----------------------------------------------------------------------- + CompositionEasingFunction GetEasing(uint32_t index) + { + if (index == NullIndex) + { + return nullptr; + } + + Check(index < m_easings.size()); + + if (m_easings[index] != nullptr) + { + return m_easings[index]; + } + + auto const* source = At(m_root.easings(), index); + + CompositionEasingFunction result{ nullptr }; + + switch (source->kind()) + { + case Fb::EasingKind::Linear: + result = m_compositor.CreateLinearEasingFunction(); + break; + case Fb::EasingKind::CubicBezier: + { + auto const* first = source->control_point_1(); + auto const* second = source->control_point_2(); + result = m_compositor.CreateCubicBezierEasingFunction( + first == nullptr ? float2{ 0, 0 } : ToFloat2(first), + second == nullptr ? float2{ 0, 0 } : ToFloat2(second)); + break; + } + case Fb::EasingKind::Step: + { + auto stepCount = source->step_count(); + auto step = m_compositor.CreateStepEasingFunction( + stepCount ? *stepCount : 1); + + if (auto value = source->initial_step()) + { + step.InitialStep(*value); + } + + if (auto value = source->final_step()) + { + step.FinalStep(*value); + } + + if (auto value = source->is_initial_step_single_frame()) + { + step.IsInitialStepSingleFrame(*value); + } + + if (auto value = source->is_final_step_single_frame()) + { + step.IsFinalStepSingleFrame(*value); + } + + result = step; + break; + } + default: + ThrowMalformed(); + } + + m_easings[index] = result; + Initialize(result, source->base()); + + return result; + } + + // ----------------------------------------------------------------------- + // Animations. + // ----------------------------------------------------------------------- + CompositionAnimation GetAnimation(uint32_t index) + { + if (index == NullIndex) + { + return nullptr; + } + + Check(index < m_animations.size()); + + if (m_animations[index] != nullptr) + { + return m_animations[index]; + } + + auto const* source = At(m_root.animations(), index); + + CompositionAnimation result = source->kind() == Fb::AnimationKind::Expression + ? CreateExpressionAnimation(source) + : CreateKeyFrameAnimation(source); + + m_animations[index] = result; + + if (auto target = source->target(); target != NullIndex) + { + result.Target(GetString(target)); + } + + if (auto const* parameters = source->reference_parameters(); parameters != nullptr) + { + for (uint32_t i = 0; i != parameters->size(); ++i) + { + auto const* parameter = parameters->Get(i); + Check(parameter != nullptr); + + auto value = GetObjectReference(parameter->target()); + Check(value != nullptr); + result.SetReferenceParameter(GetString(parameter->name()), value); + } + } + + Initialize(result, source->base()); + + return result; + } + + CompositionAnimation CreateExpressionAnimation(Fb::Animation const* source) + { + return m_compositor.CreateExpressionAnimation(GetString(Required(source->expression()))); + } + + // Creates the animation and inserts its key frames in one place, because the + // concrete type is only needed in order to insert a value key frame and it + // would otherwise have to be recovered with a cast. + CompositionAnimation CreateKeyFrameAnimation(Fb::Animation const* source) + { + auto const* frames = source->key_frames(); + + KeyFrameAnimation result{ nullptr }; + + switch (source->kind()) + { + case Fb::AnimationKind::Scalar: + { + auto animation = m_compositor.CreateScalarKeyFrameAnimation(); + ForEachValueKeyFrame(frames, [&](Fb::KeyFrame const* frame) + { + animation.InsertKeyFrame(frame->progress(), frame->scalar(), GetEasing(frame->easing())); + }); + result = animation; + break; + } + case Fb::AnimationKind::Vector2: + { + auto animation = m_compositor.CreateVector2KeyFrameAnimation(); + ForEachValueKeyFrame(frames, [&](Fb::KeyFrame const* frame) + { + auto const* v = RequireVector(frame); + animation.InsertKeyFrame(frame->progress(), { v->x(), v->y() }, GetEasing(frame->easing())); + }); + result = animation; + break; + } + case Fb::AnimationKind::Vector3: + { + auto animation = m_compositor.CreateVector3KeyFrameAnimation(); + ForEachValueKeyFrame(frames, [&](Fb::KeyFrame const* frame) + { + auto const* v = RequireVector(frame); + animation.InsertKeyFrame(frame->progress(), { v->x(), v->y(), v->z() }, GetEasing(frame->easing())); + }); + result = animation; + break; + } + case Fb::AnimationKind::Vector4: + { + auto animation = m_compositor.CreateVector4KeyFrameAnimation(); + ForEachValueKeyFrame(frames, [&](Fb::KeyFrame const* frame) + { + auto const* v = RequireVector(frame); + animation.InsertKeyFrame(frame->progress(), { v->x(), v->y(), v->z(), v->w() }, GetEasing(frame->easing())); + }); + result = animation; + break; + } + case Fb::AnimationKind::Color: + { + auto animation = m_compositor.CreateColorKeyFrameAnimation(); + + if (auto value = source->interpolation_color_space()) + { + animation.InterpolationColorSpace(static_cast(*value)); + } + + ForEachValueKeyFrame(frames, [&](Fb::KeyFrame const* frame) + { + animation.InsertKeyFrame(frame->progress(), ToColor(frame->color()), GetEasing(frame->easing())); + }); + result = animation; + break; + } + case Fb::AnimationKind::Boolean: + { + auto animation = m_compositor.CreateBooleanKeyFrameAnimation(); + + // A boolean cannot be interpolated, so it has no easing. + ForEachValueKeyFrame(frames, [&](Fb::KeyFrame const* frame) + { + animation.InsertKeyFrame(frame->progress(), frame->scalar() != 0); + }); + result = animation; + break; + } + case Fb::AnimationKind::Path: + { + auto animation = m_compositor.CreatePathKeyFrameAnimation(); + ForEachValueKeyFrame(frames, [&](Fb::KeyFrame const* frame) + { + animation.InsertKeyFrame(frame->progress(), GetPath(Required(frame->path())), GetEasing(frame->easing())); + }); + result = animation; + break; + } + default: + ThrowMalformed(); + } + + // Expression key frames are inserted through the base type, so they do + // not have to be repeated in every branch above. They are inserted after + // the value key frames, which is safe because a key frame animation is a + // map keyed on progress rather than a list. + if (frames != nullptr) + { + for (uint32_t i = 0; i != frames->size(); ++i) + { + auto const* frame = frames->Get(i); + Check(frame != nullptr); + + if (frame->kind() == Fb::KeyFrameKind::Expression) + { + result.InsertExpressionKeyFrame( + frame->progress(), + GetString(Required(frame->expression())), + GetEasing(frame->easing())); + } + } + } + + if (auto ticks = source->duration_ticks(); ticks != 0) + { + result.Duration(winrt::Windows::Foundation::TimeSpan{ ticks }); + } + + return result; + } + + template + void ForEachValueKeyFrame(::flatbuffers::Vector<::flatbuffers::Offset> const* frames, F&& apply) + { + if (frames == nullptr) + { + return; + } + + for (uint32_t i = 0; i != frames->size(); ++i) + { + auto const* frame = frames->Get(i); + Check(frame != nullptr); + + if (frame->kind() == Fb::KeyFrameKind::Value) + { + apply(frame); + } + } + } + + static Fb::Vec4 const* RequireVector(Fb::KeyFrame const* frame) + { + auto const* result = frame->vector(); + Check(result != nullptr); + return result; + } + + Compositor const& m_compositor; + Fb::LottieComposition const& m_root; + + // The caches. Each is exactly as long as the matching vector in the buffer, + // so a lookup is a bounds check and an index. Each holds the least derived + // type that any reference to that category needs, which is what stops a + // realized node from ever having to be cast back to what it was. + std::vector m_visuals; + std::vector m_shapes; + std::vector m_geometries; + std::vector m_paths; + std::vector m_brushes; + std::vector m_gradientStops; + std::vector m_viewBoxes; + std::vector m_clips; + std::vector m_shadows; + std::vector m_surfaces; + std::vector m_effects; + std::vector m_easings; + std::vector m_animations; + std::vector m_propertySets; + std::vector m_controllers; + }; +} + +namespace CommunityToolkit::WinUI::Lottie +{ + Visual LoadComposition(Compositor const& compositor, std::span buffer) + { + // The buffer is untrusted, so nothing is read out of it until the verifier + // has agreed that every offset in it is inside it. + ::flatbuffers::Verifier verifier( + reinterpret_cast(buffer.data()), + buffer.size()); + + if (buffer.size() < sizeof(uint32_t) + 4 || + !Fb::LottieCompositionBufferHasIdentifier(buffer.data()) || + !Fb::VerifyLottieCompositionBuffer(verifier)) + { + ThrowMalformed(); + } + + auto const* root = Fb::GetLottieComposition(buffer.data()); + Check(root != nullptr); + + // A newer schema may store things in fields this build does not read, so a + // buffer that declares one is refused rather than silently misinterpreted. + if (root->schema_version() > SupportedSchemaVersion) + { + ThrowUnsupported(); + } + + return Interpreter(compositor, *root).Run(); + } + + CompositionPropertySet ProgressPropertySet(Visual const& root) + { + return root.Properties(); + } +} diff --git a/dlls/LottieRuntime/LottieRuntime.h b/dlls/LottieRuntime/LottieRuntime.h new file mode 100644 index 00000000..28a65f6f --- /dev/null +++ b/dlls/LottieRuntime/LottieRuntime.h @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#pragma once + +#include +#include + +#include + +// The component is usable either as a DLL or compiled straight into an application. +// Define LOTTIERUNTIME_STATIC to build it in, and LOTTIERUNTIME_EXPORTS when building +// the DLL itself. +#if defined(LOTTIERUNTIME_STATIC) +#define LOTTIERUNTIME_API +#elif defined(LOTTIERUNTIME_EXPORTS) +#define LOTTIERUNTIME_API __declspec(dllexport) +#else +#define LOTTIERUNTIME_API __declspec(dllimport) +#endif + +namespace CommunityToolkit::WinUI::Lottie +{ + // Builds the composition described by a serialized Lottie composition. + // + // The buffer is the output of LottieGen -Language flatbuffer, or equivalently of + // CompositionSerializer. It is treated as untrusted: it is verified before it is + // read, and every index in it is range checked, so a malformed buffer produces an + // exception rather than undefined behaviour. + // + // The returned visual is the root of a tree that is already animated and ready to + // be attached to a target with SetElementChildVisual or as a child of another + // visual. The animations are bound to the "Progress" property of the property set + // returned by ProgressPropertySet, so playback is driven by animating that single + // property. + // + // The result is the least derived type that describes the root, because a caller + // that only parents the tree and drives its progress never needs anything more. + // + // Throws winrt::hresult_error with E_INVALIDARG if the buffer is not a well formed + // composition, and E_NOTIMPL if it requires a feature this build does not have. + LOTTIERUNTIME_API winrt::Windows::UI::Composition::Visual LoadComposition( + winrt::Windows::UI::Composition::Compositor const& compositor, + std::span buffer); + + // The property set that drives the animation. Animating its "Progress" scalar from + // 0 to 1 plays the animation once. + // + // This is the property set of the visual returned by LoadComposition, so it is only + // a convenience; it exists so that callers do not have to know that detail. + LOTTIERUNTIME_API winrt::Windows::UI::Composition::CompositionPropertySet ProgressPropertySet( + winrt::Windows::UI::Composition::Visual const& root); +} diff --git a/dlls/LottieRuntime/LottieRuntime.vcxproj b/dlls/LottieRuntime/LottieRuntime.vcxproj new file mode 100644 index 00000000..846e7c15 --- /dev/null +++ b/dlls/LottieRuntime/LottieRuntime.vcxproj @@ -0,0 +1,81 @@ + + + + + Debug + x64 + + + Release + x64 + + + Debug + ARM64 + + + Release + ARM64 + + + + + {6E0A5B4C-2F1D-4B7A-9C3E-1D5A8F0B2E47} + LottieRuntime + DynamicLibrary + v143 + Unicode + 10.0.22621.0 + + + 10.0.17763.0 + + + + + + + true + false + true + + + + + stdcpp20 + LOTTIERUNTIME_EXPORTS;WIN32_LEAN_AND_MEAN;NOMINMAX;%(PreprocessorDefinitions) + $(MSBuildThisFileDirectory);$(MSBuildThisFileDirectory)packages\flatbuffers\include;%(AdditionalIncludeDirectories) + Level4 + true + + + Sync + + + MinSpace + true + true + + + d2d1.lib;windowsapp.lib;%(AdditionalDependencies) + + + true + true + + + + + + + + + + + + + + diff --git a/dlls/LottieRuntime/README.md b/dlls/LottieRuntime/README.md new file mode 100644 index 00000000..6c2f0541 --- /dev/null +++ b/dlls/LottieRuntime/README.md @@ -0,0 +1,135 @@ +# LottieRuntime + +A native runtime interpreter for Lottie animations. + +`LottieRuntime` builds a Windows.UI.Composition visual tree from a serialized +composition, at runtime, without a JSON parser and without generated code. It is a +header and a source file, so it can be built as a DLL that several applications +share, or compiled straight into one. + +## How it fits in + +Lottie-Windows already turns a Lottie file into a `WinCompData` graph, which is a +description of a composition tree. There were two things that could consume that +graph: + +| Consumer | When it runs | Cost | +| --- | --- | --- | +| `Instantiator` | Runtime, in the app | Ships the whole JSON reader and translator | +| `LottieGen` code generators | Build time | No runtime cost, but one class per animation | + +This adds a third. `CompositionSerializer` writes the `WinCompData` graph into a +FlatBuffer at build time, and `LottieRuntime` reads that buffer at runtime. The +animation stays data, so it can be swapped, downloaded or themed without a rebuild, +and the code that reads it is the same size no matter how many animations an +application has. + +``` +Lottie JSON --> LottieCompositionReader --> LottieData + | + v + LottieToWinComp + | + v + WinCompData graph + / | \ + / | \ + Instantiator UIDataCodeGen CompositionSerializer + (runtime C#) (C#/C++/CX) | + v + .lcomp buffer + | + v + LottieRuntime + (runtime C++) +``` + +Because the FlatBuffer is written from the same graph the other two consume, all +three produce the same visual tree. The round trip tests in +`tests/CompDataFlatbuffer.Tests` assert exactly that. + +## Producing a buffer + +``` +LottieGen -InputFile animation.json -Language flatbuffer -OutputFolder . +``` + +This writes `Animation.lcomp`. The format is described by +[`source/LottieFlatbuffer/lottie_comp.fbs`](../../source/LottieFlatbuffer/lottie_comp.fbs). + +## Using it + +```cpp +#include + +auto bytes = ReadFileBytes(L"Animation.lcomp"); +auto root = CommunityToolkit::WinUI::Lottie::LoadComposition(compositor, bytes); + +// Drive the animation by animating one scalar from 0 to 1. +auto progress = CommunityToolkit::WinUI::Lottie::ProgressPropertySet(root); +progress.StartAnimation(L"Progress", playAnimation); + +ElementCompositionPreview::SetElementChildVisual(element, root); +``` + +`LoadComposition` returns a `Visual`, which is the least derived type that describes +the root. A caller that parents the tree and drives its progress never needs more +than that. + +## Design + +**Least derived interfaces.** A node's concrete type is known only inside the +function that creates it. The realization caches hold `Visual`, +`CompositionShape`, `CompositionBrush`, `CompositionGeometry` and so on, and every +call the interpreter makes afterwards is declared on those types. There is no point +at which the interpreter asks what a node actually is, because the buffer already +said, and the answer was consumed at the moment of creation. The one exception is +`ObjectCategory::Surface` in an object reference, where the buffer permits an image +surface that is not a `CompositionObject`; that is a validation check, not a +downcast in the normal path. + +**Size first.** There is one function per node *category*, not per node type, and +the state shared by every node is applied by a single function that takes +`CompositionObject`. Everything is in one translation unit, so `/OPT:REF` discards +the code for whatever an application never reaches. + +**Runtime cost second.** Each node is realized at most once. Lookups are a bounds +check and an array index; there is no hash table anywhere. The buffer is read in +place, so no intermediate object model is built. + +**Transient memory third.** The only allocations are the caches, which are exactly +as long as the buffer's node vectors, and the D2D geometries, which are released as +soon as they have been given to a `CompositionPath`. + +**Untrusted input.** A buffer may have come from a file or a download. It is checked +for the `LCMP` identifier, run through the FlatBuffers verifier, and every index +read out of it is range checked. A malformed buffer throws +`winrt::hresult_invalid_argument`; it never reads outside the buffer. + +## Dependencies + +Direct2D, for path geometry, and the composition effect interop interface, for the +two effects the translator emits. There is no dependency on Win2D: the interpreter +implements `IGeometrySource2DInterop` and `IGraphicsEffectD2D1Interop` itself, which +is a few dozen lines and avoids pulling a large component into every application. + +Requires the FlatBuffers C++ runtime headers (headers only, no library) from +[flatbuffers 25.2.10](https://github.com/google/flatbuffers/releases/tag/v25.2.10), +which must match the `flatc` used to generate `Generated/lottie_comp_generated.h`. + +## Limitations + +`LoadedImageSurface` is a Windows.UI.Xaml.Media type, so an interpreter that is +usable outside XAML cannot create one. An animation that embeds a raster image +throws `winrt::hresult_not_implemented`. Custom animation controllers are rejected +for the same reason the managed `Instantiator` rejects them: the composition engine +has no way to express one. + +## Status + +**This code has not been compiled.** It was written on a Linux host, where neither +MSVC nor the Windows SDK is available, so it has never been through a compiler and +must be treated as unverified. Its behaviour is specified by `Instantiator.cs` and +by `CompositionDeserializer.cs`, both of which are exercised by the tests in +`tests/CompDataFlatbuffer.Tests`, so the shape of what it does is well tested even +though this particular expression of it is not. From 8cf90b6803bd8a4a64820c8a246ec6a031b64065 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 31 Aug 2026 18:48:19 +0000 Subject: [PATCH 11/39] Document the format, the native interpreter and how they relate Co-authored-by: jonwis <18537118+jonwis@users.noreply.github.com> --- README.md | 6 +++ dlls/README.md | 8 ++++ source/LottieFlatbuffer/README.md | 75 +++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 source/LottieFlatbuffer/README.md diff --git a/README.md b/README.md index 968bb183..15b643b5 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,12 @@ Lottie-Windows consists of 3 related products: This repo also contains source code for **[samples](/LottieViewer)**. +It also contains **[LottieRuntime](/dlls/LottieRuntime)**, a native C++/WinRT interpreter that +builds the composition tree at runtime from a serialized animation produced by +`LottieGen -Language flatbuffer`. It keeps the animation as data, like the JSON path does, but +without shipping a JSON parser or the translator. See +[the format](/source/LottieFlatbuffer/README.md) for how the two relate. + ## Quick start There are **two** options to integrate Lottie animations into your **WinUI 3** or **UWP** project. diff --git a/dlls/README.md b/dlls/README.md index e795c3ae..6e09e866 100644 --- a/dlls/README.md +++ b/dlls/README.md @@ -25,3 +25,11 @@ But we don't actually use the DLLs in any of the other projects; instead, we mus ### Summary * Shared projects give us the equivalent of C++ static libs. * The dlls directory enforces modularity. + +## LottieRuntime + +`LottieRuntime` is the exception to everything above. It is a real C++ DLL that an +application is meant to consume, rather than a modularity check, and it is not part +of `Lottie-Windows.sln` because it needs the C++ toolset and the FlatBuffers headers. +It reads the serialized form of a `WinCompData` graph and builds the composition +tree at runtime. See [LottieRuntime/README.md](LottieRuntime/README.md). diff --git a/source/LottieFlatbuffer/README.md b/source/LottieFlatbuffer/README.md new file mode 100644 index 00000000..d5547614 --- /dev/null +++ b/source/LottieFlatbuffer/README.md @@ -0,0 +1,75 @@ +# LottieFlatbuffer + +`lottie_comp.fbs` is the schema for the serialized form of a `WinCompData` graph. + +## What is serialized, and why that + +A Lottie file goes through several stages before anything is drawn: + +``` +Lottie JSON --> LottieData --> WinCompData graph --> composition tree +``` + +The interesting choice is which of those to serialize. The Lottie authoring format +would be the obvious answer, but it is the wrong one: reading it back still requires +the whole translator, which is by far the largest part of Lottie-Windows and the +part that changes most often. The `WinCompData` graph is much closer to what the +composition engine needs, so a reader for it is small, and it is a closed object +model, so a reader for it is *complete* rather than approximate. + +This is the same seam that the code generators use. `Instantiator.cs`, the C#, C++/CX +and C++/WinRT generators, and this format are all consumers of the same graph, which +is why they all produce the same visual tree. + +## Layout + +The graph is a DAG with sharing, so it is stored as a set of parallel node vectors, +one per category, and every reference is a `uint32` index into one of them. +`0xFFFFFFFF` means no node. Sharing therefore costs nothing: two references to the +same node are two copies of the same index. + +Three fields can point at more than one category — a reference parameter's target, a +property set's owner and a controller's target. Those pack the category into the top +four bits, so they are the only places a reader has to decode a category. Everywhere +else the field's type already says which vector to index. + +Strings are interned into a single vector and referenced by index, which is what +makes the format small: the graph is full of repeated property names such as +`Offset`, `Progress` and `TrimEnd`. + +Path geometry is flattened into a `[ubyte]` of opcodes and a `[float]` of operands +rather than a table per command, because a path can have thousands of commands and a +FlatBuffers table has a fixed overhead that would dominate. + +Serialization is deterministic: the same graph always produces the same bytes, so +output can be diffed and cached. + +## Versioning + +The rules are at the top of `lottie_comp.fbs`. In short: fields may only be added, +never removed or reordered, and `SCHEMA_VERSION` is bumped when they are. A reader +refuses a buffer whose version is higher than the one it was built for, because such +a buffer may carry meaning in fields the reader does not look at. + +## Regenerating + +`flatc` output for both C# and C++ is checked in, so contributors do not need `flatc` +installed. After changing the schema: + +```powershell +./build/RegenerateFlatbuffers.ps1 -Flatc +``` + +The `flatc` version **must** match the `Google.FlatBuffers` NuGet package version; +the generated C# calls a version-stamped method that only exists in the matching +package. Both are currently 25.2.10. + +## Consumers + +| | | +| --- | --- | +| `source/CompDataFlatbuffer/CompositionSerializer.cs` | Writes a graph | +| `source/CompDataFlatbuffer/CompositionDeserializer.cs` | Reads one back, in C# | +| `dlls/LottieRuntime` | Reads one back, in C++/WinRT | +| `LottieGen -Language flatbuffer` | Produces a `.lcomp` file | +| `tests/CompDataFlatbuffer.Tests` | Asserts a graph survives the round trip unchanged | From 5e3e9aa7860ca2dc084697498dbfe87a6512832d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 31 Aug 2026 18:54:46 +0000 Subject: [PATCH 12/39] Address code review feedback in glob parsing, indexer and coverage test Co-authored-by: jonwis <18537118+jonwis@users.noreply.github.com> --- source/CompDataFlatbuffer/GraphIndexer.cs | 4 ++-- source/LottieGen/Glob.cs | 6 ++++-- tests/CompDataFlatbuffer.Tests/CoverageTests.cs | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/source/CompDataFlatbuffer/GraphIndexer.cs b/source/CompDataFlatbuffer/GraphIndexer.cs index 20b56d93..7d5b6bcc 100644 --- a/source/CompDataFlatbuffer/GraphIndexer.cs +++ b/source/CompDataFlatbuffer/GraphIndexer.cs @@ -223,12 +223,12 @@ uint Index(Dictionary map, T? value) var index = map.Count; map.Add(value, index); - Add(value, index); + Add(value); return (uint)index; } // Appends a newly discovered node to its category list and walks its children. - void Add(object value, int index) + void Add(object value) { switch (value) { diff --git a/source/LottieGen/Glob.cs b/source/LottieGen/Glob.cs index e8ccd0c8..e28a958c 100644 --- a/source/LottieGen/Glob.cs +++ b/source/LottieGen/Glob.cs @@ -164,11 +164,13 @@ static Regex WildcardToRegex(string wildcard) directoryPath = $".{Path.DirectorySeparatorChar}"; } - if (directoryPath.EndsWith(":")) + if (directoryPath.Length == 2 && directoryPath[1] == ':' && char.IsLetter(directoryPath[0])) { // The directory path is just a drive letter with no directory. // Ensure it is treated as the current directory on the drive. - directoryPath += @".\"; + // A name that merely ends in ':' is not a drive letter, and on a + // platform where that is a legal directory name it must be left alone. + directoryPath += $".{Path.DirectorySeparatorChar}"; } // Ensure that we always have a trailing directory separator. Callers expect this. diff --git a/tests/CompDataFlatbuffer.Tests/CoverageTests.cs b/tests/CompDataFlatbuffer.Tests/CoverageTests.cs index 7dfd7f21..b06281cf 100644 --- a/tests/CompDataFlatbuffer.Tests/CoverageTests.cs +++ b/tests/CompDataFlatbuffer.Tests/CoverageTests.cs @@ -451,8 +451,9 @@ void Add(CompositionObject? o) Add(surfaceBrush.Surface as CompositionObject); break; case CompositionEffectBrush effectBrush: - Add(effectBrush.GetEffectFactory()); - foreach (var source in effectBrush.GetEffectFactory().Effect.Sources) + var effectFactory = effectBrush.GetEffectFactory(); + Add(effectFactory); + foreach (var source in effectFactory.Effect.Sources) { Add(effectBrush.GetSourceParameter(source.Name)); } From 53c3e34863e5f8fe82afcb52ddb8ed2db8a86b70 Mon Sep 17 00:00:00 2001 From: Jon Wiswall Date: Tue, 1 Sep 2026 09:37:39 -0700 Subject: [PATCH 13/39] Fix LottieRuntime C++/WinRT COM interop and build errors - Move #include before LottieRuntime.h (which pulls in C++/WinRT headers), satisfying the classic-COM interop requirement. - Use winrt::implements<> with the projected interface listed first for GeometrySource and Effect, so winrt::make<> returns a com_ptr convertible to CompositionPath / IGraphicsEffect directly. - Fix StrokeMiterLimit cast (was uint32_t, should be float). - Default-construct WinRT projection type caches with nullptr fill value; these types have no default constructor. - Link dxguid.lib for D2D1 effect CLSIDs (Composite, GaussianBlur). --- dlls/LottieRuntime/LottieRuntime.cpp | 80 +++++++++++------------- dlls/LottieRuntime/LottieRuntime.vcxproj | 2 +- 2 files changed, 39 insertions(+), 43 deletions(-) diff --git a/dlls/LottieRuntime/LottieRuntime.cpp b/dlls/LottieRuntime/LottieRuntime.cpp index b8368738..25cafeb3 100644 --- a/dlls/LottieRuntime/LottieRuntime.cpp +++ b/dlls/LottieRuntime/LottieRuntime.cpp @@ -31,6 +31,10 @@ // to ask what a node actually is, because the buffer already said, and the answer was // consumed at the moment of creation. +// must be included before any C++/WinRT header, and LottieRuntime.h +// pulls one in (winrt/Windows.UI.Composition.h), so it has to come first here. +#include + #include "LottieRuntime.h" #include @@ -161,26 +165,21 @@ namespace // and the only way to give one real content is the interop interface that hands // out a D2D geometry. This is the smallest object that does that. // --------------------------------------------------------------------------- - struct GeometrySource : winrt::implements< - GeometrySource, - winrt::Windows::Graphics::IGeometrySource2D, - ABI::Windows::Graphics::IGeometrySource2DInterop> + struct GeometrySource : winrt::implements { - explicit GeometrySource(winrt::com_ptr geometry) : + GeometrySource(winrt::com_ptr geometry) : m_geometry(std::move(geometry)) { } - IFACEMETHODIMP GetGeometry(ID2D1Geometry** value) noexcept + IFACEMETHODIMP GetGeometry(ID2D1Geometry** value) noexcept override { m_geometry.copy_to(value); return S_OK; } - IFACEMETHODIMP TryGetGeometryUsingFactory(ID2D1Factory*, ID2D1Geometry** value) noexcept + IFACEMETHODIMP TryGetGeometryUsingFactory(ID2D1Factory*, ID2D1Geometry** value) noexcept override { - // The geometry is device independent and was made by this component's own - // factory, so there is nothing useful to return for another factory. *value = nullptr; return E_NOTIMPL; } @@ -212,15 +211,8 @@ namespace // and what to set on it. Implementing that directly avoids taking a dependency // on Win2D for the two effects the translator emits. // --------------------------------------------------------------------------- - struct Effect : winrt::implements< - Effect, - winrt::Windows::Graphics::Effects::IGraphicsEffect, - winrt::Windows::Graphics::Effects::IGraphicsEffectSource, - ABI::Windows::Graphics::Effects::IGraphicsEffectD2D1Interop> + struct Effect : winrt::implements { - // Both effects the translator emits have exactly one property, and in both - // cases it is property zero, so one class covers both and there is no need - // for a property table. Effect(GUID const& id, winrt::Windows::Foundation::IInspectable property) : m_id(id), m_property(std::move(property)) @@ -242,19 +234,19 @@ namespace m_sources.push_back(source); } - IFACEMETHODIMP GetEffectId(GUID* id) noexcept + IFACEMETHODIMP GetEffectId(GUID* id) noexcept override { *id = m_id; return S_OK; } - IFACEMETHODIMP GetPropertyCount(UINT* count) noexcept + IFACEMETHODIMP GetPropertyCount(UINT* count) noexcept override { *count = 1; return S_OK; } - IFACEMETHODIMP GetProperty(UINT index, ABI::Windows::Foundation::IPropertyValue** value) noexcept + IFACEMETHODIMP GetProperty(UINT index, ABI::Windows::Foundation::IPropertyValue** value) noexcept override { *value = nullptr; @@ -263,17 +255,22 @@ namespace return E_BOUNDS; } - return m_property.as() - ->QueryInterface(__uuidof(ABI::Windows::Foundation::IPropertyValue), reinterpret_cast(value)); + auto pv = m_property.try_as(); + if (pv) + { + *value = pv.detach(); + return S_OK; + } + return E_NOINTERFACE; } - IFACEMETHODIMP GetSourceCount(UINT* count) noexcept + IFACEMETHODIMP GetSourceCount(UINT* count) noexcept override { *count = static_cast(m_sources.size()); return S_OK; } - IFACEMETHODIMP GetSource(UINT index, ABI::Windows::Graphics::Effects::IGraphicsEffectSource** source) noexcept + IFACEMETHODIMP GetSource(UINT index, ABI::Windows::Graphics::Effects::IGraphicsEffectSource** source) noexcept override { *source = nullptr; @@ -291,9 +288,8 @@ namespace IFACEMETHODIMP GetNamedPropertyMapping( LPCWSTR, UINT*, - ABI::Windows::Graphics::Effects::GRAPHICS_EFFECT_PROPERTY_MAPPING*) noexcept + ABI::Windows::Graphics::Effects::GRAPHICS_EFFECT_PROPERTY_MAPPING*) noexcept override { - // Nothing animates an effect property by name, so there is nothing to map. return E_INVALIDARG; } @@ -320,21 +316,21 @@ namespace Interpreter(Compositor const& compositor, Fb::LottieComposition const& root) : m_compositor(compositor), m_root(root), - m_visuals(Count(root.visuals())), - m_shapes(Count(root.shapes())), - m_geometries(Count(root.geometries())), - m_paths(Count(root.canvas_geometries())), - m_brushes(Count(root.brushes())), - m_gradientStops(Count(root.gradient_stops())), - m_viewBoxes(Count(root.view_boxes())), - m_clips(Count(root.clips())), - m_shadows(Count(root.shadows())), - m_surfaces(Count(root.surfaces())), - m_effects(Count(root.effects())), - m_easings(Count(root.easings())), - m_animations(Count(root.animations())), - m_propertySets(Count(root.property_sets())), - m_controllers(Count(root.controllers())) + m_visuals(Count(root.visuals()), nullptr), + m_shapes(Count(root.shapes()), nullptr), + m_geometries(Count(root.geometries()), nullptr), + m_paths(Count(root.canvas_geometries()), nullptr), + m_brushes(Count(root.brushes()), nullptr), + m_gradientStops(Count(root.gradient_stops()), nullptr), + m_viewBoxes(Count(root.view_boxes()), nullptr), + m_clips(Count(root.clips()), nullptr), + m_shadows(Count(root.shadows()), nullptr), + m_surfaces(Count(root.surfaces()), nullptr), + m_effects(Count(root.effects()), nullptr), + m_easings(Count(root.easings()), nullptr), + m_animations(Count(root.animations()), nullptr), + m_propertySets(Count(root.property_sets()), nullptr), + m_controllers(Count(root.controllers()), nullptr) { } @@ -913,7 +909,7 @@ namespace if (auto value = source->stroke_miter_limit()) { - target.StrokeMiterLimit(static_cast(*value)); + target.StrokeMiterLimit(static_cast(*value)); } if (auto value = source->stroke_thickness()) diff --git a/dlls/LottieRuntime/LottieRuntime.vcxproj b/dlls/LottieRuntime/LottieRuntime.vcxproj index 846e7c15..10d16e61 100644 --- a/dlls/LottieRuntime/LottieRuntime.vcxproj +++ b/dlls/LottieRuntime/LottieRuntime.vcxproj @@ -60,7 +60,7 @@ true - d2d1.lib;windowsapp.lib;%(AdditionalDependencies) + d2d1.lib;dxguid.lib;windowsapp.lib;%(AdditionalDependencies) true From ebce829ccdc85bd969a53bfd7edd048b0fa499ed Mon Sep 17 00:00:00 2001 From: Jon Wiswall Date: Tue, 1 Sep 2026 09:48:20 -0700 Subject: [PATCH 14/39] Add LottieRuntimeDump sample tool A console tool that links LottieRuntime.vcxproj, loads a serialized Lottie composition (.lcomp) given as its command-line argument, and prints the composition object hierarchy of the resulting visual tree: visuals, their shapes (recursively through container shapes), fill and stroke brushes, and gradient stops. Each line shows the runtime class name and, where set, the Comment property, so the printed tree can be matched back to the source Lottie layers. Builds via project reference to LottieRuntime.vcxproj and copies LottieRuntime.dll next to the exe as a post-build step, since C++ project references do not do this automatically. --- dlls/LottieRuntimeDump/LottieRuntimeDump.cpp | 200 ++++++++++++++++++ .../LottieRuntimeDump.vcxproj | 74 +++++++ 2 files changed, 274 insertions(+) create mode 100644 dlls/LottieRuntimeDump/LottieRuntimeDump.cpp create mode 100644 dlls/LottieRuntimeDump/LottieRuntimeDump.vcxproj diff --git a/dlls/LottieRuntimeDump/LottieRuntimeDump.cpp b/dlls/LottieRuntimeDump/LottieRuntimeDump.cpp new file mode 100644 index 00000000..081c961a --- /dev/null +++ b/dlls/LottieRuntimeDump/LottieRuntimeDump.cpp @@ -0,0 +1,200 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// Sample tool: loads a serialized Lottie composition (a .lcomp FlatBuffer produced +// by LottieGen -Language flatbuffer or CompositionSerializer) through LottieRuntime, +// then prints the composition object hierarchy of the resulting visual tree. +// +// Usage: LottieRuntimeDump.exe + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +using namespace winrt; +using namespace winrt::Windows::UI::Composition; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; + +namespace +{ + std::vector ReadFileBytes(wchar_t const* path) + { + std::ifstream file(path, std::ios::binary | std::ios::ate); + if (!file) + { + throw std::runtime_error("Could not open input file."); + } + + auto const size = static_cast(file.tellg()); + std::vector result(size); + file.seekg(0); + file.read(reinterpret_cast(result.data()), static_cast(size)); + return result; + } + + void PrintLine(int depth, std::wstring_view text) + { + std::wstring indent(static_cast(depth) * 2, L' '); + std::wcout << indent << text << L"\n"; + } + + hstring RuntimeClassName(IInspectable const& node) + { + if (node == nullptr) + { + return L"(null)"; + } + + return winrt::get_class_name(node); + } + + hstring CommentOf(CompositionObject const& node) + { + auto comment = node.Comment(); + return comment.empty() ? L"" : (L" \"" + comment + L"\""); + } + + void DumpBrush(CompositionBrush const& brush, int depth); + void DumpVisual(Visual const& visual, int depth); + void DumpShape(CompositionShape const& shape, int depth); + + void DumpShape(CompositionShape const& shape, int depth) + { + if (shape == nullptr) + { + return; + } + + PrintLine(depth, RuntimeClassName(shape) + CommentOf(shape)); + + if (auto containerShape = shape.try_as()) + { + for (auto const& child : containerShape.Shapes()) + { + DumpShape(child, depth + 1); + } + } + + if (auto spriteShape = shape.try_as()) + { + if (auto fill = spriteShape.FillBrush()) + { + PrintLine(depth + 1, L"FillBrush:"); + DumpBrush(fill, depth + 2); + } + + if (auto stroke = spriteShape.StrokeBrush()) + { + PrintLine(depth + 1, L"StrokeBrush:"); + DumpBrush(stroke, depth + 2); + } + } + } + + void DumpBrush(CompositionBrush const& brush, int depth) + { + if (brush == nullptr) + { + return; + } + + PrintLine(depth, RuntimeClassName(brush) + CommentOf(brush)); + + if (auto linearGradient = brush.try_as()) + { + for (auto const& stop : linearGradient.ColorStops()) + { + PrintLine(depth + 1, L"ColorStop " + RuntimeClassName(stop)); + } + } + } + + void DumpVisual(Visual const& visual, int depth) + { + if (visual == nullptr) + { + return; + } + + PrintLine(depth, RuntimeClassName(visual) + CommentOf(visual)); + + if (auto spriteVisual = visual.try_as()) + { + if (auto brush = spriteVisual.Brush()) + { + PrintLine(depth + 1, L"Brush:"); + DumpBrush(brush, depth + 2); + } + + if (auto shapeVisual = visual.try_as()) + { + for (auto const& shape : shapeVisual.Shapes()) + { + DumpShape(shape, depth + 1); + } + } + } + else if (auto shapeVisual = visual.try_as()) + { + for (auto const& shape : shapeVisual.Shapes()) + { + DumpShape(shape, depth + 1); + } + } + + if (auto containerVisual = visual.try_as()) + { + for (auto const& child : containerVisual.Children()) + { + DumpVisual(child, depth + 1); + } + } + } +} + +int wmain(int argc, wchar_t* argv[]) +{ + winrt::init_apartment(); + + if (argc != 2) + { + std::wcerr << L"Usage: LottieRuntimeDump.exe \n"; + return 1; + } + + try + { + auto bytes = ReadFileBytes(argv[1]); + + Compositor compositor; + auto root = CommunityToolkit::WinUI::Lottie::LoadComposition( + compositor, + std::span(bytes.data(), bytes.size())); + + std::wcout << L"Composition object hierarchy:\n"; + DumpVisual(root, 0); + } + catch (winrt::hresult_error const& e) + { + std::wcerr << L"Failed to load composition: " << e.message().c_str() << L"\n"; + return 1; + } + catch (std::exception const& e) + { + std::cerr << "Failed: " << e.what() << "\n"; + return 1; + } + + return 0; +} diff --git a/dlls/LottieRuntimeDump/LottieRuntimeDump.vcxproj b/dlls/LottieRuntimeDump/LottieRuntimeDump.vcxproj new file mode 100644 index 00000000..33e8a74c --- /dev/null +++ b/dlls/LottieRuntimeDump/LottieRuntimeDump.vcxproj @@ -0,0 +1,74 @@ + + + + + Debug + x64 + + + Release + x64 + + + Debug + ARM64 + + + Release + ARM64 + + + + + {9A7B4C2E-1F3D-4B7A-9C3E-2D5A8F0B2E48} + LottieRuntimeDump + Application + v143 + Unicode + 10.0.22621.0 + 10.0.17763.0 + + + + + + + true + false + true + + + + + stdcpp20 + WIN32_LEAN_AND_MEAN;NOMINMAX;%(PreprocessorDefinitions) + $(MSBuildThisFileDirectory)..\LottieRuntime;%(AdditionalIncludeDirectories) + Level4 + true + Sync + + + Console + windowsapp.lib;%(AdditionalDependencies) + + + + + + + + + + {6E0A5B4C-2F1D-4B7A-9C3E-1D5A8F0B2E47} + + + + + + + + + + + + From 4e2ac80f634312b41d53ab374144f659b6d58704 Mon Sep 17 00:00:00 2001 From: Jon Wiswall Date: Tue, 1 Sep 2026 10:53:50 -0700 Subject: [PATCH 15/39] Add classic COM loader interface for LottieRuntime Defines ILottieCompositionLoader (IID 3874B71B-05E6-42A9-B838-1AE7F0BA4E1C), a classic COM interface accepting (UINT32 length, BYTE const* buffer, REFIID, void**) that wraps LoadComposition for callers without a C++/WinRT toolchain. Implements it as LottieCompositionLoader (CLSID 9F7B7089-F512-4BBC-81FE-4FF12B4DA159) via winrt::implements<>, plus DllGetClassObject/DllCanUnloadNow exports and a LottieRuntime.def to keep those names undecorated. The class is never registered with the system: LottieRuntime.manifest is a private-assembly manifest declaring the comClass, copied next to a consuming exe and named directly by an ACTCTX, so CoCreateInstance resolves the CLSID without any HKEY_CLASSES_ROOT entry. Adds LottieRuntimeComTest, a console harness that builds an activation context from that manifest, activates it, CoCreateInstances the loader, and calls LoadComposition on a file argument. Known issue: CoCreateInstance currently returns E_ACCESSDENIED (0x80070005) even though CreateActCtx/ActivateActCtx succeed and the DLL exports DllGetClassObject/DllCanUnloadNow correctly - the private-assembly activation path needs further debugging before this is fully working end-to-end. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 58fac82f-0489-4c57-a18d-2b7ba273faa6 --- dlls/LottieRuntime/LottieRuntime.def | 6 + dlls/LottieRuntime/LottieRuntime.manifest | 20 +++ dlls/LottieRuntime/LottieRuntime.vcxproj | 20 +++ dlls/LottieRuntime/LottieRuntimeCom.cpp | 123 +++++++++++++++ dlls/LottieRuntime/LottieRuntimeCom.h | 38 +++++ .../LottieRuntimeComTest.cpp | 140 ++++++++++++++++++ .../LottieRuntimeComTest.vcxproj | 88 +++++++++++ 7 files changed, 435 insertions(+) create mode 100644 dlls/LottieRuntime/LottieRuntime.def create mode 100644 dlls/LottieRuntime/LottieRuntime.manifest create mode 100644 dlls/LottieRuntime/LottieRuntimeCom.cpp create mode 100644 dlls/LottieRuntime/LottieRuntimeCom.h create mode 100644 dlls/LottieRuntimeComTest/LottieRuntimeComTest.cpp create mode 100644 dlls/LottieRuntimeComTest/LottieRuntimeComTest.vcxproj diff --git a/dlls/LottieRuntime/LottieRuntime.def b/dlls/LottieRuntime/LottieRuntime.def new file mode 100644 index 00000000..7c435ed5 --- /dev/null +++ b/dlls/LottieRuntime/LottieRuntime.def @@ -0,0 +1,6 @@ +; Classic COM entry points must have undecorated names for CoCreateInstance's +; loader (and for the test's GetProcAddress-based activation) to find them. +LIBRARY LottieRuntime +EXPORTS + DllGetClassObject PRIVATE + DllCanUnloadNow PRIVATE diff --git a/dlls/LottieRuntime/LottieRuntime.manifest b/dlls/LottieRuntime/LottieRuntime.manifest new file mode 100644 index 00000000..95945106 --- /dev/null +++ b/dlls/LottieRuntime/LottieRuntime.manifest @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/dlls/LottieRuntime/LottieRuntime.vcxproj b/dlls/LottieRuntime/LottieRuntime.vcxproj index 10d16e61..b9ef90ff 100644 --- a/dlls/LottieRuntime/LottieRuntime.vcxproj +++ b/dlls/LottieRuntime/LottieRuntime.vcxproj @@ -61,6 +61,7 @@ d2d1.lib;dxguid.lib;windowsapp.lib;%(AdditionalDependencies) + LottieRuntime.def true @@ -70,12 +71,31 @@ + + + + + + + + + + false + + + diff --git a/dlls/LottieRuntime/LottieRuntimeCom.cpp b/dlls/LottieRuntime/LottieRuntimeCom.cpp new file mode 100644 index 00000000..9bb953b7 --- /dev/null +++ b/dlls/LottieRuntime/LottieRuntimeCom.cpp @@ -0,0 +1,123 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// The classic COM entry point declared in LottieRuntimeCom.h. This is the only +// translation unit that needs to know both LoadComposition and classic COM, so it +// stays out of LottieRuntime.cpp. + +#include + +#include "LottieRuntimeCom.h" +#include "LottieRuntime.h" + +#include +#include + +using namespace winrt::Windows::UI::Composition; + + +namespace +{ + // The loader owns the Compositor it hands nodes out of. A caller that wants to + // parent the resulting Visual into its own tree does so through the WinRT + // interfaces it queried result for; it never needs to supply a Compositor of + // its own, because there is nothing it could usefully do with one that this + // object does not already do internally. + struct LottieCompositionLoader : winrt::implements + { + STDMETHODIMP LoadComposition(UINT32 length, BYTE const* buffer, REFIID riid, void** result) noexcept override + { + if (result == nullptr) + { + return E_POINTER; + } + + *result = nullptr; + + if (buffer == nullptr && length != 0) + { + return E_INVALIDARG; + } + + try + { + auto const span = std::span(reinterpret_cast(buffer), length); + auto const visual = CommunityToolkit::WinUI::Lottie::LoadComposition(m_compositor, span); + return visual.as<::IUnknown>()->QueryInterface(riid, result); + } + catch (...) + { + return winrt::to_hresult(); + } + } + + private: + Compositor m_compositor; + }; + + // A minimal class factory: this DLL only ever creates LottieCompositionLoader, + // so there is no per-CLSID dispatch to do. + struct ClassFactory : winrt::implements + { + STDMETHODIMP CreateInstance(::IUnknown* outer, REFIID riid, void** result) noexcept override + { + if (result == nullptr) + { + return E_POINTER; + } + + *result = nullptr; + + if (outer != nullptr) + { + return CLASS_E_NOAGGREGATION; + } + + try + { + return winrt::make()->QueryInterface(riid, result); + } + catch (...) + { + return winrt::to_hresult(); + } + } + + STDMETHODIMP LockServer(BOOL) noexcept override + { + return S_OK; + } + }; +} + +extern "C" HRESULT __stdcall DllGetClassObject(REFCLSID rclsid, REFIID riid, void** result) +{ + if (result == nullptr) + { + return E_POINTER; + } + + *result = nullptr; + + if (rclsid != CLSID_LottieCompositionLoader) + { + return CLASS_E_CLASSNOTAVAILABLE; + } + + try + { + return winrt::make()->QueryInterface(riid, result); + } + catch (...) + { + return winrt::to_hresult(); + } +} + +extern "C" HRESULT __stdcall DllCanUnloadNow() +{ + // The loader has no process-wide state, so it is always safe to unload; the + // reference-counted objects it hands out keep the DLL alive on their own. + return S_OK; +} diff --git a/dlls/LottieRuntime/LottieRuntimeCom.h b/dlls/LottieRuntime/LottieRuntimeCom.h new file mode 100644 index 00000000..a08a4840 --- /dev/null +++ b/dlls/LottieRuntime/LottieRuntimeCom.h @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#pragma once + +#include +#include + +// A classic (non-WinRT) COM entry point for LoadComposition, for callers that only +// have a byte buffer and a COM activation story, rather than a Compositor and a +// C++/WinRT toolchain. It is not registered with the system: it is meant to be +// activated registration-free, through an application manifest that names +// LottieRuntime.dll and this CLSID (see the "Isolated applications and side-by-side +// assemblies" COM activation model). +// +// The object it produces is the root Visual of the composition, queried for +// whatever interface the caller asked for through riid - typically IInspectable, +// ABI::Windows::UI::Composition::IVisual, or IUnknown. +MIDL_INTERFACE("3874B71B-05E6-42A9-B838-1AE7F0BA4E1C") +ILottieCompositionLoader : public IUnknown +{ +public: + // Parses buffer (the output of LottieGen -Language flatbuffer, or equivalently + // CompositionSerializer) and produces the root Visual of the resulting tree. + // + // The buffer is treated as untrusted: it is verified before it is read, so a + // malformed buffer produces a failure HRESULT rather than undefined behaviour. + STDMETHOD(LoadComposition)(UINT32 length, BYTE const* buffer, REFIID riid, void** result) = 0; +}; + +// A caller activating this class registration-free never links against +// LottieRuntime.dll's import library (there is no exported symbol to link +// against for an in-proc COM server resolved purely through an activation +// context), so the CLSID is a compile-time constant here rather than an +// extern declaration. +constexpr CLSID CLSID_LottieCompositionLoader = { + 0x9f7b7089, 0xf512, 0x4bbc, { 0x81, 0xfe, 0x4f, 0xf1, 0x2b, 0x4d, 0xa1, 0x59 } }; diff --git a/dlls/LottieRuntimeComTest/LottieRuntimeComTest.cpp b/dlls/LottieRuntimeComTest/LottieRuntimeComTest.cpp new file mode 100644 index 00000000..50de323f --- /dev/null +++ b/dlls/LottieRuntimeComTest/LottieRuntimeComTest.cpp @@ -0,0 +1,140 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// Exercises the classic COM entry point declared in LottieRuntimeCom.h. +// +// LottieRuntime.dll is never registered with the system: its CLSID is only ever +// known through LottieRuntime.manifest, its own private-assembly manifest, copied +// next to this exe at build time. This test builds an activation context directly +// out of that manifest - the same shape a real isolated application uses when it +// carries a private assembly instead of registering a DLL - and activates it for +// the duration of the CoCreateInstance call, which is the reason CoCreateInstance +// resolves the CLSID without anything ever touching HKEY_CLASSES_ROOT. + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "LottieRuntimeCom.h" + +namespace +{ + std::vector ReadCompositionFile(wchar_t const* path) + { + HANDLE file = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (file == INVALID_HANDLE_VALUE) + { + throw std::runtime_error("Could not open the composition file."); + } + + LARGE_INTEGER size{}; + GetFileSizeEx(file, &size); + + std::vector buffer(static_cast(size.QuadPart)); + DWORD read = 0; + BOOL const ok = ::ReadFile(file, buffer.data(), static_cast(buffer.size()), &read, nullptr); + CloseHandle(file); + + if (!ok || read != buffer.size()) + { + throw std::runtime_error("Could not read the composition file."); + } + + return buffer; + } +} + +int wmain(int argc, wchar_t* argv[]) +{ + if (argc != 2) + { + fwprintf(stderr, L"Usage: LottieRuntimeComTest \n"); + return 1; + } + + try + { + winrt::check_hresult(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED)); + + // The manifest is a private assembly for this exe: it declares the CLSID + // directly rather than through a level of indirection, so it is loaded + // straight into the activation context as the primary manifest, with no + // separate application manifest and no HKEY_CLASSES_ROOT registration. + wchar_t moduleDir[MAX_PATH]{}; + GetModuleFileNameW(nullptr, moduleDir, MAX_PATH); + PathRemoveFileSpecW(moduleDir); + + std::wstring const manifestPath = std::wstring(moduleDir) + L"\\LottieRuntime.manifest"; + + ACTCTXW actCtx{ sizeof(ACTCTXW) }; + actCtx.lpSource = manifestPath.c_str(); + actCtx.lpAssemblyDirectory = moduleDir; + actCtx.dwFlags = ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID; + + HANDLE const hActCtx = CreateActCtxW(&actCtx); + if (hActCtx == INVALID_HANDLE_VALUE) + { + fwprintf(stderr, L"CreateActCtx failed: %lu (manifest: %s, dir: %s)\n", GetLastError(), manifestPath.c_str(), moduleDir); + return 1; + } + + ULONG_PTR cookie = 0; + ActivateActCtx(hActCtx, &cookie); + + winrt::com_ptr loader; + HRESULT const hr = CoCreateInstance( + CLSID_LottieCompositionLoader, + nullptr, + CLSCTX_INPROC_SERVER, + __uuidof(ILottieCompositionLoader), + loader.put_void()); + + DeactivateActCtx(0, cookie); + ReleaseActCtx(hActCtx); + + if (FAILED(hr)) + { + fwprintf(stderr, L"CoCreateInstance failed (registration-free activation did not resolve the CLSID): 0x%08X (last error %lu)\n", hr, GetLastError()); + return 1; + } + + auto const buffer = ReadCompositionFile(argv[1]); + + winrt::com_ptr<::IUnknown> root; + HRESULT const loadHr = loader->LoadComposition( + static_cast(buffer.size()), + reinterpret_cast(buffer.data()), + __uuidof(::IUnknown), + root.put_void()); + + if (FAILED(loadHr)) + { + fwprintf(stderr, L"LoadComposition failed: 0x%08X\n", loadHr); + return 1; + } + + wprintf(L"Registration-free activation succeeded; loaded a composition with a root object at %p.\n", root.get()); + return 0; + } + catch (winrt::hresult_error const& e) + { + fwprintf(stderr, L"%s\n", e.message().c_str()); + return 1; + } + catch (std::exception const& e) + { + fprintf(stderr, "%s\n", e.what()); + return 1; + } +} diff --git a/dlls/LottieRuntimeComTest/LottieRuntimeComTest.vcxproj b/dlls/LottieRuntimeComTest/LottieRuntimeComTest.vcxproj new file mode 100644 index 00000000..9ca4357c --- /dev/null +++ b/dlls/LottieRuntimeComTest/LottieRuntimeComTest.vcxproj @@ -0,0 +1,88 @@ + + + + + Debug + x64 + + + Release + x64 + + + Debug + ARM64 + + + Release + ARM64 + + + + + {2B6C4A1E-7F3D-4B7A-9C3E-3D5A8F0B2E49} + LottieRuntimeComTest + Application + v143 + Unicode + 10.0.22621.0 + 10.0.17763.0 + + + false + + + + + + + true + false + true + + + + + stdcpp20 + WIN32_LEAN_AND_MEAN;NOMINMAX;%(PreprocessorDefinitions) + $(MSBuildThisFileDirectory)..\LottieRuntime;%(AdditionalIncludeDirectories) + Level4 + true + Sync + + + Console + windowsapp.lib;shlwapi.lib;ole32.lib;%(AdditionalDependencies) + false + + + + + + + + + + {6E0A5B4C-2F1D-4B7A-9C3E-1D5A8F0B2E47} + + + + + + + + + + + + + + + + From 75d048b10d5d28773de040cc8a3560610923a397 Mon Sep 17 00:00:00 2001 From: Jon Wiswall Date: Tue, 1 Sep 2026 11:42:48 -0700 Subject: [PATCH 16/39] Initialize DispatcherQueue for COM loader Create the desktop DispatcherQueue lazily before activating the loader's Compositor. This keeps CoCreateInstance side-effect-free and gives Windows.UI.Composition the thread infrastructure it requires. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 58fac82f-0489-4c57-a18d-2b7ba273faa6 --- dlls/LottieRuntime/LottieRuntime.vcxproj | 2 +- dlls/LottieRuntime/LottieRuntimeCom.cpp | 31 +++++++++++++++++++----- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/dlls/LottieRuntime/LottieRuntime.vcxproj b/dlls/LottieRuntime/LottieRuntime.vcxproj index b9ef90ff..afa1301e 100644 --- a/dlls/LottieRuntime/LottieRuntime.vcxproj +++ b/dlls/LottieRuntime/LottieRuntime.vcxproj @@ -60,7 +60,7 @@ true - d2d1.lib;dxguid.lib;windowsapp.lib;%(AdditionalDependencies) + CoreMessaging.lib;d2d1.lib;dxguid.lib;windowsapp.lib;%(AdditionalDependencies) LottieRuntime.def diff --git a/dlls/LottieRuntime/LottieRuntimeCom.cpp b/dlls/LottieRuntime/LottieRuntimeCom.cpp index 9bb953b7..2f531373 100644 --- a/dlls/LottieRuntime/LottieRuntimeCom.cpp +++ b/dlls/LottieRuntime/LottieRuntimeCom.cpp @@ -11,7 +11,10 @@ #include "LottieRuntimeCom.h" #include "LottieRuntime.h" +#include + #include +#include #include using namespace winrt::Windows::UI::Composition; @@ -19,11 +22,8 @@ using namespace winrt::Windows::UI::Composition; namespace { - // The loader owns the Compositor it hands nodes out of. A caller that wants to - // parent the resulting Visual into its own tree does so through the WinRT - // interfaces it queried result for; it never needs to supply a Compositor of - // its own, because there is nothing it could usefully do with one that this - // object does not already do internally. + // The loader creates its Compositor on the first load. Desktop threads need a + // DispatcherQueue before Windows.UI.Composition can activate a Compositor. struct LottieCompositionLoader : winrt::implements { STDMETHODIMP LoadComposition(UINT32 length, BYTE const* buffer, REFIID riid, void** result) noexcept override @@ -42,6 +42,24 @@ namespace try { + if (!m_compositor) + { + if (!winrt::Windows::System::DispatcherQueue::GetForCurrentThread()) + { + DispatcherQueueOptions const options{ + sizeof(DispatcherQueueOptions), + DQTYPE_THREAD_CURRENT, + DQTAT_COM_NONE, + }; + + winrt::check_hresult(CreateDispatcherQueueController( + options, + reinterpret_cast(winrt::put_abi(m_dispatcherQueueController)))); + } + + m_compositor = Compositor(); + } + auto const span = std::span(reinterpret_cast(buffer), length); auto const visual = CommunityToolkit::WinUI::Lottie::LoadComposition(m_compositor, span); return visual.as<::IUnknown>()->QueryInterface(riid, result); @@ -53,7 +71,8 @@ namespace } private: - Compositor m_compositor; + winrt::Windows::System::DispatcherQueueController m_dispatcherQueueController{ nullptr }; + Compositor m_compositor{ nullptr }; }; // A minimal class factory: this DLL only ever creates LottieCompositionLoader, From c2a9e3663f4d570c92ff3cd305586e99bbf8016c Mon Sep 17 00:00:00 2001 From: Jon Wiswall Date: Tue, 1 Sep 2026 11:44:07 -0700 Subject: [PATCH 17/39] Track LottieRuntime COM server lifetime Use C++/WinRT's module lock for IClassFactory::LockServer and DllCanUnloadNow so COM cannot unload the DLL while its objects remain alive. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 58fac82f-0489-4c57-a18d-2b7ba273faa6 --- dlls/LottieRuntime/LottieRuntimeCom.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/dlls/LottieRuntime/LottieRuntimeCom.cpp b/dlls/LottieRuntime/LottieRuntimeCom.cpp index 2f531373..dc664301 100644 --- a/dlls/LottieRuntime/LottieRuntimeCom.cpp +++ b/dlls/LottieRuntime/LottieRuntimeCom.cpp @@ -103,8 +103,17 @@ namespace } } - STDMETHODIMP LockServer(BOOL) noexcept override + STDMETHODIMP LockServer(BOOL lock) noexcept override { + if (lock) + { + ++winrt::get_module_lock(); + } + else + { + --winrt::get_module_lock(); + } + return S_OK; } }; @@ -136,7 +145,5 @@ extern "C" HRESULT __stdcall DllGetClassObject(REFCLSID rclsid, REFIID riid, voi extern "C" HRESULT __stdcall DllCanUnloadNow() { - // The loader has no process-wide state, so it is always safe to unload; the - // reference-counted objects it hands out keep the DLL alive on their own. - return S_OK; + return winrt::get_module_lock() == 0 ? S_OK : S_FALSE; } From 6bc9cc7d31dc5347ebf7c84cd0204c13201ec7f3 Mon Sep 17 00:00:00 2001 From: Jon Wiswall Date: Tue, 1 Sep 2026 11:44:20 -0700 Subject: [PATCH 18/39] Add LottieRuntime COM trace collector Build the COM test, record it under Time Travel Debugging, and collect optional SideBySide and COM event logs into one directory. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 58fac82f-0489-4c57-a18d-2b7ba273faa6 --- tools/collect-lottieruntime-cocreate-ttd.ps1 | 38 ++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 tools/collect-lottieruntime-cocreate-ttd.ps1 diff --git a/tools/collect-lottieruntime-cocreate-ttd.ps1 b/tools/collect-lottieruntime-cocreate-ttd.ps1 new file mode 100644 index 00000000..4c494585 --- /dev/null +++ b/tools/collect-lottieruntime-cocreate-ttd.ps1 @@ -0,0 +1,38 @@ +param( + [string]$VsDevShell = "C:\Program Files\Microsoft Visual Studio\18\Professional\Common7\Tools\Launch-VsDevShell.ps1", + [string]$Config = "Debug", + [string]$Platform = "x64", + [string]$TraceDir = ".\trace", + [string]$RunName = "LottieRuntimeComTest_E_ACCESSDENIED.run", + [string]$InputPath = ".\nonexistent.bin", + [string]$TtTracer = "tttracer.exe" +) + +$ErrorActionPreference = "Stop" + +$RepoRoot = Split-Path -Parent $PSScriptRoot +Set-Location $RepoRoot + +& $VsDevShell -SkipAutomaticLocation + +msbuild "dlls\LottieRuntimeComTest\LottieRuntimeComTest.vcxproj" ` + /p:Platform=$Platform ` + /p:Configuration=$Config ` + /t:Rebuild ` + /nologo ` + /v:minimal + +New-Item -ItemType Directory -Force -Path $TraceDir | Out-Null + +$ExePath = ".\dlls\LottieRuntimeComTest\$Platform\$Config\LottieRuntimeComTest.exe" +$RunPath = Join-Path $TraceDir $RunName + +& $TtTracer -out $RunPath $ExePath $InputPath + +wevtutil qe Application /q:"*[System[Provider[@Name='Microsoft-Windows-SideBySide']]]" /c:30 /rd:true /f:text > (Join-Path $TraceDir "sxs-events.txt") +wevtutil qe Application /q:"*[System[Provider[@Name='Microsoft-Windows-COMRuntime']]]" /c:30 /rd:true /f:text > (Join-Path $TraceDir "com-events.txt") + +Write-Host "Done." +Write-Host "Trace: $RunPath" +Write-Host "SxS: $(Join-Path $TraceDir 'sxs-events.txt')" +Write-Host "COM: $(Join-Path $TraceDir 'com-events.txt')" From 9449edd4317e92756b7affa5197c553c75e3e331 Mon Sep 17 00:00:00 2001 From: Jon Wiswall Date: Tue, 1 Sep 2026 11:45:42 -0700 Subject: [PATCH 19/39] Remove temporary COM trace collector Remove the one-off investigation helper now that the activation failure is diagnosed. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 58fac82f-0489-4c57-a18d-2b7ba273faa6 --- tools/collect-lottieruntime-cocreate-ttd.ps1 | 38 -------------------- 1 file changed, 38 deletions(-) delete mode 100644 tools/collect-lottieruntime-cocreate-ttd.ps1 diff --git a/tools/collect-lottieruntime-cocreate-ttd.ps1 b/tools/collect-lottieruntime-cocreate-ttd.ps1 deleted file mode 100644 index 4c494585..00000000 --- a/tools/collect-lottieruntime-cocreate-ttd.ps1 +++ /dev/null @@ -1,38 +0,0 @@ -param( - [string]$VsDevShell = "C:\Program Files\Microsoft Visual Studio\18\Professional\Common7\Tools\Launch-VsDevShell.ps1", - [string]$Config = "Debug", - [string]$Platform = "x64", - [string]$TraceDir = ".\trace", - [string]$RunName = "LottieRuntimeComTest_E_ACCESSDENIED.run", - [string]$InputPath = ".\nonexistent.bin", - [string]$TtTracer = "tttracer.exe" -) - -$ErrorActionPreference = "Stop" - -$RepoRoot = Split-Path -Parent $PSScriptRoot -Set-Location $RepoRoot - -& $VsDevShell -SkipAutomaticLocation - -msbuild "dlls\LottieRuntimeComTest\LottieRuntimeComTest.vcxproj" ` - /p:Platform=$Platform ` - /p:Configuration=$Config ` - /t:Rebuild ` - /nologo ` - /v:minimal - -New-Item -ItemType Directory -Force -Path $TraceDir | Out-Null - -$ExePath = ".\dlls\LottieRuntimeComTest\$Platform\$Config\LottieRuntimeComTest.exe" -$RunPath = Join-Path $TraceDir $RunName - -& $TtTracer -out $RunPath $ExePath $InputPath - -wevtutil qe Application /q:"*[System[Provider[@Name='Microsoft-Windows-SideBySide']]]" /c:30 /rd:true /f:text > (Join-Path $TraceDir "sxs-events.txt") -wevtutil qe Application /q:"*[System[Provider[@Name='Microsoft-Windows-COMRuntime']]]" /c:30 /rd:true /f:text > (Join-Path $TraceDir "com-events.txt") - -Write-Host "Done." -Write-Host "Trace: $RunPath" -Write-Host "SxS: $(Join-Path $TraceDir 'sxs-events.txt')" -Write-Host "COM: $(Join-Path $TraceDir 'com-events.txt')" From 76617d8bf29c1db8955e0933b67b8f36bcd3c0c4 Mon Sep 17 00:00:00 2001 From: Jon Wiswall Date: Tue, 1 Sep 2026 14:05:28 -0700 Subject: [PATCH 20/39] Fix interpreter realization gaps Handle omitted property-set state, expose the required effect-source interface, cache and cycle-check canvas geometries and strings, preserve sprite shadows, and load URI and stream image surfaces. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 58fac82f-0489-4c57-a18d-2b7ba273faa6 --- .gitmodules | 3 + dlls/LottieRuntime/LottieRuntime.cpp | 132 ++++++++++++++++++++++++--- external/flatbuffers | 1 + 3 files changed, 121 insertions(+), 15 deletions(-) create mode 100644 .gitmodules create mode 160000 external/flatbuffers diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..d67d60a6 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "external/flatbuffers"] + path = external/flatbuffers + url = https://github.com/google/flatbuffers.git diff --git a/dlls/LottieRuntime/LottieRuntime.cpp b/dlls/LottieRuntime/LottieRuntime.cpp index 25cafeb3..f2d55262 100644 --- a/dlls/LottieRuntime/LottieRuntime.cpp +++ b/dlls/LottieRuntime/LottieRuntime.cpp @@ -51,9 +51,13 @@ #include #include #include +#include #include #include +#undef GetCurrentTime +#include + #include "Generated/lottie_comp_generated.h" namespace Fb = CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema; @@ -211,7 +215,11 @@ namespace // and what to set on it. Implementing that directly avoids taking a dependency // on Win2D for the two effects the translator emits. // --------------------------------------------------------------------------- - struct Effect : winrt::implements + struct Effect : winrt::implements< + Effect, + winrt::Windows::Graphics::Effects::IGraphicsEffect, + winrt::Windows::Graphics::Effects::IGraphicsEffectSource, + ABI::Windows::Graphics::Effects::IGraphicsEffectD2D1Interop> { Effect(GUID const& id, winrt::Windows::Foundation::IInspectable property) : m_id(id), @@ -316,10 +324,14 @@ namespace Interpreter(Compositor const& compositor, Fb::LottieComposition const& root) : m_compositor(compositor), m_root(root), + m_strings(Count(root.strings())), + m_stringCached(Count(root.strings())), m_visuals(Count(root.visuals()), nullptr), m_shapes(Count(root.shapes()), nullptr), m_geometries(Count(root.geometries()), nullptr), m_paths(Count(root.canvas_geometries()), nullptr), + m_canvasGeometries(Count(root.canvas_geometries())), + m_canvasGeometryStates(Count(root.canvas_geometries())), m_brushes(Count(root.brushes()), nullptr), m_gradientStops(Count(root.gradient_stops()), nullptr), m_viewBoxes(Count(root.view_boxes()), nullptr), @@ -348,7 +360,7 @@ namespace return vector == nullptr ? 0 : vector->size(); } - winrt::hstring GetString(uint32_t index) const + winrt::hstring GetString(uint32_t index) { if (index == NullIndex) { @@ -357,12 +369,17 @@ namespace auto const* strings = m_root.strings(); Check(strings != nullptr && index < strings->size()); - auto const* value = strings->Get(index); - Check(value != nullptr); + if (!m_stringCached[index]) + { + auto const* value = strings->Get(index); + Check(value != nullptr); - // The buffer holds UTF-8 and WinRT wants UTF-16, so this is the one place - // where text is copied out of the buffer. - return winrt::to_hstring(std::string_view(value->c_str(), value->size())); + m_strings[index] = + winrt::to_hstring(std::string_view(value->c_str(), value->size())); + m_stringCached[index] = true; + } + + return m_strings[index]; } // ----------------------------------------------------------------------- @@ -396,6 +413,11 @@ namespace void StartAnimations(CompositionObject const& target, Fb::CompObj const* source) { + if (source == nullptr) + { + return; + } + auto const* animators = source->animators(); if (animators == nullptr) { @@ -471,14 +493,25 @@ namespace target.InsertScalar(name, value->scalar()); break; case Fb::PropertyValueType::Vector2: - target.InsertVector2(name, { Vector(value)->x(), Vector(value)->y() }); + { + auto const* vector = Vector(value); + target.InsertVector2(name, { vector->x(), vector->y() }); break; + } case Fb::PropertyValueType::Vector3: - target.InsertVector3(name, { Vector(value)->x(), Vector(value)->y(), Vector(value)->z() }); + { + auto const* vector = Vector(value); + target.InsertVector3(name, { vector->x(), vector->y(), vector->z() }); break; + } case Fb::PropertyValueType::Vector4: - target.InsertVector4(name, { Vector(value)->x(), Vector(value)->y(), Vector(value)->z(), Vector(value)->w() }); + { + auto const* vector = Vector(value); + target.InsertVector4( + name, + { vector->x(), vector->y(), vector->z(), vector->w() }); break; + } case Fb::PropertyValueType::None: break; default: @@ -636,6 +669,11 @@ namespace sprite.Brush(GetBrush(brush)); } + if (auto shadow = source->shadow(); shadow != NullIndex) + { + sprite.Shadow(GetShadow(shadow)); + } + container = sprite; break; } @@ -680,6 +718,8 @@ namespace ThrowMalformed(); } + // Any case that resolves referenced objects must populate m_visuals + // first. The assignment below only precedes the child walk. // Cached before the children are walked, so that a cycle terminates. m_visuals[index] = container; @@ -815,6 +855,8 @@ namespace ThrowMalformed(); } + // Any case that resolves referenced objects must populate m_shapes + // first. The assignment below cannot terminate recursive graphs. m_shapes[index] = result; ApplyShapeProperties(result, source); @@ -1047,6 +1089,28 @@ namespace } winrt::com_ptr BuildGeometry(uint32_t index) + { + Check(index < m_canvasGeometries.size()); + + switch (m_canvasGeometryStates[index]) + { + case CanvasGeometryState::Realized: + return m_canvasGeometries[index]; + case CanvasGeometryState::Realizing: + ThrowMalformed(); + case CanvasGeometryState::Unrealized: + break; + } + + m_canvasGeometryStates[index] = CanvasGeometryState::Realizing; + auto result = BuildGeometryCore(index); + Check(result != nullptr); + m_canvasGeometries[index] = result; + m_canvasGeometryStates[index] = CanvasGeometryState::Realized; + return result; + } + + winrt::com_ptr BuildGeometryCore(uint32_t index) { auto const* source = At(m_root.canvas_geometries(), index); @@ -1344,6 +1408,8 @@ namespace ThrowMalformed(); } + // Any case that resolves referenced objects must populate m_brushes + // first. The assignment below cannot terminate recursive graphs. m_brushes[index] = result; Initialize(result, source->base()); @@ -1484,6 +1550,8 @@ namespace ThrowMalformed(); } + // Any case that resolves referenced objects must populate m_clips + // first. The assignment below cannot terminate recursive graphs. m_clips[index] = result; if (auto const* value = source->center_point()) @@ -1610,12 +1678,35 @@ namespace break; } case Fb::SurfaceKind::LoadedImageFromUri: + { + auto const uri = source->uri(); + Check(uri != NullIndex); + m_surfaces[index] = + winrt::Windows::UI::Xaml::Media::LoadedImageSurface::StartLoadFromUri( + winrt::Windows::Foundation::Uri(GetString(uri))); + break; + } case Fb::SurfaceKind::LoadedImageFromStream: - // LoadedImageSurface lives in Windows.UI.Xaml.Media, so an - // interpreter that is usable outside XAML cannot create one. The - // translator only emits these for animations that embed an image, - // which the Lottie feature set this interpreter targets excludes. - ThrowUnsupported(); + { + auto const* bytes = source->bytes(); + Check(bytes != nullptr); + + winrt::Windows::Storage::Streams::InMemoryRandomAccessStream stream; + winrt::Windows::Storage::Streams::DataWriter writer( + stream.GetOutputStreamAt(0)); + writer.WriteBytes( + winrt::array_view( + bytes->data(), + bytes->data() + bytes->size())); + writer.StoreAsync().get(); + writer.FlushAsync().get(); + stream.Seek(0); + + m_surfaces[index] = + winrt::Windows::UI::Xaml::Media::LoadedImageSurface::StartLoadFromStream( + stream); + break; + } default: ThrowMalformed(); } @@ -1951,14 +2042,25 @@ namespace Compositor const& m_compositor; Fb::LottieComposition const& m_root; + enum class CanvasGeometryState : uint8_t + { + Unrealized, + Realizing, + Realized, + }; + // The caches. Each is exactly as long as the matching vector in the buffer, // so a lookup is a bounds check and an index. Each holds the least derived // type that any reference to that category needs, which is what stops a // realized node from ever having to be cast back to what it was. + std::vector m_strings; + std::vector m_stringCached; std::vector m_visuals; std::vector m_shapes; std::vector m_geometries; std::vector m_paths; + std::vector> m_canvasGeometries; + std::vector m_canvasGeometryStates; std::vector m_brushes; std::vector m_gradientStops; std::vector m_viewBoxes; diff --git a/external/flatbuffers b/external/flatbuffers new file mode 160000 index 00000000..5761d6e6 --- /dev/null +++ b/external/flatbuffers @@ -0,0 +1 @@ +Subproject commit 5761d6e67af841d15ee21bc1ce9a78ffa9cf939e From 7e0245e5a2252db490ceb5028867342185eee601 Mon Sep 17 00:00:00 2001 From: Jon Wiswall Date: Tue, 1 Sep 2026 14:51:15 -0700 Subject: [PATCH 21/39] Regenerate native bindings from pinned FlatBuffers Use the FlatBuffers headers and flatc from pinned upstream default-branch commit 5761d6e for C++. Keep C# generation on the 25.2.10 runtime available through Microsoft's configured NuGet feed. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 58fac82f-0489-4c57-a18d-2b7ba273faa6 --- build/RegenerateFlatbuffers.ps1 | 35 ++++---- .../Generated/lottie_comp_generated.h | 89 ++++++++++++------- 2 files changed, 78 insertions(+), 46 deletions(-) diff --git a/build/RegenerateFlatbuffers.ps1 b/build/RegenerateFlatbuffers.ps1 index 8cc52934..1a2e0efc 100644 --- a/build/RegenerateFlatbuffers.ps1 +++ b/build/RegenerateFlatbuffers.ps1 @@ -4,42 +4,47 @@ # need flatc installed in order to build. Run this script after any change to # source/LottieFlatbuffer/lottie_comp.fbs, and check in the result. # -# The version of flatc MUST match the version of the Google.FlatBuffers NuGet -# package referenced by dlls/CompDataFlatbuffer/CompDataFlatbuffer.dll.csproj, -# because the generated C# calls a version-stamped validation method -# (FlatBufferConstants.FLATBUFFERS_) that only exists in the matching -# package. -# -# Download flatc from https://github.com/google/flatbuffers/releases/tag/v25.2.10 +# C# output must match the Google.FlatBuffers package available through the +# configured Microsoft feed. C++ output must match external/flatbuffers. [CmdletBinding()] param( - # Path to the flatc executable. Defaults to whatever is on the PATH. - [string]$Flatc = "flatc" + # C# code must match the Google.FlatBuffers package version. + [string]$CSharpFlatc = "flatc", + + # C++ code must match the headers in external/flatbuffers. + [Parameter(Mandatory)] + [string]$CppFlatc ) $ErrorActionPreference = "Stop" -$expectedVersion = "25.2.10" +$expectedCSharpVersion = "25.2.10" +$expectedCppVersion = "25.12.19" $repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..") $schema = Join-Path $repoRoot "source/LottieFlatbuffer/lottie_comp.fbs" $csharpOut = Join-Path $repoRoot "source/CompDataFlatbuffer/Generated" $cppOut = Join-Path $repoRoot "dlls/LottieRuntime/Generated" -$actualVersion = (& $Flatc --version) -replace '^flatc version ', '' -if ($actualVersion.Trim() -ne $expectedVersion) { - throw "flatc version $expectedVersion is required, but '$Flatc' is version $($actualVersion.Trim())." +$actualCSharpVersion = (& $CSharpFlatc --version) -replace '^flatc version ', '' +if ($actualCSharpVersion.Trim() -ne $expectedCSharpVersion) { + throw "C# flatc version $expectedCSharpVersion is required, but '$CSharpFlatc' is version $($actualCSharpVersion.Trim())." +} + +$actualCppVersion = (& $CppFlatc --version) -replace '^flatc version ', '' +if ($actualCppVersion.Trim() -ne $expectedCppVersion) { + throw "C++ flatc version $expectedCppVersion is required, but '$CppFlatc' is version $($actualCppVersion.Trim())." } New-Item -ItemType Directory -Force -Path $csharpOut, $cppOut | Out-Null # --gen-onefile keeps the checked in C# to a single file rather than one file # per type spread over a namespace directory tree. -& $Flatc --csharp --gen-onefile -o $csharpOut $schema +& $CSharpFlatc --csharp --gen-onefile -o $csharpOut $schema if ($LASTEXITCODE -ne 0) { throw "flatc failed to generate C#." } -& $Flatc --cpp --scoped-enums -o $cppOut $schema +& $CppFlatc --cpp --scoped-enums -o $cppOut $schema if ($LASTEXITCODE -ne 0) { throw "flatc failed to generate C++." } Write-Host "Generated FlatBuffers code from $schema" diff --git a/dlls/LottieRuntime/Generated/lottie_comp_generated.h b/dlls/LottieRuntime/Generated/lottie_comp_generated.h index b2eba1b4..eb3b0700 100644 --- a/dlls/LottieRuntime/Generated/lottie_comp_generated.h +++ b/dlls/LottieRuntime/Generated/lottie_comp_generated.h @@ -9,8 +9,8 @@ // Ensure the included flatbuffers.h is the same version as when this file was // generated, otherwise it may not be compatible. static_assert(FLATBUFFERS_VERSION_MAJOR == 25 && - FLATBUFFERS_VERSION_MINOR == 2 && - FLATBUFFERS_VERSION_REVISION == 10, + FLATBUFFERS_VERSION_MINOR == 12 && + FLATBUFFERS_VERSION_REVISION == 19, "Non-compatible flatbuffers version included"); namespace CommunityToolkit { @@ -959,7 +959,8 @@ struct Animator FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { uint32_t controller() const { return GetField(VT_CONTROLLER, 4294967295); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_PROPERTY, 4) && VerifyField(verifier, VT_ANIMATION, 4) && @@ -1020,7 +1021,8 @@ struct CompObj FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { uint32_t properties() const { return GetField(VT_PROPERTIES, 4294967295); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_COMMENT, 4) && VerifyOffset(verifier, VT_ANIMATORS) && @@ -1104,7 +1106,8 @@ struct PropertyValue FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Color *color() const { return GetStruct(VT_COLOR); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_NAME, 4) && VerifyField(verifier, VT_TYPE, 1) && @@ -1173,7 +1176,8 @@ struct ReferenceParameter FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table uint32_t target() const { return GetField(VT_TARGET, 0); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_NAME, 4) && VerifyField(verifier, VT_TARGET, 4) && @@ -1224,7 +1228,8 @@ struct SourceParameter FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { uint32_t brush() const { return GetField(VT_BRUSH, 0); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_NAME, 4) && VerifyField(verifier, VT_BRUSH, 4) && @@ -1299,7 +1304,8 @@ struct KeyFrame FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { uint32_t path() const { return GetField(VT_PATH, 4294967295); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_PROGRESS, 4) && VerifyField(verifier, VT_EASING, 4) && @@ -1450,7 +1456,8 @@ struct Visual FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { uint32_t shadow() const { return GetField(VT_SHADOW, 4294967295); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_BASE) && verifier.VerifyTable(base()) && @@ -1716,7 +1723,8 @@ struct Shape FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { ::flatbuffers::Optional stroke_thickness() const { return GetOptional(VT_STROKE_THICKNESS); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_BASE) && verifier.VerifyTable(base()) && @@ -1962,7 +1970,8 @@ struct Geometry FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *radius() const { return GetStruct(VT_RADIUS); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_BASE) && verifier.VerifyTable(base()) && @@ -2152,7 +2161,8 @@ struct Brush FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { const ::flatbuffers::Vector<::flatbuffers::Offset> *source_parameters() const { return GetPointer> *>(VT_SOURCE_PARAMETERS); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_BASE) && verifier.VerifyTable(base()) && @@ -2391,7 +2401,8 @@ struct GradientStop FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { float offset() const { return GetField(VT_OFFSET, 0.0f); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_BASE) && verifier.VerifyTable(base()) && @@ -2449,7 +2460,8 @@ struct ViewBox FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Vec2 *size() const { return GetStruct(VT_SIZE); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_BASE) && verifier.VerifyTable(base()) && @@ -2529,7 +2541,8 @@ struct Clip FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { uint32_t geometry() const { return GetField(VT_GEOMETRY, 4294967295); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_BASE) && verifier.VerifyTable(base()) && @@ -2647,7 +2660,8 @@ struct Shadow FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { ::flatbuffers::Optional source_policy() const { return GetOptional(VT_SOURCE_POLICY); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_BASE) && verifier.VerifyTable(base()) && @@ -2755,7 +2769,8 @@ struct Surface FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { const ::flatbuffers::Vector *bytes() const { return GetPointer *>(VT_BYTES); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_BASE) && verifier.VerifyTable(base()) && @@ -2871,7 +2886,8 @@ struct Effect FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { float blur_amount() const { return GetField(VT_BLUR_AMOUNT, 0.0f); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_BASE) && verifier.VerifyTable(base()) && @@ -2987,7 +3003,8 @@ struct Easing FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { ::flatbuffers::Optional is_final_step_single_frame() const { return GetOptional(VT_IS_FINAL_STEP_SINGLE_FRAME); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_BASE) && verifier.VerifyTable(base()) && @@ -3105,7 +3122,8 @@ struct Animation FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { ::flatbuffers::Optional interpolation_color_space() const { return GetOptional(VT_INTERPOLATION_COLOR_SPACE); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_BASE) && verifier.VerifyTable(base()) && @@ -3225,7 +3243,8 @@ struct PropertySet FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { const ::flatbuffers::Vector<::flatbuffers::Offset> *values() const { return GetPointer> *>(VT_VALUES); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_BASE) && verifier.VerifyTable(base()) && @@ -3310,7 +3329,8 @@ struct Controller FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { bool is_custom() const { return GetField(VT_IS_CUSTOM, 0) != 0; } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_BASE) && verifier.VerifyTable(base()) && @@ -3400,7 +3420,8 @@ struct PropertyBinding FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { const CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema::Color *default_color() const { return GetStruct(VT_DEFAULT_COLOR); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_NAME, 4) && VerifyField(verifier, VT_DISPLAY_NAME, 4) && @@ -3485,7 +3506,8 @@ struct Marker FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { float duration_progress() const { return GetField(VT_DURATION_PROGRESS, 0.0f); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_NAME, 4) && VerifyField(verifier, VT_PROGRESS, 4) && @@ -3566,7 +3588,8 @@ struct Metadata FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { uint32_t theming_property_set() const { return GetField(VT_THEMING_PROPERTY_SET, 4294967295); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_NAME, 4) && VerifyField(verifier, VT_WIDTH, 4) && @@ -3757,7 +3780,8 @@ struct LottieComposition FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table const ::flatbuffers::Vector *custom_controllers() const { return GetPointer *>(VT_CUSTOM_CONTROLLERS); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_SCHEMA_VERSION, 2) && VerifyField(verifier, VT_REQUIRED_UAP_VERSION, 2) && @@ -4077,7 +4101,8 @@ struct CanvasGeometry FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { float radius_y() const { return GetField(VT_RADIUS_Y, 0.0f); } - bool Verify(::flatbuffers::Verifier &verifier) const { + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_KIND, 1) && VerifyField(verifier, VT_A, 4) && @@ -4266,14 +4291,16 @@ inline bool SizePrefixedLottieCompositionBufferHasIdentifier(const void *buf) { buf, LottieCompositionIdentifier(), true); } +template inline bool VerifyLottieCompositionBuffer( - ::flatbuffers::Verifier &verifier) { - return verifier.VerifyBuffer(LottieCompositionIdentifier()); + ::flatbuffers::VerifierTemplate &verifier) { + return verifier.template VerifyBuffer(LottieCompositionIdentifier()); } +template inline bool VerifySizePrefixedLottieCompositionBuffer( - ::flatbuffers::Verifier &verifier) { - return verifier.VerifySizePrefixedBuffer(LottieCompositionIdentifier()); + ::flatbuffers::VerifierTemplate &verifier) { + return verifier.template VerifySizePrefixedBuffer(LottieCompositionIdentifier()); } inline const char *LottieCompositionExtension() { From 70f33daba3c6b0a6c43d7447702b9dcf266b7578 Mon Sep 17 00:00:00 2001 From: Jon Wiswall Date: Tue, 1 Sep 2026 14:51:27 -0700 Subject: [PATCH 22/39] Support custom animation controllers Create custom controllers explicitly, pass them to StartAnimation, and initialize root property sets before descending into children so controller expressions can bind to root progress. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 58fac82f-0489-4c57-a18d-2b7ba273faa6 --- dlls/LottieRuntime/LottieRuntime.cpp | 91 +++++++++++++++++++--------- 1 file changed, 63 insertions(+), 28 deletions(-) diff --git a/dlls/LottieRuntime/LottieRuntime.cpp b/dlls/LottieRuntime/LottieRuntime.cpp index f2d55262..0aad6531 100644 --- a/dlls/LottieRuntime/LottieRuntime.cpp +++ b/dlls/LottieRuntime/LottieRuntime.cpp @@ -16,13 +16,11 @@ // takes the least derived type. Everything lives in one translation unit so that // the linker can discard what an application does not reach. // -// * Runtime cost second. Every node is realized at most once and is then found by -// indexing an array, never by hashing. The buffer is read in place; nothing is -// copied out of it. +// * Runtime cost second. Nodes and strings are realized once, then found by indexing +// dense arrays. The FlatBuffer remains the object model. // -// * Transient memory third. The only allocations are the realization caches, which -// are exactly as large as the node vectors in the buffer, and the D2D geometries, -// which are released as soon as they have been handed to a CompositionPath. +// * Transient memory third. Cache sizes come directly from the buffer. D2D geometry +// remains cached during interpretation so shared subgraphs stay shared. // // Calls are made through the least derived interface that has the member being used. // A node is only ever known by its concrete type inside the function that creates it; @@ -31,8 +29,7 @@ // to ask what a node actually is, because the buffer already said, and the answer was // consumed at the moment of creation. -// must be included before any C++/WinRT header, and LottieRuntime.h -// pulls one in (winrt/Windows.UI.Composition.h), so it has to come first here. +// C++/WinRT requires before projected headers. #include #include "LottieRuntime.h" @@ -389,6 +386,12 @@ namespace // does to every node is available on CompositionObject. // ----------------------------------------------------------------------- void Initialize(CompositionObject const& target, Fb::CompObj const* source) + { + InitializeProperties(target, source); + StartAnimations(target, source); + } + + void InitializeProperties(CompositionObject const& target, Fb::CompObj const* source) { if (source == nullptr) { @@ -408,7 +411,6 @@ namespace RealizePropertySet(properties, target.Properties()); } - StartAnimations(target, source); } void StartAnimations(CompositionObject const& target, Fb::CompObj const* source) @@ -433,18 +435,28 @@ namespace auto animation = GetAnimation(animator->animation()); Check(animation != nullptr); - // A custom controller is one that drives a property that is not its - // own, which the composition engine has no way to express. The - // managed instantiator rejects these too. auto controllerIndex = animator->controller(); - if (controllerIndex != NullIndex && IsCustomController(controllerIndex)) + auto customController = + controllerIndex != NullIndex && IsCustomController(controllerIndex) + ? GetController(controllerIndex) + : nullptr; + + if (customController != nullptr && + animation.try_as() != nullptr) { - ThrowUnsupported(); + ThrowMalformed(); } - target.StartAnimation(property, animation); + if (customController != nullptr) + { + target.StartAnimation(property, animation, customController); + } + else + { + target.StartAnimation(property, animation); + } - if (controllerIndex != NullIndex) + if (controllerIndex != NullIndex && customController == nullptr) { RealizeController(controllerIndex, target, property); } @@ -559,7 +571,7 @@ namespace // ----------------------------------------------------------------------- // Controllers. // ----------------------------------------------------------------------- - void RealizeController(uint32_t index, CompositionObject const& target, winrt::hstring const& property) + void RealizeController(uint32_t index, AnimationController const& controller) { Check(index < m_controllers.size()); @@ -568,12 +580,6 @@ namespace return; } - auto controller = target.TryGetAnimationController(property); - if (controller == nullptr) - { - return; - } - m_controllers[index] = controller; auto const* source = At(m_root.controllers(), index); @@ -586,6 +592,37 @@ namespace Initialize(controller, source->base()); } + void RealizeController( + uint32_t index, + CompositionObject const& target, + winrt::hstring const& property) + { + auto controller = target.TryGetAnimationController(property); + Check(controller != nullptr); + RealizeController(index, controller); + } + + AnimationController GetController(uint32_t index) + { + Check(index < m_controllers.size()); + auto const* source = At(m_root.controllers(), index); + + if (m_controllers[index] == nullptr) + { + if (source->is_custom()) + { + RealizeController(index, m_compositor.CreateAnimationController()); + } + else + { + GetObjectReference(source->target_object()); + } + } + + Check(m_controllers[index] != nullptr); + return m_controllers[index]; + } + // ----------------------------------------------------------------------- // Object references. // @@ -623,10 +660,7 @@ namespace Check(result != nullptr); return result; } - case Fb::ObjectCategory::Controller: - // A controller is realized by the animator that produces it, so it - // can never be the target of a reference. - ThrowMalformed(); + case Fb::ObjectCategory::Controller: return GetController(index); default: ThrowMalformed(); } @@ -724,6 +758,7 @@ namespace m_visuals[index] = container; ApplyVisualProperties(container, source); + InitializeProperties(container, source->base()); if (auto const* children = source->children(); children != nullptr) { @@ -736,7 +771,7 @@ namespace } } - Initialize(container, source->base()); + StartAnimations(container, source->base()); return container; } From b8867d711b3725fc40172fd580e4dbf5aa12754a Mon Sep 17 00:00:00 2001 From: Jon Wiswall Date: Tue, 1 Sep 2026 15:49:45 -0700 Subject: [PATCH 23/39] Make LottieRuntime DLL ABI-only Remove exported C++ symbols, require callers to own the DispatcherQueue, support architecture-neutral registration-free activation, load embedded image streams synchronously, and make the native project portable from its packaged source layout. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 58fac82f-0489-4c57-a18d-2b7ba273faa6 --- dlls/LottieRuntime/LottieRuntime.cpp | 22 +++++++++++--------- dlls/LottieRuntime/LottieRuntime.h | 25 ++++++++--------------- dlls/LottieRuntime/LottieRuntime.manifest | 10 ++------- dlls/LottieRuntime/LottieRuntime.vcxproj | 19 ++++++++--------- dlls/LottieRuntime/LottieRuntimeCom.cpp | 17 +++------------ dlls/LottieRuntime/LottieRuntimeCom.h | 25 ++++++----------------- 6 files changed, 40 insertions(+), 78 deletions(-) diff --git a/dlls/LottieRuntime/LottieRuntime.cpp b/dlls/LottieRuntime/LottieRuntime.cpp index 0aad6531..e059bbcc 100644 --- a/dlls/LottieRuntime/LottieRuntime.cpp +++ b/dlls/LottieRuntime/LottieRuntime.cpp @@ -41,6 +41,8 @@ #include #include +#include +#include #include #include @@ -1726,16 +1728,16 @@ namespace auto const* bytes = source->bytes(); Check(bytes != nullptr); - winrt::Windows::Storage::Streams::InMemoryRandomAccessStream stream; - winrt::Windows::Storage::Streams::DataWriter writer( - stream.GetOutputStreamAt(0)); - writer.WriteBytes( - winrt::array_view( - bytes->data(), - bytes->data() + bytes->size())); - writer.StoreAsync().get(); - writer.FlushAsync().get(); - stream.Seek(0); + winrt::com_ptr byteStream; + byteStream.attach(SHCreateMemStream(bytes->data(), bytes->size())); + Check(byteStream != nullptr); + + winrt::Windows::Storage::Streams::IRandomAccessStream stream{ nullptr }; + winrt::check_hresult(CreateRandomAccessStreamOverStream( + byteStream.get(), + BSOS_DEFAULT, + winrt::guid_of(), + winrt::put_abi(stream))); m_surfaces[index] = winrt::Windows::UI::Xaml::Media::LoadedImageSurface::StartLoadFromStream( diff --git a/dlls/LottieRuntime/LottieRuntime.h b/dlls/LottieRuntime/LottieRuntime.h index 28a65f6f..76b182cd 100644 --- a/dlls/LottieRuntime/LottieRuntime.h +++ b/dlls/LottieRuntime/LottieRuntime.h @@ -4,24 +4,17 @@ #pragma once +#include + #include #include #include -// The component is usable either as a DLL or compiled straight into an application. -// Define LOTTIERUNTIME_STATIC to build it in, and LOTTIERUNTIME_EXPORTS when building -// the DLL itself. -#if defined(LOTTIERUNTIME_STATIC) -#define LOTTIERUNTIME_API -#elif defined(LOTTIERUNTIME_EXPORTS) -#define LOTTIERUNTIME_API __declspec(dllexport) -#else -#define LOTTIERUNTIME_API __declspec(dllimport) -#endif - namespace CommunityToolkit::WinUI::Lottie { + // LoadComposition is the direct-link C++ API. + // DLL callers use ILottieCompositionLoader from LottieRuntimeCom.h. // Builds the composition described by a serialized Lottie composition. // // The buffer is the output of LottieGen -Language flatbuffer, or equivalently of @@ -35,20 +28,18 @@ namespace CommunityToolkit::WinUI::Lottie // returned by ProgressPropertySet, so playback is driven by animating that single // property. // - // The result is the least derived type that describes the root, because a caller - // that only parents the tree and drives its progress never needs anything more. + // Visual is sufficient for parenting the root and driving its progress. // // Throws winrt::hresult_error with E_INVALIDARG if the buffer is not a well formed // composition, and E_NOTIMPL if it requires a feature this build does not have. - LOTTIERUNTIME_API winrt::Windows::UI::Composition::Visual LoadComposition( + winrt::Windows::UI::Composition::Visual LoadComposition( winrt::Windows::UI::Composition::Compositor const& compositor, std::span buffer); // The property set that drives the animation. Animating its "Progress" scalar from // 0 to 1 plays the animation once. // - // This is the property set of the visual returned by LoadComposition, so it is only - // a convenience; it exists so that callers do not have to know that detail. - LOTTIERUNTIME_API winrt::Windows::UI::Composition::CompositionPropertySet ProgressPropertySet( + // Returns the root Visual's property set. + winrt::Windows::UI::Composition::CompositionPropertySet ProgressPropertySet( winrt::Windows::UI::Composition::Visual const& root); } diff --git a/dlls/LottieRuntime/LottieRuntime.manifest b/dlls/LottieRuntime/LottieRuntime.manifest index 95945106..d93a09f5 100644 --- a/dlls/LottieRuntime/LottieRuntime.manifest +++ b/dlls/LottieRuntime/LottieRuntime.manifest @@ -1,17 +1,11 @@ - + + processorArchitecture="*" /> 10.0.17763.0 + $(MSBuildThisFileDirectory)..\..\external\flatbuffers\include + $(MSBuildThisFileDirectory)flatbuffers\include @@ -45,8 +49,8 @@ stdcpp20 - LOTTIERUNTIME_EXPORTS;WIN32_LEAN_AND_MEAN;NOMINMAX;%(PreprocessorDefinitions) - $(MSBuildThisFileDirectory);$(MSBuildThisFileDirectory)packages\flatbuffers\include;%(AdditionalIncludeDirectories) + WIN32_LEAN_AND_MEAN;NOMINMAX;%(PreprocessorDefinitions) + $(MSBuildThisFileDirectory);$(FlatBuffersIncludeDirectory);%(AdditionalIncludeDirectories) Level4 true @@ -60,7 +64,7 @@ true - CoreMessaging.lib;d2d1.lib;dxguid.lib;windowsapp.lib;%(AdditionalDependencies) + d2d1.lib;dxguid.lib;shcore.lib;shlwapi.lib;windowsapp.lib;%(AdditionalDependencies) LottieRuntime.def @@ -81,13 +85,8 @@ - + diff --git a/dlls/LottieRuntime/LottieRuntimeCom.cpp b/dlls/LottieRuntime/LottieRuntimeCom.cpp index dc664301..b82fef6c 100644 --- a/dlls/LottieRuntime/LottieRuntimeCom.cpp +++ b/dlls/LottieRuntime/LottieRuntimeCom.cpp @@ -11,8 +11,6 @@ #include "LottieRuntimeCom.h" #include "LottieRuntime.h" -#include - #include #include #include @@ -22,8 +20,8 @@ using namespace winrt::Windows::UI::Composition; namespace { - // The loader creates its Compositor on the first load. Desktop threads need a - // DispatcherQueue before Windows.UI.Composition can activate a Compositor. + // The caller owns the thread's DispatcherQueue. It must outlive every Visual + // returned by this loader. struct LottieCompositionLoader : winrt::implements { STDMETHODIMP LoadComposition(UINT32 length, BYTE const* buffer, REFIID riid, void** result) noexcept override @@ -46,15 +44,7 @@ namespace { if (!winrt::Windows::System::DispatcherQueue::GetForCurrentThread()) { - DispatcherQueueOptions const options{ - sizeof(DispatcherQueueOptions), - DQTYPE_THREAD_CURRENT, - DQTAT_COM_NONE, - }; - - winrt::check_hresult(CreateDispatcherQueueController( - options, - reinterpret_cast(winrt::put_abi(m_dispatcherQueueController)))); + return E_ILLEGAL_METHOD_CALL; } m_compositor = Compositor(); @@ -71,7 +61,6 @@ namespace } private: - winrt::Windows::System::DispatcherQueueController m_dispatcherQueueController{ nullptr }; Compositor m_compositor{ nullptr }; }; diff --git a/dlls/LottieRuntime/LottieRuntimeCom.h b/dlls/LottieRuntime/LottieRuntimeCom.h index a08a4840..d4997a9d 100644 --- a/dlls/LottieRuntime/LottieRuntimeCom.h +++ b/dlls/LottieRuntime/LottieRuntimeCom.h @@ -7,32 +7,19 @@ #include #include -// A classic (non-WinRT) COM entry point for LoadComposition, for callers that only -// have a byte buffer and a COM activation story, rather than a Compositor and a -// C++/WinRT toolchain. It is not registered with the system: it is meant to be -// activated registration-free, through an application manifest that names -// LottieRuntime.dll and this CLSID (see the "Isolated applications and side-by-side -// assemblies" COM activation model). +// COM interface for DLL activation. LottieRuntime.manifest declares the class for +// registration-free COM activation. // -// The object it produces is the root Visual of the composition, queried for -// whatever interface the caller asked for through riid - typically IInspectable, -// ABI::Windows::UI::Composition::IVisual, or IUnknown. +// riid selects the interface returned for the root Visual. MIDL_INTERFACE("3874B71B-05E6-42A9-B838-1AE7F0BA4E1C") ILottieCompositionLoader : public IUnknown { public: - // Parses buffer (the output of LottieGen -Language flatbuffer, or equivalently - // CompositionSerializer) and produces the root Visual of the resulting tree. - // - // The buffer is treated as untrusted: it is verified before it is read, so a - // malformed buffer produces a failure HRESULT rather than undefined behaviour. + // buffer contains LottieGen -Language flatbuffer output. Validation precedes + // every read; malformed data returns E_INVALIDARG. STDMETHOD(LoadComposition)(UINT32 length, BYTE const* buffer, REFIID riid, void** result) = 0; }; -// A caller activating this class registration-free never links against -// LottieRuntime.dll's import library (there is no exported symbol to link -// against for an in-proc COM server resolved purely through an activation -// context), so the CLSID is a compile-time constant here rather than an -// extern declaration. +// Registration-free clients do not link an import library. constexpr CLSID CLSID_LottieCompositionLoader = { 0x9f7b7089, 0xf512, 0x4bbc, { 0x81, 0xfe, 0x4f, 0xf1, 0x2b, 0x4d, 0xa1, 0x59 } }; From d28d46ae76f952bb10d0232471a0f35e7386748d Mon Sep 17 00:00:00 2001 From: Jon Wiswall Date: Tue, 1 Sep 2026 15:49:59 -0700 Subject: [PATCH 24/39] Consolidate LottieRuntime sample tooling Replace separate validation and dump executables with LottieRuntime.exe supporting validate, --dump, and --show modes. Document JSON conversion and package the complete native runtime source set, COM project, generated binding, and FlatBuffers headers in LottieGen.nupkg. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 58fac82f-0489-4c57-a18d-2b7ba273faa6 --- LottieGen/DotnetTool/LottieGen.csproj | 15 + dlls/LottieRuntime/README.md | 118 ++++-- .../LottieRuntimeComTest.cpp | 140 ------- .../LottieRuntimeComTest.vcxproj | 88 ----- dlls/LottieRuntimeDump/LottieRuntimeDump.cpp | 200 ---------- dlls/LottieRuntimeTool/LottieRuntime.cpp | 363 ++++++++++++++++++ .../LottieRuntime.vcxproj} | 20 +- dlls/LottieRuntimeTool/README.md | 54 +++ 8 files changed, 524 insertions(+), 474 deletions(-) delete mode 100644 dlls/LottieRuntimeComTest/LottieRuntimeComTest.cpp delete mode 100644 dlls/LottieRuntimeComTest/LottieRuntimeComTest.vcxproj delete mode 100644 dlls/LottieRuntimeDump/LottieRuntimeDump.cpp create mode 100644 dlls/LottieRuntimeTool/LottieRuntime.cpp rename dlls/{LottieRuntimeDump/LottieRuntimeDump.vcxproj => LottieRuntimeTool/LottieRuntime.vcxproj} (73%) create mode 100644 dlls/LottieRuntimeTool/README.md diff --git a/LottieGen/DotnetTool/LottieGen.csproj b/LottieGen/DotnetTool/LottieGen.csproj index 6ae9c9ac..79e61622 100644 --- a/LottieGen/DotnetTool/LottieGen.csproj +++ b/LottieGen/DotnetTool/LottieGen.csproj @@ -37,4 +37,19 @@ + + + + + + + + + + + + + + + diff --git a/dlls/LottieRuntime/README.md b/dlls/LottieRuntime/README.md index 6c2f0541..c5784f88 100644 --- a/dlls/LottieRuntime/README.md +++ b/dlls/LottieRuntime/README.md @@ -2,23 +2,22 @@ A native runtime interpreter for Lottie animations. -`LottieRuntime` builds a Windows.UI.Composition visual tree from a serialized -composition, at runtime, without a JSON parser and without generated code. It is a -header and a source file, so it can be built as a DLL that several applications -share, or compiled straight into one. +`LottieRuntime` builds a `Windows.UI.Composition` visual tree from a serialized +composition. The DLL consumes FlatBuffer data through a classic COM interface. +Applications can also link the implementation directly. ## How it fits in Lottie-Windows already turns a Lottie file into a `WinCompData` graph, which is a -description of a composition tree. There were two things that could consume that -graph: +description of a composition tree. Three consumers use that graph: | Consumer | When it runs | Cost | | --- | --- | --- | | `Instantiator` | Runtime, in the app | Ships the whole JSON reader and translator | | `LottieGen` code generators | Build time | No runtime cost, but one class per animation | +| `LottieRuntime` | Runtime, in the app | One interpreter shared by every animation | -This adds a third. `CompositionSerializer` writes the `WinCompData` graph into a +`CompositionSerializer` writes the `WinCompData` graph into a FlatBuffer at build time, and `LottieRuntime` reads that buffer at runtime. The animation stays data, so it can be swapped, downloaded or themed without a rebuild, and the code that reads it is the same size no matter how many animations an @@ -57,24 +56,35 @@ LottieGen -InputFile animation.json -Language flatbuffer -OutputFolder . This writes `Animation.lcomp`. The format is described by [`source/LottieFlatbuffer/lottie_comp.fbs`](../../source/LottieFlatbuffer/lottie_comp.fbs). -## Using it +## Using the DLL ```cpp -#include +#include auto bytes = ReadFileBytes(L"Animation.lcomp"); -auto root = CommunityToolkit::WinUI::Lottie::LoadComposition(compositor, bytes); - -// Drive the animation by animating one scalar from 0 to 1. -auto progress = CommunityToolkit::WinUI::Lottie::ProgressPropertySet(root); -progress.StartAnimation(L"Progress", playAnimation); - -ElementCompositionPreview::SetElementChildVisual(element, root); +winrt::com_ptr loader; +winrt::check_hresult(CoCreateInstance( + CLSID_LottieCompositionLoader, + nullptr, + CLSCTX_INPROC_SERVER, + __uuidof(ILottieCompositionLoader), + loader.put_void())); + +winrt::Windows::UI::Composition::Visual root{ nullptr }; +winrt::check_hresult(loader->LoadComposition( + static_cast(bytes.size()), + reinterpret_cast(bytes.data()), + winrt::guid_of(), + winrt::put_abi(root))); ``` -`LoadComposition` returns a `Visual`, which is the least derived type that describes -the root. A caller that parents the tree and drives its progress never needs more -than that. +`LottieRuntime.manifest` declares the private COM assembly. Activate that manifest +before `CoCreateInstance`; `LottieRuntime.exe` contains the complete activation +sequence. No system registration is required. The calling thread must own a +`DispatcherQueue` that outlives the returned visual tree. + +`LottieRuntime.h` is the C++ API for applications linking the implementation +directly. It is not exported by `LottieRuntime.dll`. ## Design @@ -93,13 +103,13 @@ the state shared by every node is applied by a single function that takes `CompositionObject`. Everything is in one translation unit, so `/OPT:REF` discards the code for whatever an application never reaches. -**Runtime cost second.** Each node is realized at most once. Lookups are a bounds -check and an array index; there is no hash table anywhere. The buffer is read in -place, so no intermediate object model is built. +**Runtime cost second.** Each node and string is realized at most once. Lookups use +O(1) bounds-checked array indexing into the FlatBuffer's node vectors. The buffer +is read in place; no intermediate object model is built. -**Transient memory third.** The only allocations are the caches, which are exactly -as long as the buffer's node vectors, and the D2D geometries, which are released as -soon as they have been given to a `CompositionPath`. +**Transient memory third.** Realization caches are dense vectors matching the +FlatBuffer's node vectors. D2D geometries are cached while the graph is built so +shared geometry remains shared and cycles are rejected. **Untrusted input.** A buffer may have come from a file or a download. It is checked for the `LCMP` identifier, run through the FlatBuffers verifier, and every index @@ -113,23 +123,51 @@ two effects the translator emits. There is no dependency on Win2D: the interpret implements `IGeometrySource2DInterop` and `IGraphicsEffectD2D1Interop` itself, which is a few dozen lines and avoids pulling a large component into every application. -Requires the FlatBuffers C++ runtime headers (headers only, no library) from -[flatbuffers 25.2.10](https://github.com/google/flatbuffers/releases/tag/v25.2.10), -which must match the `flatc` used to generate `Generated/lottie_comp_generated.h`. +The FlatBuffers C++ headers come from the pinned `external/flatbuffers` submodule. +The checked-in C++ binding is generated by `flatc` from that commit. The checked-in +C# binding remains on `Google.FlatBuffers` 25.2.10 from Microsoft's configured +NuGet source. `build/RegenerateFlatbuffers.ps1` validates both generator versions. + +## Incorporate it in a build + +`LottieGen.nupkg` contains the complete native source set under +`LottieRuntime/`. + +### C++/WinRT + +Add `LottieRuntime.cpp` to the consuming native project. Add `LottieRuntime/` +and `LottieRuntime/flatbuffers/include/` to its include paths. Build as C++20 +and link: -## Limitations +```text +d2d1.lib +dxguid.lib +shcore.lib +shlwapi.lib +windowsapp.lib +``` + +Include `LottieRuntime.h` and call `LoadComposition` with the application's +`Compositor`. This direct-link path does not use COM activation. + +### Managed applications + +Add the packaged `LottieRuntime.vcxproj` to the solution. The project builds +`LottieRuntime.dll`, including the `ILottieCompositionLoader` implementation. +Copy both files into every managed application output directory: + +```text +LottieRuntime.dll +LottieRuntime.manifest +``` -`LoadedImageSurface` is a Windows.UI.Xaml.Media type, so an interpreter that is -usable outside XAML cannot create one. An animation that embeds a raster image -throws `winrt::hresult_not_implemented`. Custom animation controllers are rejected -for the same reason the managed `Instantiator` rejects them: the composition engine -has no way to express one. +The managed application activates `LottieRuntime.manifest`, calls +`CoCreateInstance(CLSID_LottieCompositionLoader)`, and invokes +`ILottieCompositionLoader::LoadComposition`. The calling thread owns the +`DispatcherQueue` for the lifetime of the returned visual tree. ## Status -**This code has not been compiled.** It was written on a Linux host, where neither -MSVC nor the Windows SDK is available, so it has never been through a compiler and -must be treated as unverified. Its behaviour is specified by `Instantiator.cs` and -by `CompositionDeserializer.cs`, both of which are exercised by the tests in -`tests/CompDataFlatbuffer.Tests`, so the shape of what it does is well tested even -though this particular expression of it is not. +Debug and Release x64 builds are supported. `LottieRuntime.exe` validates, dumps, +or displays a composition through registration-free activation. +`tests/CompDataFlatbuffer.Tests` verifies serializer round trips. diff --git a/dlls/LottieRuntimeComTest/LottieRuntimeComTest.cpp b/dlls/LottieRuntimeComTest/LottieRuntimeComTest.cpp deleted file mode 100644 index 50de323f..00000000 --- a/dlls/LottieRuntimeComTest/LottieRuntimeComTest.cpp +++ /dev/null @@ -1,140 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -// Exercises the classic COM entry point declared in LottieRuntimeCom.h. -// -// LottieRuntime.dll is never registered with the system: its CLSID is only ever -// known through LottieRuntime.manifest, its own private-assembly manifest, copied -// next to this exe at build time. This test builds an activation context directly -// out of that manifest - the same shape a real isolated application uses when it -// carries a private assembly instead of registering a DLL - and activates it for -// the duration of the CoCreateInstance call, which is the reason CoCreateInstance -// resolves the CLSID without anything ever touching HKEY_CLASSES_ROOT. - -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include - -#include "LottieRuntimeCom.h" - -namespace -{ - std::vector ReadCompositionFile(wchar_t const* path) - { - HANDLE file = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); - if (file == INVALID_HANDLE_VALUE) - { - throw std::runtime_error("Could not open the composition file."); - } - - LARGE_INTEGER size{}; - GetFileSizeEx(file, &size); - - std::vector buffer(static_cast(size.QuadPart)); - DWORD read = 0; - BOOL const ok = ::ReadFile(file, buffer.data(), static_cast(buffer.size()), &read, nullptr); - CloseHandle(file); - - if (!ok || read != buffer.size()) - { - throw std::runtime_error("Could not read the composition file."); - } - - return buffer; - } -} - -int wmain(int argc, wchar_t* argv[]) -{ - if (argc != 2) - { - fwprintf(stderr, L"Usage: LottieRuntimeComTest \n"); - return 1; - } - - try - { - winrt::check_hresult(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED)); - - // The manifest is a private assembly for this exe: it declares the CLSID - // directly rather than through a level of indirection, so it is loaded - // straight into the activation context as the primary manifest, with no - // separate application manifest and no HKEY_CLASSES_ROOT registration. - wchar_t moduleDir[MAX_PATH]{}; - GetModuleFileNameW(nullptr, moduleDir, MAX_PATH); - PathRemoveFileSpecW(moduleDir); - - std::wstring const manifestPath = std::wstring(moduleDir) + L"\\LottieRuntime.manifest"; - - ACTCTXW actCtx{ sizeof(ACTCTXW) }; - actCtx.lpSource = manifestPath.c_str(); - actCtx.lpAssemblyDirectory = moduleDir; - actCtx.dwFlags = ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID; - - HANDLE const hActCtx = CreateActCtxW(&actCtx); - if (hActCtx == INVALID_HANDLE_VALUE) - { - fwprintf(stderr, L"CreateActCtx failed: %lu (manifest: %s, dir: %s)\n", GetLastError(), manifestPath.c_str(), moduleDir); - return 1; - } - - ULONG_PTR cookie = 0; - ActivateActCtx(hActCtx, &cookie); - - winrt::com_ptr loader; - HRESULT const hr = CoCreateInstance( - CLSID_LottieCompositionLoader, - nullptr, - CLSCTX_INPROC_SERVER, - __uuidof(ILottieCompositionLoader), - loader.put_void()); - - DeactivateActCtx(0, cookie); - ReleaseActCtx(hActCtx); - - if (FAILED(hr)) - { - fwprintf(stderr, L"CoCreateInstance failed (registration-free activation did not resolve the CLSID): 0x%08X (last error %lu)\n", hr, GetLastError()); - return 1; - } - - auto const buffer = ReadCompositionFile(argv[1]); - - winrt::com_ptr<::IUnknown> root; - HRESULT const loadHr = loader->LoadComposition( - static_cast(buffer.size()), - reinterpret_cast(buffer.data()), - __uuidof(::IUnknown), - root.put_void()); - - if (FAILED(loadHr)) - { - fwprintf(stderr, L"LoadComposition failed: 0x%08X\n", loadHr); - return 1; - } - - wprintf(L"Registration-free activation succeeded; loaded a composition with a root object at %p.\n", root.get()); - return 0; - } - catch (winrt::hresult_error const& e) - { - fwprintf(stderr, L"%s\n", e.message().c_str()); - return 1; - } - catch (std::exception const& e) - { - fprintf(stderr, "%s\n", e.what()); - return 1; - } -} diff --git a/dlls/LottieRuntimeComTest/LottieRuntimeComTest.vcxproj b/dlls/LottieRuntimeComTest/LottieRuntimeComTest.vcxproj deleted file mode 100644 index 9ca4357c..00000000 --- a/dlls/LottieRuntimeComTest/LottieRuntimeComTest.vcxproj +++ /dev/null @@ -1,88 +0,0 @@ - - - - - Debug - x64 - - - Release - x64 - - - Debug - ARM64 - - - Release - ARM64 - - - - - {2B6C4A1E-7F3D-4B7A-9C3E-3D5A8F0B2E49} - LottieRuntimeComTest - Application - v143 - Unicode - 10.0.22621.0 - 10.0.17763.0 - - - false - - - - - - - true - false - true - - - - - stdcpp20 - WIN32_LEAN_AND_MEAN;NOMINMAX;%(PreprocessorDefinitions) - $(MSBuildThisFileDirectory)..\LottieRuntime;%(AdditionalIncludeDirectories) - Level4 - true - Sync - - - Console - windowsapp.lib;shlwapi.lib;ole32.lib;%(AdditionalDependencies) - false - - - - - - - - - - {6E0A5B4C-2F1D-4B7A-9C3E-1D5A8F0B2E47} - - - - - - - - - - - - - - - - diff --git a/dlls/LottieRuntimeDump/LottieRuntimeDump.cpp b/dlls/LottieRuntimeDump/LottieRuntimeDump.cpp deleted file mode 100644 index 081c961a..00000000 --- a/dlls/LottieRuntimeDump/LottieRuntimeDump.cpp +++ /dev/null @@ -1,200 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -// Sample tool: loads a serialized Lottie composition (a .lcomp FlatBuffer produced -// by LottieGen -Language flatbuffer or CompositionSerializer) through LottieRuntime, -// then prints the composition object hierarchy of the resulting visual tree. -// -// Usage: LottieRuntimeDump.exe - -#include - -#include - -#include -#include -#include - -#include -#include -#include -#include - -using namespace winrt; -using namespace winrt::Windows::UI::Composition; -using namespace winrt::Windows::Foundation; -using namespace winrt::Windows::Foundation::Collections; - -namespace -{ - std::vector ReadFileBytes(wchar_t const* path) - { - std::ifstream file(path, std::ios::binary | std::ios::ate); - if (!file) - { - throw std::runtime_error("Could not open input file."); - } - - auto const size = static_cast(file.tellg()); - std::vector result(size); - file.seekg(0); - file.read(reinterpret_cast(result.data()), static_cast(size)); - return result; - } - - void PrintLine(int depth, std::wstring_view text) - { - std::wstring indent(static_cast(depth) * 2, L' '); - std::wcout << indent << text << L"\n"; - } - - hstring RuntimeClassName(IInspectable const& node) - { - if (node == nullptr) - { - return L"(null)"; - } - - return winrt::get_class_name(node); - } - - hstring CommentOf(CompositionObject const& node) - { - auto comment = node.Comment(); - return comment.empty() ? L"" : (L" \"" + comment + L"\""); - } - - void DumpBrush(CompositionBrush const& brush, int depth); - void DumpVisual(Visual const& visual, int depth); - void DumpShape(CompositionShape const& shape, int depth); - - void DumpShape(CompositionShape const& shape, int depth) - { - if (shape == nullptr) - { - return; - } - - PrintLine(depth, RuntimeClassName(shape) + CommentOf(shape)); - - if (auto containerShape = shape.try_as()) - { - for (auto const& child : containerShape.Shapes()) - { - DumpShape(child, depth + 1); - } - } - - if (auto spriteShape = shape.try_as()) - { - if (auto fill = spriteShape.FillBrush()) - { - PrintLine(depth + 1, L"FillBrush:"); - DumpBrush(fill, depth + 2); - } - - if (auto stroke = spriteShape.StrokeBrush()) - { - PrintLine(depth + 1, L"StrokeBrush:"); - DumpBrush(stroke, depth + 2); - } - } - } - - void DumpBrush(CompositionBrush const& brush, int depth) - { - if (brush == nullptr) - { - return; - } - - PrintLine(depth, RuntimeClassName(brush) + CommentOf(brush)); - - if (auto linearGradient = brush.try_as()) - { - for (auto const& stop : linearGradient.ColorStops()) - { - PrintLine(depth + 1, L"ColorStop " + RuntimeClassName(stop)); - } - } - } - - void DumpVisual(Visual const& visual, int depth) - { - if (visual == nullptr) - { - return; - } - - PrintLine(depth, RuntimeClassName(visual) + CommentOf(visual)); - - if (auto spriteVisual = visual.try_as()) - { - if (auto brush = spriteVisual.Brush()) - { - PrintLine(depth + 1, L"Brush:"); - DumpBrush(brush, depth + 2); - } - - if (auto shapeVisual = visual.try_as()) - { - for (auto const& shape : shapeVisual.Shapes()) - { - DumpShape(shape, depth + 1); - } - } - } - else if (auto shapeVisual = visual.try_as()) - { - for (auto const& shape : shapeVisual.Shapes()) - { - DumpShape(shape, depth + 1); - } - } - - if (auto containerVisual = visual.try_as()) - { - for (auto const& child : containerVisual.Children()) - { - DumpVisual(child, depth + 1); - } - } - } -} - -int wmain(int argc, wchar_t* argv[]) -{ - winrt::init_apartment(); - - if (argc != 2) - { - std::wcerr << L"Usage: LottieRuntimeDump.exe \n"; - return 1; - } - - try - { - auto bytes = ReadFileBytes(argv[1]); - - Compositor compositor; - auto root = CommunityToolkit::WinUI::Lottie::LoadComposition( - compositor, - std::span(bytes.data(), bytes.size())); - - std::wcout << L"Composition object hierarchy:\n"; - DumpVisual(root, 0); - } - catch (winrt::hresult_error const& e) - { - std::wcerr << L"Failed to load composition: " << e.message().c_str() << L"\n"; - return 1; - } - catch (std::exception const& e) - { - std::cerr << "Failed: " << e.what() << "\n"; - return 1; - } - - return 0; -} diff --git a/dlls/LottieRuntimeTool/LottieRuntime.cpp b/dlls/LottieRuntimeTool/LottieRuntime.cpp new file mode 100644 index 00000000..ad8b8daa --- /dev/null +++ b/dlls/LottieRuntimeTool/LottieRuntime.cpp @@ -0,0 +1,363 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// Loads a serialized Lottie composition through the registration-free COM surface. + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace winrt; +using namespace winrt::Windows::UI::Composition; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; + +namespace +{ + winrt::Windows::System::DispatcherQueueController CreateDispatcherQueue() + { + DispatcherQueueOptions const options{ + sizeof(DispatcherQueueOptions), + DQTYPE_THREAD_CURRENT, + DQTAT_COM_NONE, + }; + + winrt::Windows::System::DispatcherQueueController controller{ nullptr }; + winrt::check_hresult(CreateDispatcherQueueController( + options, + reinterpret_cast(winrt::put_abi(controller)))); + return controller; + } + + winrt::com_ptr CreateLoader() + { + wchar_t moduleDirectory[MAX_PATH]{}; + winrt::check_bool(GetModuleFileNameW(nullptr, moduleDirectory, MAX_PATH)); + winrt::check_bool(PathRemoveFileSpecW(moduleDirectory)); + + auto const manifestPath = + std::wstring(moduleDirectory) + L"\\LottieRuntime.manifest"; + + ACTCTXW context{ sizeof(ACTCTXW) }; + context.lpSource = manifestPath.c_str(); + context.lpAssemblyDirectory = moduleDirectory; + context.dwFlags = ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID; + + auto const activationContext = CreateActCtxW(&context); + winrt::check_bool(activationContext != INVALID_HANDLE_VALUE); + + ULONG_PTR cookie{}; + winrt::check_bool(ActivateActCtx(activationContext, &cookie)); + + winrt::com_ptr loader; + auto const result = CoCreateInstance( + CLSID_LottieCompositionLoader, + nullptr, + CLSCTX_INPROC_SERVER, + __uuidof(ILottieCompositionLoader), + loader.put_void()); + + DeactivateActCtx(0, cookie); + ReleaseActCtx(activationContext); + winrt::check_hresult(result); + return loader; + } + + std::vector ReadFileBytes(wchar_t const* path) + { + std::ifstream file(path, std::ios::binary | std::ios::ate); + if (!file) + { + throw std::runtime_error("Could not open input file."); + } + + auto const size = static_cast(file.tellg()); + std::vector result(size); + file.seekg(0); + file.read(reinterpret_cast(result.data()), static_cast(size)); + return result; + } + + void PrintLine(int depth, std::wstring_view text) + { + std::wstring indent(static_cast(depth) * 2, L' '); + std::wcout << indent << text << L"\n"; + } + + hstring RuntimeClassName(winrt::Windows::Foundation::IInspectable const& node) + { + if (node == nullptr) + { + return L"(null)"; + } + + return winrt::get_class_name(node); + } + + hstring CommentOf(CompositionObject const& node) + { + auto comment = node.Comment(); + return comment.empty() ? L"" : (L" \"" + comment + L"\""); + } + + void DumpBrush(CompositionBrush const& brush, int depth); + void DumpVisual(Visual const& visual, int depth); + void DumpShape(CompositionShape const& shape, int depth); + + void DumpShape(CompositionShape const& shape, int depth) + { + if (shape == nullptr) + { + return; + } + + PrintLine(depth, RuntimeClassName(shape) + CommentOf(shape)); + + if (auto containerShape = shape.try_as()) + { + for (auto const& child : containerShape.Shapes()) + { + DumpShape(child, depth + 1); + } + } + + if (auto spriteShape = shape.try_as()) + { + if (auto fill = spriteShape.FillBrush()) + { + PrintLine(depth + 1, L"FillBrush:"); + DumpBrush(fill, depth + 2); + } + + if (auto stroke = spriteShape.StrokeBrush()) + { + PrintLine(depth + 1, L"StrokeBrush:"); + DumpBrush(stroke, depth + 2); + } + } + } + + void DumpBrush(CompositionBrush const& brush, int depth) + { + if (brush == nullptr) + { + return; + } + + PrintLine(depth, RuntimeClassName(brush) + CommentOf(brush)); + + if (auto linearGradient = brush.try_as()) + { + for (auto const& stop : linearGradient.ColorStops()) + { + PrintLine(depth + 1, L"ColorStop " + RuntimeClassName(stop)); + } + } + } + + void DumpVisual(Visual const& visual, int depth) + { + if (visual == nullptr) + { + return; + } + + PrintLine(depth, RuntimeClassName(visual) + CommentOf(visual)); + + if (auto spriteVisual = visual.try_as()) + { + if (auto brush = spriteVisual.Brush()) + { + PrintLine(depth + 1, L"Brush:"); + DumpBrush(brush, depth + 2); + } + + if (auto shapeVisual = visual.try_as()) + { + for (auto const& shape : shapeVisual.Shapes()) + { + DumpShape(shape, depth + 1); + } + } + } + else if (auto shapeVisual = visual.try_as()) + { + for (auto const& shape : shapeVisual.Shapes()) + { + DumpShape(shape, depth + 1); + } + } + + if (auto containerVisual = visual.try_as()) + { + for (auto const& child : containerVisual.Children()) + { + DumpVisual(child, depth + 1); + } + } + } + + LRESULT CALLBACK WindowProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam) + { + if (message == WM_DESTROY) + { + PostQuitMessage(0); + return 0; + } + + return DefWindowProcW(window, message, wParam, lParam); + } + + void ShowVisual(Visual const& root) + { + auto const instance = GetModuleHandleW(nullptr); + wchar_t const className[] = L"LottieRuntimeWindow"; + + WNDCLASSW windowClass{}; + windowClass.lpfnWndProc = WindowProc; + windowClass.hInstance = instance; + windowClass.hCursor = LoadCursorW(nullptr, IDC_ARROW); + windowClass.hbrBackground = static_cast(GetStockObject(WHITE_BRUSH)); + windowClass.lpszClassName = className; + winrt::check_bool(RegisterClassW(&windowClass) != 0); + + auto const window = CreateWindowExW( + 0, + className, + L"LottieRuntime", + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, + CW_USEDEFAULT, + 900, + 700, + nullptr, + nullptr, + instance, + nullptr); + winrt::check_bool(window != nullptr); + + auto compositor = root.Compositor(); + auto compositorInterop = + compositor.as(); + winrt::Windows::UI::Composition::Desktop::DesktopWindowTarget target{ nullptr }; + winrt::check_hresult(compositorInterop->CreateDesktopWindowTarget( + window, + false, + reinterpret_cast( + winrt::put_abi(target)))); + target.Root(root); + + auto progress = compositor.CreateScalarKeyFrameAnimation(); + progress.InsertKeyFrame(0.0f, 0.0f); + progress.InsertKeyFrame(1.0f, 1.0f); + progress.Duration(std::chrono::seconds(5)); + progress.IterationBehavior(AnimationIterationBehavior::Forever); + root.Properties().StartAnimation(L"Progress", progress); + + ShowWindow(window, SW_SHOW); + UpdateWindow(window); + + MSG message{}; + while (GetMessageW(&message, nullptr, 0, 0) > 0) + { + TranslateMessage(&message); + DispatchMessageW(&message); + } + } +} + +int wmain(int argc, wchar_t* argv[]) +{ + if (argc < 2 || argc > 3) + { + std::wcerr << L"Usage: LottieRuntime.exe [--dump|--show]\n"; + return 1; + } + + try + { + enum class Mode + { + Validate, + Dump, + Show, + }; + + auto mode = Mode::Validate; + if (argc == 3) + { + if (std::wstring_view(argv[2]) == L"--dump") + { + mode = Mode::Dump; + } + else if (std::wstring_view(argv[2]) == L"--show") + { + mode = Mode::Show; + } + else + { + throw std::invalid_argument("Mode must be --dump or --show."); + } + } + + winrt::check_hresult(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED)); + auto dispatcherQueueController = CreateDispatcherQueue(); + auto bytes = ReadFileBytes(argv[1]); + + auto loader = CreateLoader(); + Visual root{ nullptr }; + winrt::check_hresult(loader->LoadComposition( + static_cast(bytes.size()), + reinterpret_cast(bytes.data()), + winrt::guid_of(), + winrt::put_abi(root))); + + switch (mode) + { + case Mode::Validate: + std::wcout << L"Composition loaded successfully.\n"; + break; + case Mode::Dump: + std::wcout << L"Composition object hierarchy:\n"; + DumpVisual(root, 0); + break; + case Mode::Show: + ShowVisual(root); + break; + } + + root = nullptr; + loader = nullptr; + dispatcherQueueController.ShutdownQueueAsync(); + } + catch (winrt::hresult_error const& e) + { + std::wcerr << L"Failed to load composition: " << e.message().c_str() << L"\n"; + return 1; + } + catch (std::exception const& e) + { + std::cerr << "Failed: " << e.what() << "\n"; + return 1; + } + + return 0; +} diff --git a/dlls/LottieRuntimeDump/LottieRuntimeDump.vcxproj b/dlls/LottieRuntimeTool/LottieRuntime.vcxproj similarity index 73% rename from dlls/LottieRuntimeDump/LottieRuntimeDump.vcxproj rename to dlls/LottieRuntimeTool/LottieRuntime.vcxproj index 33e8a74c..ebe01e7a 100644 --- a/dlls/LottieRuntimeDump/LottieRuntimeDump.vcxproj +++ b/dlls/LottieRuntimeTool/LottieRuntime.vcxproj @@ -21,7 +21,8 @@ {9A7B4C2E-1F3D-4B7A-9C3E-2D5A8F0B2E48} - LottieRuntimeDump + LottieRuntime + LottieRuntime Application v143 Unicode @@ -49,25 +50,32 @@ Console - windowsapp.lib;%(AdditionalDependencies) + CoreMessaging.lib;ole32.lib;shlwapi.lib;user32.lib;windowsapp.lib;%(AdditionalDependencies) - + {6E0A5B4C-2F1D-4B7A-9C3E-1D5A8F0B2E47} + false - + + + + - + - + diff --git a/dlls/LottieRuntimeTool/README.md b/dlls/LottieRuntimeTool/README.md new file mode 100644 index 00000000..289bbf05 --- /dev/null +++ b/dlls/LottieRuntimeTool/README.md @@ -0,0 +1,54 @@ +# LottieRuntime tool + +`LottieRuntime.exe` loads FlatBuffer compositions through the registration-free +`ILottieCompositionLoader` COM interface. + +## Build + +```powershell +& "C:\Program Files\Microsoft Visual Studio\18\Professional\Common7\Tools\Launch-VsDevShell.ps1" -SkipAutomaticLocation +msbuild .\dlls\LottieRuntimeTool\LottieRuntime.vcxproj /p:Platform=x64 /p:Configuration=Release +``` + +The executable is written to: + +```text +dlls\LottieRuntimeTool\x64\Release\LottieRuntime.exe +``` + +## Convert Lottie JSON + +```powershell +$output = Join-Path $env:TEMP "lottie-runtime" +New-Item -ItemType Directory -Force $output | Out-Null + +dotnet run --project .\LottieGen\DotnetTool\LottieGen.csproj -- ` + -InputFile .\LottieSamples\Assets\LottieLogo1.json ` + -Language flatbuffer ` + -OutputFolder $output +``` + +The command writes `$output\LottieLogo1.lcomp`. + +## Load, inspect, or display + +Validate the composition: + +```powershell +.\dlls\LottieRuntimeTool\x64\Release\LottieRuntime.exe ` + "$output\LottieLogo1.lcomp" +``` + +Print the interpreted Composition object hierarchy: + +```powershell +.\dlls\LottieRuntimeTool\x64\Release\LottieRuntime.exe ` + "$output\LottieLogo1.lcomp" --dump +``` + +Display and animate the interpreted visual: + +```powershell +.\dlls\LottieRuntimeTool\x64\Release\LottieRuntime.exe ` + "$output\LottieLogo1.lcomp" --show +``` From aa21a6d6b7d3dfdde82a4d9d87a48be130ce908b Mon Sep 17 00:00:00 2001 From: Jon Wiswall Date: Tue, 1 Sep 2026 15:58:24 -0700 Subject: [PATCH 25/39] Optimize LottieRuntime tool for size Disable incremental linking and enable /O1, function-level linking, string pooling, LTCG, reference elimination, and COMDAT folding for Release builds. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 58fac82f-0489-4c57-a18d-2b7ba273faa6 --- dlls/LottieRuntimeTool/LottieRuntime.vcxproj | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/dlls/LottieRuntimeTool/LottieRuntime.vcxproj b/dlls/LottieRuntimeTool/LottieRuntime.vcxproj index ebe01e7a..ee8b65c3 100644 --- a/dlls/LottieRuntimeTool/LottieRuntime.vcxproj +++ b/dlls/LottieRuntimeTool/LottieRuntime.vcxproj @@ -37,6 +37,7 @@ true false true + false @@ -54,6 +55,20 @@ + + + MinSpace + true + true + true + + + UseLinkTimeCodeGeneration + true + true + + + From 93b1cb90959b19913ff9a94a6b349dfda49c6841 Mon Sep 17 00:00:00 2001 From: Jon Wiswall Date: Tue, 1 Sep 2026 16:37:12 -0700 Subject: [PATCH 26/39] Validate buffer UAP requirements Keep the runtime loadable on Windows 10 1809 while rejecting compositions whose declared UniversalApiContract version is unavailable before graph hydration. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 58fac82f-0489-4c57-a18d-2b7ba273faa6 --- dlls/LottieRuntime/LottieRuntime.cpp | 10 ++++++++++ dlls/LottieRuntime/LottieRuntime.vcxproj | 5 ++--- dlls/LottieRuntime/README.md | 3 ++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/dlls/LottieRuntime/LottieRuntime.cpp b/dlls/LottieRuntime/LottieRuntime.cpp index e059bbcc..209b9655 100644 --- a/dlls/LottieRuntime/LottieRuntime.cpp +++ b/dlls/LottieRuntime/LottieRuntime.cpp @@ -48,6 +48,7 @@ #include #include +#include #include #include #include @@ -2139,6 +2140,15 @@ namespace CommunityToolkit::WinUI::Lottie ThrowUnsupported(); } + auto const requiredUapVersion = root->required_uap_version(); + if (requiredUapVersion != 0 && + !winrt::Windows::Foundation::Metadata::ApiInformation::IsApiContractPresent( + L"Windows.Foundation.UniversalApiContract", + requiredUapVersion)) + { + ThrowUnsupported(); + } + return Interpreter(compositor, *root).Run(); } diff --git a/dlls/LottieRuntime/LottieRuntime.vcxproj b/dlls/LottieRuntime/LottieRuntime.vcxproj index 075df1be..5cad979f 100644 --- a/dlls/LottieRuntime/LottieRuntime.vcxproj +++ b/dlls/LottieRuntime/LottieRuntime.vcxproj @@ -27,9 +27,8 @@ Unicode 10.0.22621.0 - + 10.0.17763.0 $(MSBuildThisFileDirectory)..\..\external\flatbuffers\include diff --git a/dlls/LottieRuntime/README.md b/dlls/LottieRuntime/README.md index c5784f88..fcac423d 100644 --- a/dlls/LottieRuntime/README.md +++ b/dlls/LottieRuntime/README.md @@ -114,7 +114,8 @@ shared geometry remains shared and cycles are rejected. **Untrusted input.** A buffer may have come from a file or a download. It is checked for the `LCMP` identifier, run through the FlatBuffers verifier, and every index read out of it is range checked. A malformed buffer throws -`winrt::hresult_invalid_argument`; it never reads outside the buffer. +`winrt::hresult_invalid_argument`; it never reads outside the buffer. The declared +Universal API contract is checked before graph hydration. ## Dependencies From f215215051223fda65a9f5892f8735c910c40eee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:17:45 +0000 Subject: [PATCH 27/39] Add a managed FlatBuffer interpreter with dump-and-compare tests Co-authored-by: jonwis <18537118+jonwis@users.noreply.github.com> --- dlls/LottieRuntime/README.md | 6 + .../LottieRuntime/CompositionInterpreter.cs | 1798 +++++++++++++++++ source/LottieRuntime/LottieRuntime.projitems | 14 + source/LottieRuntime/LottieRuntime.shproj | 13 + source/LottieRuntime/README.md | 108 + .../CompDataFlatbuffer.Tests.csproj | 1 + .../CompositionApiStandIns.cs | 1112 ++++++++++ .../CompDataFlatbuffer.Tests/CoverageTests.cs | 17 + .../InterpretedTreeDumper.cs | 805 ++++++++ .../InterpreterTests.cs | 214 ++ 10 files changed, 4088 insertions(+) create mode 100644 source/LottieRuntime/CompositionInterpreter.cs create mode 100644 source/LottieRuntime/LottieRuntime.projitems create mode 100644 source/LottieRuntime/LottieRuntime.shproj create mode 100644 source/LottieRuntime/README.md create mode 100644 tests/CompDataFlatbuffer.Tests/CompositionApiStandIns.cs create mode 100644 tests/CompDataFlatbuffer.Tests/InterpretedTreeDumper.cs create mode 100644 tests/CompDataFlatbuffer.Tests/InterpreterTests.cs diff --git a/dlls/LottieRuntime/README.md b/dlls/LottieRuntime/README.md index fcac423d..ac9a5d20 100644 --- a/dlls/LottieRuntime/README.md +++ b/dlls/LottieRuntime/README.md @@ -153,6 +153,12 @@ Include `LottieRuntime.h` and call `LoadComposition` with the application's ### Managed applications +A managed application can also use the C# interpreter in +[`source/LottieRuntime`](../../source/LottieRuntime/README.md), which reads the same +buffer and performs the same validation, and needs no COM activation. Use the DLL +instead when the application should not take a Win2D dependency, or when the same +animation is loaded from both native and managed code. + Add the packaged `LottieRuntime.vcxproj` to the solution. The project builds `LottieRuntime.dll`, including the `ILottieCompositionLoader` implementation. Copy both files into every managed application output directory: diff --git a/source/LottieRuntime/CompositionInterpreter.cs b/source/LottieRuntime/CompositionInterpreter.cs new file mode 100644 index 00000000..123061a1 --- /dev/null +++ b/source/LottieRuntime/CompositionInterpreter.cs @@ -0,0 +1,1798 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.IO; +using System.Numerics; +using System.Runtime.InteropServices.WindowsRuntime; +using CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer; +using Google.FlatBuffers; +using Microsoft.Graphics.Canvas.Geometry; +using Windows.Foundation.Metadata; +using Windows.Graphics.Effects; +using Fb = CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema; +using Mgc = Microsoft.Graphics.Canvas; +using Mgce = Microsoft.Graphics.Canvas.Effects; +#if WINAPPSDK +using Wc = Microsoft.UI.Composition; +using Wm = Microsoft.UI.Xaml.Media; +#else +using Wc = Windows.UI.Composition; +using Wm = Windows.UI.Xaml.Media; +#endif +using Wui = Windows.UI; + +namespace CommunityToolkit.WinUI.Lottie.LottieRuntime +{ + /// + /// Builds a tree directly from a serialized composition. + /// + /// + /// This is the managed counterpart of the native interpreter in + /// dlls/LottieRuntime. Both read the same FlatBuffer and build the same + /// visual tree, and both are structured the same way, so that a change to the + /// format only ever has to be made twice rather than in a different shape each + /// time. + /// + /// It differs from Instantiator in that it never builds a WinCompData + /// graph: the buffer is read in place and the composition objects are created as + /// the walk reaches them. That makes an animation data rather than code, so it can + /// be downloaded, themed or swapped without a rebuild, and the amount of code in + /// the application does not grow with the number of animations. + /// + /// A buffer is untrusted input. It is checked for the LCMP identifier, run + /// through the FlatBuffers verifier, and every index read out of it is range + /// checked. A malformed buffer is reported as a + /// ; a buffer that needs something this + /// build cannot provide is reported as a . + /// Neither reads outside the buffer. + /// +#if PUBLIC_LottieRuntime + public +#endif + sealed class CompositionInterpreter + { + // The smallest buffer that could possibly be a composition: a root offset + // followed by the four byte file identifier. Checked before the identifier is + // read so that a short buffer is reported as a format error. + const int MinimumBufferLength = 8; + + readonly Wc.Compositor _compositor; + readonly Fb.LottieComposition _root; + readonly string[] _strings; + + // Realization caches. These are indexed identically to the node vectors in the + // buffer, so looking up an already realized node is an array index rather than + // a hash lookup, and a node that is reached by more than one path is realized + // exactly once. Each holds the least derived type that a reference to that + // category needs, which is what keeps the interpreter free of downcasts. + readonly Wc.Visual?[] _visuals; + readonly Wc.CompositionShape?[] _shapes; + readonly Wc.CompositionGeometry?[] _geometries; + readonly CanvasGeometry?[] _canvasGeometries; + readonly CanvasGeometryState[] _canvasGeometryStates; + readonly Wc.CompositionBrush?[] _brushes; + readonly Wc.CompositionColorGradientStop?[] _gradientStops; + readonly Wc.CompositionViewBox?[] _viewBoxes; + readonly Wc.CompositionClip?[] _clips; + readonly Wc.CompositionShadow?[] _shadows; + readonly Wc.ICompositionSurface?[] _surfaces; + readonly IGraphicsEffect?[] _effects; + readonly Wc.CompositionEasingFunction?[] _easings; + readonly Wc.CompositionAnimation?[] _animations; + readonly Wc.CompositionPropertySet?[] _propertySets; + readonly Wc.AnimationController?[] _controllers; + + CompositionInterpreter(Wc.Compositor compositor, Fb.LottieComposition root) + { + _compositor = compositor; + _root = root; + + _strings = new string[root.StringsLength]; + for (var i = 0; i < _strings.Length; i++) + { + _strings[i] = root.Strings(i) ?? string.Empty; + } + + _visuals = new Wc.Visual?[root.VisualsLength]; + _shapes = new Wc.CompositionShape?[root.ShapesLength]; + _geometries = new Wc.CompositionGeometry?[root.GeometriesLength]; + _canvasGeometries = new CanvasGeometry?[root.CanvasGeometriesLength]; + _canvasGeometryStates = new CanvasGeometryState[root.CanvasGeometriesLength]; + _brushes = new Wc.CompositionBrush?[root.BrushesLength]; + _gradientStops = new Wc.CompositionColorGradientStop?[root.GradientStopsLength]; + _viewBoxes = new Wc.CompositionViewBox?[root.ViewBoxesLength]; + _clips = new Wc.CompositionClip?[root.ClipsLength]; + _shadows = new Wc.CompositionShadow?[root.ShadowsLength]; + _surfaces = new Wc.ICompositionSurface?[root.SurfacesLength]; + _effects = new IGraphicsEffect?[root.EffectsLength]; + _easings = new Wc.CompositionEasingFunction?[root.EasingsLength]; + _animations = new Wc.CompositionAnimation?[root.AnimationsLength]; + _propertySets = new Wc.CompositionPropertySet?[root.PropertySetsLength]; + _controllers = new Wc.AnimationController?[root.ControllersLength]; + } + + // A canvas geometry is built from other canvas geometries, so the walk has to + // be able to tell a shared geometry from a cycle. + enum CanvasGeometryState + { + Unrealized, + Realizing, + Realized, + } + + /// + /// Builds the visual tree described by a serialized composition. + /// + /// The compositor that creates the objects. + /// A buffer produced by CompositionSerializer. + /// The root of the visual tree. + /// The buffer is not a well formed composition. + /// The composition needs a newer schema or a newer version of Windows. + public static Wc.Visual LoadComposition(Wc.Compositor compositor, ReadOnlySpan bytes) + { + if (compositor is null) + { + throw new ArgumentNullException(nameof(compositor)); + } + + // The buffer is untrusted, so nothing is read out of it until the verifier + // has agreed that every offset in it is inside it. + var buffer = new ByteBuffer(bytes.ToArray()); + + if (bytes.Length < MinimumBufferLength || + !Fb.LottieComposition.LottieCompositionBufferHasIdentifier(buffer)) + { + throw new FlatBufferFormatException("The buffer is not a composition."); + } + + Fb.LottieComposition root; + + // Verification proves that the buffer is structurally sound, but it cannot + // prove that the indices stored in it point at the right things. Neither + // the verifier nor the reader reports a malformed buffer purely by + // returning false; both also throw. Everything that reads the buffer is + // therefore done inside a handler, so that a corrupt buffer always surfaces + // as a format error and never as an exception that a caller could not have + // anticipated. + try + { + if (!Fb.LottieComposition.VerifyLottieComposition(buffer)) + { + throw new FlatBufferFormatException("The buffer is not a valid composition."); + } + + root = Fb.LottieComposition.GetRootAsLottieComposition(buffer); + } + catch (Exception e) when (IsBufferFailure(e)) + { + throw new FlatBufferFormatException("The buffer is malformed.", e); + } + + // A newer schema may store things in fields this build does not read, so a + // buffer that declares one is refused rather than silently misinterpreted. + if (root.SchemaVersion > Format.Version) + { + throw new NotSupportedException( + $"Schema version {root.SchemaVersion} is newer than the supported version {Format.Version}."); + } + + // The translator records the API contract that the graph needs. Checking it + // before anything is created means that an animation which cannot run on + // this version of Windows is refused rather than half built. + var requiredUapVersion = root.RequiredUapVersion; + if (requiredUapVersion != 0 && + !ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", requiredUapVersion)) + { + throw new NotSupportedException( + $"The composition requires UniversalApiContract {requiredUapVersion}."); + } + + try + { + var interpreter = new CompositionInterpreter(compositor, root); + + return interpreter.GetVisual(root.RootVisual) + ?? throw new FlatBufferFormatException("The composition has no root visual."); + } + catch (Exception e) when (IsBufferFailure(e)) + { + throw new FlatBufferFormatException("The buffer is malformed.", e); + } + } + + /// + /// Returns the property set that drives an interpreted composition. + /// + /// A root returned by . + /// The property set that holds the animation's Progress property. + public static Wc.CompositionPropertySet ProgressPropertySet(Wc.Visual root) + { + if (root is null) + { + throw new ArgumentNullException(nameof(root)); + } + + return root.Properties; + } + + // The failures that reading a structurally valid but semantically wrong buffer + // can produce. Reading past the end of the buffer is not a safety problem + // because ByteBuffer is bounds checked; the only thing being corrected here is + // the type of the exception. + static bool IsBufferFailure(Exception e) + => e is ArgumentException or IndexOutOfRangeException or + InvalidOperationException or OverflowException or + FormatException or NullReferenceException; + + // --------------------------------------------------------------------- + // Table accessors. Every index is bounds checked, because a buffer that + // passes verification can still contain an out of range index. + // --------------------------------------------------------------------- + static T Require(T? value, string what) + where T : struct + => value ?? throw new FlatBufferFormatException($"A {what} is missing."); + + static uint RequireIndex(uint index, string what) + => index != Format.NullIndex + ? index + : throw new FlatBufferFormatException($"A required {what} is missing."); + + string? GetString(uint index) + => index == Format.NullIndex + ? null + : index < (uint)_strings.Length + ? _strings[index] + : throw new FlatBufferFormatException($"String index {index} is out of range."); + + string GetRequiredString(uint index) + => GetString(index) ?? throw new FlatBufferFormatException("A required string is missing."); + + // --------------------------------------------------------------------- + // Shared state. + // --------------------------------------------------------------------- + // Applies the state that every CompositionObject has. Called after the object + // has been placed in its realization cache, so that a cycle terminates. + void InitializeCompositionObject(Wc.CompositionObject target, Fb.CompObj? source) + { + if (source is null) + { + return; + } + + var value = source.Value; + + if (GetString(value.Comment) is string comment) + { + target.Comment = comment; + } + + // The property set is realized through the object that owns it, so that the + // owner and its property set can never both create it. + if (value.Properties != Format.NullIndex) + { + RealizePropertySet(value.Properties, target.Properties); + } + + // Animations are started last, because setting a property stops any + // animation that is running on it. + for (var i = 0; i < value.AnimatorsLength; i++) + { + var animator = Require(value.Animators(i), "animator"); + var property = GetRequiredString(animator.Property); + var animation = GetAnimation(animator.Animation) + ?? throw new FlatBufferFormatException($"Animator '{property}' has no animation."); + + // A non custom controller comes into existence when the animation is + // started, so only a custom one is passed in. Either way the resulting + // controller is cached under the index that the buffer gave it. + var controllerIndex = animator.Controller; + var customController = controllerIndex != Format.NullIndex && IsCustomController(controllerIndex) + ? GetController(controllerIndex) + : null; + + // An expression animation is driven by its inputs and so cannot have a + // controller. This is checked here in order to reject the buffer rather + // than build an unusable tree. + if (customController is not null && animation is Wc.ExpressionAnimation) + { + throw new FlatBufferFormatException( + $"Animator '{property}' gives a controller to an expression animation."); + } + + if (customController is null) + { + target.StartAnimation(property, animation); + } + else + { + target.StartAnimation(property, animation, customController); + } + + if (controllerIndex != Format.NullIndex && customController is null) + { + RealizeController( + controllerIndex, + target.TryGetAnimationController(property) + ?? throw new FlatBufferFormatException($"Animator '{property}' has no controller.")); + } + } + } + + bool IsCustomController(uint index) + => GetControllerTable(index).IsCustom; + + Fb.Controller GetControllerTable(uint index) + => index < (uint)_controllers.Length + ? Require(_root.Controllers((int)index), "controller") + : throw new FlatBufferFormatException($"Controller index {index} is out of range."); + + void RealizeController(uint index, Wc.AnimationController controller) + { + if (_controllers[index] is not null) + { + return; + } + + _controllers[index] = controller; + + var table = GetControllerTable(index); + + if (table.IsPaused) + { + controller.Pause(); + } + + InitializeCompositionObject(controller, table.Base); + } + + Wc.AnimationController? GetController(uint index) + { + if (index == Format.NullIndex) + { + return null; + } + + var table = GetControllerTable(index); + + if (_controllers[index] is null) + { + if (table.IsCustom) + { + RealizeController(index, _compositor.CreateAnimationController()); + } + else + { + // A non custom controller comes into existence when its target + // object starts the animation, so realizing the target realizes the + // controller. + GetObjectReference(table.TargetObject); + } + } + + return _controllers[index] + ?? throw new FlatBufferFormatException($"Controller {index} could not be realized."); + } + + // --------------------------------------------------------------------- + // Property sets. + // --------------------------------------------------------------------- + void RealizePropertySet(uint index, Wc.CompositionPropertySet propertySet) + { + if (index >= (uint)_propertySets.Length) + { + throw new FlatBufferFormatException($"Property set index {index} is out of range."); + } + + if (_propertySets[index] is not null) + { + return; + } + + // Cached before the values are applied, because a value may be an + // expression that refers back to this property set. + _propertySets[index] = propertySet; + + var table = Require(_root.PropertySets((int)index), "property set"); + + for (var i = 0; i < table.ValuesLength; i++) + { + var value = Require(table.Values(i), "property value"); + var name = GetRequiredString(value.Name); + + switch (value.Type) + { + case Fb.PropertyValueType.Color: + propertySet.InsertColor(name, ToColor(Require(value.Color, "color"))); + break; + case Fb.PropertyValueType.Scalar: + propertySet.InsertScalar(name, value.Scalar); + break; + case Fb.PropertyValueType.Vector2: + propertySet.InsertVector2(name, ToVector2(value.Vector)); + break; + case Fb.PropertyValueType.Vector3: + propertySet.InsertVector3(name, ToVector3(value.Vector)); + break; + case Fb.PropertyValueType.Vector4: + propertySet.InsertVector4(name, ToVector4(value.Vector)); + break; + case Fb.PropertyValueType.None: + break; + default: + throw new FlatBufferFormatException($"Unsupported property value type {value.Type}."); + } + } + + InitializeCompositionObject(propertySet, table.Base); + } + + Wc.CompositionPropertySet? GetPropertySet(uint index) + { + if (index == Format.NullIndex) + { + return null; + } + + if (index >= (uint)_propertySets.Length) + { + throw new FlatBufferFormatException($"Property set index {index} is out of range."); + } + + if (_propertySets[index] is null) + { + var table = Require(_root.PropertySets((int)index), "property set"); + + if (table.Owner == Format.NullIndex) + { + // A property set with no owner is a standalone one, which is how the + // translator exposes theming properties. + RealizePropertySet(index, _compositor.CreatePropertySet()); + } + else + { + // Realizing the owner realizes its property set as a side effect. + GetObjectReference(table.Owner); + } + } + + return _propertySets[index] + ?? throw new FlatBufferFormatException($"Property set {index} could not be realized."); + } + + // Resolves a packed (category, index) reference. Only used by the fields that + // can point at an object of any category. + Wc.CompositionObject? GetObjectReference(uint reference) + { + if (reference == Format.NullIndex) + { + return null; + } + + var category = Format.UnpackCategory(reference); + var index = (uint)Format.UnpackIndex(reference); + + return category switch + { + Fb.ObjectCategory.Visual => GetVisual(index), + Fb.ObjectCategory.Shape => GetShape(index), + Fb.ObjectCategory.Geometry => GetGeometry(index), + Fb.ObjectCategory.Brush => GetBrush(index), + Fb.ObjectCategory.Animation => GetAnimation(index), + Fb.ObjectCategory.Easing => GetEasing(index), + Fb.ObjectCategory.PropertySet => GetPropertySet(index), + + // Only a visual surface is a CompositionObject; an image surface is not, + // and nothing can legitimately reference one this way. + Fb.ObjectCategory.Surface => GetSurface(index) as Wc.CompositionObject + ?? throw new FlatBufferFormatException("An image surface cannot be referenced."), + Fb.ObjectCategory.Clip => GetClip(index), + Fb.ObjectCategory.Controller => GetController(index), + Fb.ObjectCategory.Shadow => GetShadow(index), + Fb.ObjectCategory.GradientStop => GetGradientStop(index), + Fb.ObjectCategory.ViewBox => GetViewBox(index), + _ => throw new FlatBufferFormatException($"Unsupported object category {category}."), + }; + } + + // --------------------------------------------------------------------- + // Visuals. + // --------------------------------------------------------------------- + Wc.Visual? GetVisual(uint index) + { + if (index == Format.NullIndex) + { + return null; + } + + if (index >= (uint)_visuals.Length) + { + throw new FlatBufferFormatException($"Visual index {index} is out of range."); + } + + if (_visuals[index] is Wc.Visual cached) + { + return cached; + } + + var table = Require(_root.Visuals((int)index), "visual"); + + // The concrete type is known only here. Everything below this point, + // including the recursive walk into the children, uses a base type. + Wc.ContainerVisual result = table.Kind switch + { + Fb.VisualKind.Shape => _compositor.CreateShapeVisual(), + Fb.VisualKind.Sprite => _compositor.CreateSpriteVisual(), + Fb.VisualKind.Layer => _compositor.CreateLayerVisual(), + Fb.VisualKind.Container => _compositor.CreateContainerVisual(), + _ => throw new FlatBufferFormatException($"Unsupported visual kind {table.Kind}."), + }; + + // Cached before anything it refers to is realized, so that a cycle + // terminates. + _visuals[index] = result; + + if (table.BorderMode.HasValue) + { + result.BorderMode = (Wc.CompositionBorderMode)table.BorderMode.Value; + } + + if (ToVector3(table.CenterPoint) is Vector3 centerPoint) + { + result.CenterPoint = centerPoint; + } + + if (table.Clip != Format.NullIndex) + { + result.Clip = GetClip(table.Clip); + } + + if (table.IsVisible.HasValue) + { + result.IsVisible = table.IsVisible.Value; + } + + if (ToVector3(table.Offset) is Vector3 offset) + { + result.Offset = offset; + } + + if (table.Opacity.HasValue) + { + result.Opacity = table.Opacity.Value; + } + + if (table.RotationAngleInDegrees.HasValue) + { + result.RotationAngleInDegrees = table.RotationAngleInDegrees.Value; + } + + if (ToVector3(table.RotationAxis) is Vector3 rotationAxis) + { + result.RotationAxis = rotationAxis; + } + + if (ToVector3(table.Scale) is Vector3 scale) + { + result.Scale = scale; + } + + if (ToVector2(table.Size) is Vector2 size) + { + result.Size = size; + } + + if (ToMatrix4x4(table.TransformMatrix) is Matrix4x4 transformMatrix) + { + result.TransformMatrix = transformMatrix; + } + + switch (result) + { + case Wc.ShapeVisual shapeVisual: + for (var i = 0; i < table.ShapesLength; i++) + { + shapeVisual.Shapes.Add( + GetShape(table.Shapes(i)) + ?? throw new FlatBufferFormatException("A shape is missing.")); + } + + if (table.ViewBox != Format.NullIndex) + { + shapeVisual.ViewBox = GetViewBox(table.ViewBox); + } + + break; + case Wc.SpriteVisual spriteVisual: + if (table.Brush != Format.NullIndex) + { + spriteVisual.Brush = GetBrush(table.Brush); + } + + if (table.Shadow != Format.NullIndex) + { + spriteVisual.Shadow = GetShadow(table.Shadow); + } + + break; + case Wc.LayerVisual layerVisual: + if (table.Shadow != Format.NullIndex) + { + layerVisual.Shadow = GetShadow(table.Shadow); + } + + break; + } + + for (var i = 0; i < table.ChildrenLength; i++) + { + result.Children.InsertAtTop( + GetVisual(table.Children(i)) + ?? throw new FlatBufferFormatException("A visual child is missing.")); + } + + InitializeCompositionObject(result, table.Base); + return result; + } + + // --------------------------------------------------------------------- + // Shapes. + // --------------------------------------------------------------------- + Wc.CompositionShape? GetShape(uint index) + { + if (index == Format.NullIndex) + { + return null; + } + + if (index >= (uint)_shapes.Length) + { + throw new FlatBufferFormatException($"Shape index {index} is out of range."); + } + + if (_shapes[index] is Wc.CompositionShape cached) + { + return cached; + } + + var table = Require(_root.Shapes((int)index), "shape"); + + Wc.CompositionShape result = table.Kind switch + { + Fb.ShapeKind.Sprite => _compositor.CreateSpriteShape(), + Fb.ShapeKind.Container => _compositor.CreateContainerShape(), + _ => throw new FlatBufferFormatException($"Unsupported shape kind {table.Kind}."), + }; + + _shapes[index] = result; + + if (ToVector2(table.CenterPoint) is Vector2 centerPoint) + { + result.CenterPoint = centerPoint; + } + + if (ToVector2(table.Offset) is Vector2 offset) + { + result.Offset = offset; + } + + if (table.RotationAngleInDegrees.HasValue) + { + result.RotationAngleInDegrees = table.RotationAngleInDegrees.Value; + } + + if (ToVector2(table.Scale) is Vector2 scale) + { + result.Scale = scale; + } + + if (ToMatrix3x2(table.TransformMatrix) is Matrix3x2 transformMatrix) + { + result.TransformMatrix = transformMatrix; + } + + switch (result) + { + case Wc.CompositionContainerShape containerShape: + for (var i = 0; i < table.ShapesLength; i++) + { + containerShape.Shapes.Add( + GetShape(table.Shapes(i)) + ?? throw new FlatBufferFormatException("A shape is missing.")); + } + + break; + case Wc.CompositionSpriteShape spriteShape: + ApplySpriteShapeProperties(spriteShape, table); + break; + } + + InitializeCompositionObject(result, table.Base); + return result; + } + + void ApplySpriteShapeProperties(Wc.CompositionSpriteShape target, Fb.Shape table) + { + if (table.FillBrush != Format.NullIndex) + { + target.FillBrush = GetBrush(table.FillBrush); + } + + if (table.StrokeBrush != Format.NullIndex) + { + target.StrokeBrush = GetBrush(table.StrokeBrush); + } + + if (table.Geometry != Format.NullIndex) + { + target.Geometry = GetGeometry(table.Geometry); + } + + if (table.IsStrokeNonScaling.HasValue) + { + target.IsStrokeNonScaling = table.IsStrokeNonScaling.Value; + } + + if (table.StrokeDashOffset.HasValue) + { + target.StrokeDashOffset = table.StrokeDashOffset.Value; + } + + for (var i = 0; i < table.StrokeDashArrayLength; i++) + { + target.StrokeDashArray.Add(table.StrokeDashArray(i)); + } + + if (table.StrokeDashCap.HasValue) + { + target.StrokeDashCap = (Wc.CompositionStrokeCap)table.StrokeDashCap.Value; + } + + if (table.StrokeStartCap.HasValue) + { + target.StrokeStartCap = (Wc.CompositionStrokeCap)table.StrokeStartCap.Value; + } + + if (table.StrokeEndCap.HasValue) + { + target.StrokeEndCap = (Wc.CompositionStrokeCap)table.StrokeEndCap.Value; + } + + if (table.StrokeLineJoin.HasValue) + { + target.StrokeLineJoin = (Wc.CompositionStrokeLineJoin)table.StrokeLineJoin.Value; + } + + if (table.StrokeMiterLimit.HasValue) + { + target.StrokeMiterLimit = table.StrokeMiterLimit.Value; + } + + if (table.StrokeThickness.HasValue) + { + target.StrokeThickness = table.StrokeThickness.Value; + } + } + + // --------------------------------------------------------------------- + // Geometries. + // --------------------------------------------------------------------- + Wc.CompositionGeometry? GetGeometry(uint index) + { + if (index == Format.NullIndex) + { + return null; + } + + if (index >= (uint)_geometries.Length) + { + throw new FlatBufferFormatException($"Geometry index {index} is out of range."); + } + + if (_geometries[index] is Wc.CompositionGeometry cached) + { + return cached; + } + + var table = Require(_root.Geometries((int)index), "geometry"); + + Wc.CompositionGeometry result = table.Kind switch + { + Fb.GeometryKind.Rectangle => _compositor.CreateRectangleGeometry(), + Fb.GeometryKind.RoundedRectangle => _compositor.CreateRoundedRectangleGeometry(), + Fb.GeometryKind.Ellipse => _compositor.CreateEllipseGeometry(), + Fb.GeometryKind.Path => _compositor.CreatePathGeometry(), + _ => throw new FlatBufferFormatException($"Unsupported geometry kind {table.Kind}."), + }; + + _geometries[index] = result; + + if (table.TrimStart.HasValue) + { + result.TrimStart = table.TrimStart.Value; + } + + if (table.TrimEnd.HasValue) + { + result.TrimEnd = table.TrimEnd.Value; + } + + if (table.TrimOffset.HasValue) + { + result.TrimOffset = table.TrimOffset.Value; + } + + switch (result) + { + case Wc.CompositionPathGeometry pathGeometry: + var path = GetCanvasGeometry(table.Path); + pathGeometry.Path = path is null ? null : new Wc.CompositionPath(path); + break; + case Wc.CompositionRoundedRectangleGeometry roundedRectangle: + if (ToVector2(table.Offset) is Vector2 roundedOffset) + { + roundedRectangle.Offset = roundedOffset; + } + + if (ToVector2(table.Size) is Vector2 roundedSize) + { + roundedRectangle.Size = roundedSize; + } + + roundedRectangle.CornerRadius = ToVector2(table.CornerRadius) ?? Vector2.Zero; + break; + case Wc.CompositionRectangleGeometry rectangle: + if (ToVector2(table.Offset) is Vector2 rectangleOffset) + { + rectangle.Offset = rectangleOffset; + } + + if (ToVector2(table.Size) is Vector2 rectangleSize) + { + rectangle.Size = rectangleSize; + } + + break; + case Wc.CompositionEllipseGeometry ellipse: + ellipse.Center = ToVector2(table.Center) ?? Vector2.Zero; + ellipse.Radius = ToVector2(table.Radius) ?? Vector2.Zero; + break; + } + + InitializeCompositionObject(result, table.Base); + return result; + } + + // --------------------------------------------------------------------- + // Brushes. + // --------------------------------------------------------------------- + Wc.CompositionBrush? GetBrush(uint index) + { + if (index == Format.NullIndex) + { + return null; + } + + if (index >= (uint)_brushes.Length) + { + throw new FlatBufferFormatException($"Brush index {index} is out of range."); + } + + if (_brushes[index] is Wc.CompositionBrush cached) + { + return cached; + } + + var table = Require(_root.Brushes((int)index), "brush"); + + Wc.CompositionBrush result; + switch (table.Kind) + { + case Fb.BrushKind.Color: + result = _compositor.CreateColorBrush(); + break; + case Fb.BrushKind.LinearGradient: + result = _compositor.CreateLinearGradientBrush(); + break; + case Fb.BrushKind.RadialGradient: + result = _compositor.CreateRadialGradientBrush(); + break; + case Fb.BrushKind.Mask: + result = _compositor.CreateMaskBrush(); + break; + case Fb.BrushKind.Surface: + // The surface is needed in order to construct the brush, so it is + // realized before the brush is placed in the cache. + result = _compositor.CreateSurfaceBrush( + GetSurface(RequireIndex(table.Surface, "surface")) + ?? throw new FlatBufferFormatException("A surface brush has no surface.")); + break; + case Fb.BrushKind.Effect: + var effect = GetEffect(RequireIndex(table.Effect, "effect")) + ?? throw new FlatBufferFormatException("An effect brush has no effect."); + result = _compositor.CreateEffectFactory(effect).CreateBrush(); + break; + default: + throw new FlatBufferFormatException($"Unsupported brush kind {table.Kind}."); + } + + _brushes[index] = result; + + switch (result) + { + case Wc.CompositionColorBrush colorBrush: + if (table.Color.HasValue) + { + colorBrush.Color = ToColor(table.Color.Value); + } + + break; + case Wc.CompositionMaskBrush maskBrush: + if (table.Source != Format.NullIndex) + { + maskBrush.Source = GetBrush(table.Source); + } + + if (table.Mask != Format.NullIndex) + { + maskBrush.Mask = GetBrush(table.Mask); + } + + break; + case Wc.CompositionEffectBrush effectBrush: + for (var i = 0; i < table.SourceParametersLength; i++) + { + var parameter = Require(table.SourceParameters(i), "source parameter"); + effectBrush.SetSourceParameter( + GetRequiredString(parameter.Name), + GetBrush(parameter.Brush) + ?? throw new FlatBufferFormatException("An effect source has no brush.")); + } + + break; + } + + if (result is Wc.CompositionGradientBrush gradientBrush) + { + ApplyGradientBrushProperties(gradientBrush, table); + } + + InitializeCompositionObject(result, table.Base); + return result; + } + + void ApplyGradientBrushProperties(Wc.CompositionGradientBrush target, Fb.Brush table) + { + if (ToVector2(table.AnchorPoint) is Vector2 anchorPoint) + { + target.AnchorPoint = anchorPoint; + } + + if (ToVector2(table.CenterPoint) is Vector2 centerPoint) + { + target.CenterPoint = centerPoint; + } + + for (var i = 0; i < table.ColorStopsLength; i++) + { + target.ColorStops.Add( + GetGradientStop(table.ColorStops(i)) + ?? throw new FlatBufferFormatException("A gradient stop is missing.")); + } + + if (table.ExtendMode.HasValue) + { + target.ExtendMode = (Wc.CompositionGradientExtendMode)table.ExtendMode.Value; + } + + if (table.InterpolationSpace.HasValue) + { + target.InterpolationSpace = (Wc.CompositionColorSpace)table.InterpolationSpace.Value; + } + + if (table.MappingMode.HasValue) + { + target.MappingMode = (Wc.CompositionMappingMode)table.MappingMode.Value; + } + + if (ToVector2(table.Offset) is Vector2 offset) + { + target.Offset = offset; + } + + if (table.RotationAngleInDegrees.HasValue) + { + target.RotationAngleInDegrees = table.RotationAngleInDegrees.Value; + } + + if (ToVector2(table.Scale) is Vector2 scale) + { + target.Scale = scale; + } + + if (ToMatrix3x2(table.TransformMatrix) is Matrix3x2 transformMatrix) + { + target.TransformMatrix = transformMatrix; + } + + switch (target) + { + case Wc.CompositionLinearGradientBrush linear: + if (ToVector2(table.StartPoint) is Vector2 startPoint) + { + linear.StartPoint = startPoint; + } + + if (ToVector2(table.EndPoint) is Vector2 endPoint) + { + linear.EndPoint = endPoint; + } + + break; + case Wc.CompositionRadialGradientBrush radial: + if (ToVector2(table.EllipseCenter) is Vector2 ellipseCenter) + { + radial.EllipseCenter = ellipseCenter; + } + + if (ToVector2(table.EllipseRadius) is Vector2 ellipseRadius) + { + radial.EllipseRadius = ellipseRadius; + } + + if (ToVector2(table.GradientOriginOffset) is Vector2 gradientOriginOffset) + { + radial.GradientOriginOffset = gradientOriginOffset; + } + + break; + } + } + + Wc.CompositionColorGradientStop? GetGradientStop(uint index) + { + if (index == Format.NullIndex) + { + return null; + } + + if (index >= (uint)_gradientStops.Length) + { + throw new FlatBufferFormatException($"Gradient stop index {index} is out of range."); + } + + if (_gradientStops[index] is Wc.CompositionColorGradientStop cached) + { + return cached; + } + + var table = Require(_root.GradientStops((int)index), "gradient stop"); + var result = _compositor.CreateColorGradientStop(); + _gradientStops[index] = result; + + result.Color = ToColor(Require(table.Color, "color")); + result.Offset = table.Offset; + + InitializeCompositionObject(result, table.Base); + return result; + } + + // --------------------------------------------------------------------- + // Clips, view boxes, shadows and surfaces. + // --------------------------------------------------------------------- + Wc.CompositionViewBox? GetViewBox(uint index) + { + if (index == Format.NullIndex) + { + return null; + } + + if (index >= (uint)_viewBoxes.Length) + { + throw new FlatBufferFormatException($"View box index {index} is out of range."); + } + + if (_viewBoxes[index] is Wc.CompositionViewBox cached) + { + return cached; + } + + var table = Require(_root.ViewBoxes((int)index), "view box"); + var result = _compositor.CreateViewBox(); + _viewBoxes[index] = result; + + result.Size = ToVector2(table.Size) ?? Vector2.Zero; + + InitializeCompositionObject(result, table.Base); + return result; + } + + Wc.CompositionClip? GetClip(uint index) + { + if (index == Format.NullIndex) + { + return null; + } + + if (index >= (uint)_clips.Length) + { + throw new FlatBufferFormatException($"Clip index {index} is out of range."); + } + + if (_clips[index] is Wc.CompositionClip cached) + { + return cached; + } + + var table = Require(_root.Clips((int)index), "clip"); + + Wc.CompositionClip result = table.Kind switch + { + Fb.ClipKind.Geometric => _compositor.CreateGeometricClip(), + Fb.ClipKind.Inset => _compositor.CreateInsetClip(), + _ => throw new FlatBufferFormatException($"Unsupported clip kind {table.Kind}."), + }; + + _clips[index] = result; + + if (ToVector2(table.CenterPoint) is Vector2 centerPoint) + { + result.CenterPoint = centerPoint; + } + + if (ToVector2(table.Scale) is Vector2 scale) + { + result.Scale = scale; + } + + switch (result) + { + case Wc.InsetClip inset: + if (table.LeftInset.HasValue) + { + inset.LeftInset = table.LeftInset.Value; + } + + if (table.RightInset.HasValue) + { + inset.RightInset = table.RightInset.Value; + } + + if (table.TopInset.HasValue) + { + inset.TopInset = table.TopInset.Value; + } + + if (table.BottomInset.HasValue) + { + inset.BottomInset = table.BottomInset.Value; + } + + break; + case Wc.CompositionGeometricClip geometric: + if (table.Geometry != Format.NullIndex) + { + geometric.Geometry = GetGeometry(table.Geometry); + } + + break; + } + + InitializeCompositionObject(result, table.Base); + return result; + } + + Wc.CompositionShadow? GetShadow(uint index) + { + if (index == Format.NullIndex) + { + return null; + } + + if (index >= (uint)_shadows.Length) + { + throw new FlatBufferFormatException($"Shadow index {index} is out of range."); + } + + if (_shadows[index] is Wc.CompositionShadow cached) + { + return cached; + } + + var table = Require(_root.Shadows((int)index), "shadow"); + + Wc.CompositionShadow result = table.Kind switch + { + Fb.ShadowKind.Drop => _compositor.CreateDropShadow(), + _ => throw new FlatBufferFormatException($"Unsupported shadow kind {table.Kind}."), + }; + + _shadows[index] = result; + + if (result is Wc.DropShadow dropShadow) + { + if (table.BlurRadius.HasValue) + { + dropShadow.BlurRadius = table.BlurRadius.Value; + } + + if (table.Color.HasValue) + { + dropShadow.Color = ToColor(table.Color.Value); + } + + if (table.Mask != Format.NullIndex) + { + dropShadow.Mask = GetBrush(table.Mask); + } + + if (ToVector3(table.Offset) is Vector3 offset) + { + dropShadow.Offset = offset; + } + + if (table.Opacity.HasValue) + { + dropShadow.Opacity = table.Opacity.Value; + } + + if (table.SourcePolicy.HasValue) + { + dropShadow.SourcePolicy = (Wc.CompositionDropShadowSourcePolicy)table.SourcePolicy.Value; + } + } + + InitializeCompositionObject(result, table.Base); + return result; + } + + Wc.ICompositionSurface? GetSurface(uint index) + { + if (index == Format.NullIndex) + { + return null; + } + + if (index >= (uint)_surfaces.Length) + { + throw new FlatBufferFormatException($"Surface index {index} is out of range."); + } + + if (_surfaces[index] is Wc.ICompositionSurface cached) + { + return cached; + } + + var table = Require(_root.Surfaces((int)index), "surface"); + + switch (table.Kind) + { + case Fb.SurfaceKind.VisualSurface: + var visualSurface = _compositor.CreateVisualSurface(); + _surfaces[index] = visualSurface; + + if (table.SourceVisual != Format.NullIndex) + { + visualSurface.SourceVisual = GetVisual(table.SourceVisual); + } + + if (ToVector2(table.SourceSize) is Vector2 sourceSize) + { + visualSurface.SourceSize = sourceSize; + } + + if (ToVector2(table.SourceOffset) is Vector2 sourceOffset) + { + visualSurface.SourceOffset = sourceOffset; + } + + InitializeCompositionObject(visualSurface, table.Base); + return visualSurface; + + case Fb.SurfaceKind.LoadedImageFromUri: + var uri = GetRequiredString(table.Uri); + var fromUri = Wm.LoadedImageSurface.StartLoadFromUri(new Uri(uri, UriKind.RelativeOrAbsolute)); + _surfaces[index] = fromUri; + return fromUri; + + case Fb.SurfaceKind.LoadedImageFromStream: + var bytes = new byte[table.BytesLength]; + for (var i = 0; i < bytes.Length; i++) + { + bytes[i] = table.Bytes(i); + } + + var fromStream = Wm.LoadedImageSurface.StartLoadFromStream( + bytes.AsBuffer().AsStream().AsRandomAccessStream()); + _surfaces[index] = fromStream; + return fromStream; + + default: + throw new FlatBufferFormatException($"Unsupported surface kind {table.Kind}."); + } + } + + // --------------------------------------------------------------------- + // Effects. + // --------------------------------------------------------------------- + IGraphicsEffect? GetEffect(uint index) + { + if (index == Format.NullIndex) + { + return null; + } + + if (index >= (uint)_effects.Length) + { + throw new FlatBufferFormatException($"Effect index {index} is out of range."); + } + + if (_effects[index] is IGraphicsEffect cached) + { + return cached; + } + + var table = Require(_root.Effects((int)index), "effect"); + + // The sources are the names that the brush later binds real brushes to. The + // composition engine matches them up by name. + var sources = new List(table.SourcesLength); + for (var i = 0; i < table.SourcesLength; i++) + { + sources.Add(new Wc.CompositionEffectSourceParameter(GetRequiredString(table.Sources(i)))); + } + + IGraphicsEffect result; + switch (table.Kind) + { + case Fb.EffectKind.Composite: + var composite = new Mgce.CompositeEffect + { + Mode = (Mgc.CanvasComposite)table.Mode, + }; + + foreach (var source in sources) + { + composite.Sources.Add(source); + } + + result = composite; + break; + case Fb.EffectKind.GaussianBlur: + if (sources.Count != 1) + { + throw new FlatBufferFormatException("A blur effect must have exactly one source."); + } + + result = new Mgce.GaussianBlurEffect + { + BlurAmount = table.BlurAmount, + Source = sources[0], + }; + break; + default: + throw new FlatBufferFormatException($"Unsupported effect kind {table.Kind}."); + } + + _effects[index] = result; + return result; + } + + // --------------------------------------------------------------------- + // Easings. + // --------------------------------------------------------------------- + Wc.CompositionEasingFunction? GetEasing(uint index) + { + if (index == Format.NullIndex) + { + return null; + } + + if (index >= (uint)_easings.Length) + { + throw new FlatBufferFormatException($"Easing index {index} is out of range."); + } + + if (_easings[index] is Wc.CompositionEasingFunction cached) + { + return cached; + } + + var table = Require(_root.Easings((int)index), "easing"); + + Wc.CompositionEasingFunction result; + switch (table.Kind) + { + case Fb.EasingKind.Linear: + result = _compositor.CreateLinearEasingFunction(); + break; + case Fb.EasingKind.CubicBezier: + result = _compositor.CreateCubicBezierEasingFunction( + ToVector2(table.ControlPoint1) ?? Vector2.Zero, + ToVector2(table.ControlPoint2) ?? Vector2.Zero); + break; + case Fb.EasingKind.Step: + var step = _compositor.CreateStepEasingFunction(); + + if (table.StepCount.HasValue) + { + step.StepCount = table.StepCount.Value; + } + + if (table.InitialStep.HasValue) + { + step.InitialStep = table.InitialStep.Value; + } + + if (table.FinalStep.HasValue) + { + step.FinalStep = table.FinalStep.Value; + } + + if (table.IsInitialStepSingleFrame.HasValue) + { + step.IsInitialStepSingleFrame = table.IsInitialStepSingleFrame.Value; + } + + if (table.IsFinalStepSingleFrame.HasValue) + { + step.IsFinalStepSingleFrame = table.IsFinalStepSingleFrame.Value; + } + + result = step; + break; + default: + throw new FlatBufferFormatException($"Unsupported easing kind {table.Kind}."); + } + + _easings[index] = result; + + InitializeCompositionObject(result, table.Base); + return result; + } + + // --------------------------------------------------------------------- + // Animations. + // --------------------------------------------------------------------- + Wc.CompositionAnimation? GetAnimation(uint index) + { + if (index == Format.NullIndex) + { + return null; + } + + if (index >= (uint)_animations.Length) + { + throw new FlatBufferFormatException($"Animation index {index} is out of range."); + } + + if (_animations[index] is Wc.CompositionAnimation cached) + { + return cached; + } + + var table = Require(_root.Animations((int)index), "animation"); + + Wc.CompositionAnimation result = table.Kind == Fb.AnimationKind.Expression + ? _compositor.CreateExpressionAnimation(GetRequiredString(table.Expression)) + : CreateKeyFrameAnimation(table); + + _animations[index] = result; + + if (GetString(table.Target) is string target) + { + result.Target = target; + } + + for (var i = 0; i < table.ReferenceParametersLength; i++) + { + var parameter = Require(table.ReferenceParameters(i), "reference parameter"); + result.SetReferenceParameter( + GetRequiredString(parameter.Name), + GetObjectReference(parameter.Target) + ?? throw new FlatBufferFormatException("A reference parameter has no target.")); + } + + InitializeCompositionObject(result, table.Base); + return result; + } + + // Creates the animation and inserts its key frames in one place, because the + // concrete type is only needed in order to insert a value key frame and would + // otherwise have to be recovered with a cast. + Wc.CompositionAnimation CreateKeyFrameAnimation(Fb.Animation table) + { + Wc.KeyFrameAnimation result; + + switch (table.Kind) + { + case Fb.AnimationKind.Scalar: + var scalar = _compositor.CreateScalarKeyFrameAnimation(); + ForEachValueKeyFrame(table, frame => + scalar.InsertKeyFrame(frame.Progress, frame.Scalar, GetEasing(frame.Easing))); + result = scalar; + break; + case Fb.AnimationKind.Vector2: + var vector2 = _compositor.CreateVector2KeyFrameAnimation(); + ForEachValueKeyFrame(table, frame => + vector2.InsertKeyFrame(frame.Progress, ToVector2(frame.Vector), GetEasing(frame.Easing))); + result = vector2; + break; + case Fb.AnimationKind.Vector3: + var vector3 = _compositor.CreateVector3KeyFrameAnimation(); + ForEachValueKeyFrame(table, frame => + vector3.InsertKeyFrame(frame.Progress, ToVector3(frame.Vector), GetEasing(frame.Easing))); + result = vector3; + break; + case Fb.AnimationKind.Vector4: + var vector4 = _compositor.CreateVector4KeyFrameAnimation(); + ForEachValueKeyFrame(table, frame => + vector4.InsertKeyFrame(frame.Progress, ToVector4(frame.Vector), GetEasing(frame.Easing))); + result = vector4; + break; + case Fb.AnimationKind.Color: + var color = _compositor.CreateColorKeyFrameAnimation(); + + if (table.InterpolationColorSpace.HasValue) + { + color.InterpolationColorSpace = + (Wc.CompositionColorSpace)table.InterpolationColorSpace.Value; + } + + ForEachValueKeyFrame(table, frame => + color.InsertKeyFrame( + frame.Progress, + ToColor(Require(frame.Color, "color")), + GetEasing(frame.Easing))); + result = color; + break; + case Fb.AnimationKind.Boolean: + var boolean = _compositor.CreateBooleanKeyFrameAnimation(); + + // A boolean cannot be interpolated, so it has no easing. + ForEachValueKeyFrame(table, frame => + boolean.InsertKeyFrame(frame.Progress, frame.Scalar != 0)); + result = boolean; + break; + case Fb.AnimationKind.Path: + var path = _compositor.CreatePathKeyFrameAnimation(); + ForEachValueKeyFrame(table, frame => + path.InsertKeyFrame( + frame.Progress, + new Wc.CompositionPath( + GetCanvasGeometry(frame.Path) + ?? throw new FlatBufferFormatException("A path key frame has no path.")), + GetEasing(frame.Easing))); + result = path; + break; + default: + throw new FlatBufferFormatException($"Unsupported animation kind {table.Kind}."); + } + + // Expression key frames are inserted through the base type, so they do not + // have to be repeated in every branch above. They are inserted after the + // value key frames, which is safe because a key frame animation is a map + // keyed on progress rather than a list. + for (var i = 0; i < table.KeyFramesLength; i++) + { + var frame = Require(table.KeyFrames(i), "key frame"); + + if (frame.Kind == Fb.KeyFrameKind.Expression) + { + result.InsertExpressionKeyFrame( + frame.Progress, + GetRequiredString(frame.Expression), + GetEasing(frame.Easing)); + } + } + + result.Duration = TimeSpan.FromTicks(table.DurationTicks); + + return result; + } + + void ForEachValueKeyFrame(Fb.Animation table, Action apply) + { + for (var i = 0; i < table.KeyFramesLength; i++) + { + var frame = Require(table.KeyFrames(i), "key frame"); + + if (frame.Kind == Fb.KeyFrameKind.Value) + { + apply(frame); + } + } + } + + // --------------------------------------------------------------------- + // Paths. + // + // A canvas geometry is not a CompositionObject, so it has no shared state and + // no animators; it is only ever a value. + // --------------------------------------------------------------------- + CanvasGeometry? GetCanvasGeometry(uint index) + { + if (index == Format.NullIndex) + { + return null; + } + + if (index >= (uint)_canvasGeometries.Length) + { + throw new FlatBufferFormatException($"Canvas geometry index {index} is out of range."); + } + + switch (_canvasGeometryStates[index]) + { + case CanvasGeometryState.Realized: + return _canvasGeometries[index]; + case CanvasGeometryState.Realizing: + // A geometry that is built from itself would never finish. + throw new FlatBufferFormatException($"Canvas geometry {index} is cyclic."); + } + + _canvasGeometryStates[index] = CanvasGeometryState.Realizing; + + var table = Require(_root.CanvasGeometries((int)index), "canvas geometry"); + + CanvasGeometry result; + switch (table.Kind) + { + case Fb.CanvasGeometryKind.Combination: + var a = GetCanvasGeometry(RequireIndex(table.A, "geometry")) + ?? throw new FlatBufferFormatException("A combination has no first geometry."); + var b = GetCanvasGeometry(RequireIndex(table.B, "geometry")) + ?? throw new FlatBufferFormatException("A combination has no second geometry."); + result = a.CombineWith( + b, + ToMatrix3x2(table.Matrix) ?? Matrix3x2.Identity, + (CanvasGeometryCombine)table.CombineMode); + break; + + case Fb.CanvasGeometryKind.TransformedGeometry: + var source = GetCanvasGeometry(RequireIndex(table.Source, "geometry")) + ?? throw new FlatBufferFormatException("A transformed geometry has no source."); + result = source.Transform(ToMatrix3x2(table.Matrix) ?? Matrix3x2.Identity); + break; + + case Fb.CanvasGeometryKind.Group: + var geometries = new CanvasGeometry[table.GeometriesLength]; + for (var i = 0; i < geometries.Length; i++) + { + geometries[i] = GetCanvasGeometry(table.Geometries(i)) + ?? throw new FlatBufferFormatException("A grouped geometry is missing."); + } + + result = CanvasGeometry.CreateGroup( + null, + geometries, + (CanvasFilledRegionDetermination)table.FillRule); + break; + + case Fb.CanvasGeometryKind.Path: + result = CreatePath(table); + break; + + case Fb.CanvasGeometryKind.Ellipse: + result = CanvasGeometry.CreateEllipse(null, table.X, table.Y, table.RadiusX, table.RadiusY); + break; + + case Fb.CanvasGeometryKind.RoundedRectangle: + result = CanvasGeometry.CreateRoundedRectangle( + null, table.X, table.Y, table.W, table.H, table.RadiusX, table.RadiusY); + break; + + default: + throw new FlatBufferFormatException($"Unsupported canvas geometry kind {table.Kind}."); + } + + _canvasGeometries[index] = result; + _canvasGeometryStates[index] = CanvasGeometryState.Realized; + return result; + } + + // Replays the flattened opcode and operand streams of a path. The operands of + // every command are consecutive in one array, which is why there is no table + // per command. + static CanvasGeometry CreatePath(Fb.CanvasGeometry table) + { + using var builder = new CanvasPathBuilder(null); + builder.SetFilledRegionDetermination((CanvasFilledRegionDetermination)table.FillRule); + + var operand = 0; + var inFigure = false; + + float Next() + => operand < table.OperandsLength + ? table.Operands(operand++) + : throw new FlatBufferFormatException("A path ran out of operands."); + + void RequireFigure(bool expected) + { + if (inFigure != expected) + { + throw new FlatBufferFormatException("A path has a figure that does not begin and end."); + } + } + + for (var i = 0; i < table.OpsLength; i++) + { + switch ((Fb.PathOp)table.Ops(i)) + { + case Fb.PathOp.BeginFigure: + RequireFigure(false); + builder.BeginFigure(new Vector2(Next(), Next())); + inFigure = true; + break; + case Fb.PathOp.EndFigure: + RequireFigure(true); + builder.EndFigure((CanvasFigureLoop)Next()); + inFigure = false; + break; + case Fb.PathOp.AddLine: + RequireFigure(true); + builder.AddLine(new Vector2(Next(), Next())); + break; + case Fb.PathOp.AddCubicBezier: + RequireFigure(true); + builder.AddCubicBezier( + new Vector2(Next(), Next()), + new Vector2(Next(), Next()), + new Vector2(Next(), Next())); + break; + default: + throw new FlatBufferFormatException($"Unsupported path opcode {table.Ops(i)}."); + } + } + + RequireFigure(false); + + return CanvasGeometry.CreatePath(builder); + } + + // --------------------------------------------------------------------- + // Value helpers. An absent struct field means the property was never set, + // so these return null rather than a default value. + // --------------------------------------------------------------------- + static Wui.Color ToColor(Fb.Color value) + => Wui.Color.FromArgb(value.A, value.R, value.G, value.B); + + static Vector2? ToVector2(Fb.Vec2? value) + => value.HasValue ? new Vector2(value.Value.X, value.Value.Y) : null; + + static Vector3? ToVector3(Fb.Vec3? value) + => value.HasValue ? new Vector3(value.Value.X, value.Value.Y, value.Value.Z) : null; + + static Vector2 ToVector2(Fb.Vec4? value) + => value.HasValue ? new Vector2(value.Value.X, value.Value.Y) : Vector2.Zero; + + static Vector3 ToVector3(Fb.Vec4? value) + => value.HasValue ? new Vector3(value.Value.X, value.Value.Y, value.Value.Z) : Vector3.Zero; + + static Vector4 ToVector4(Fb.Vec4? value) + => value.HasValue + ? new Vector4(value.Value.X, value.Value.Y, value.Value.Z, value.Value.W) + : Vector4.Zero; + + static Matrix3x2? ToMatrix3x2(Fb.Mat3x2? value) + => value.HasValue + ? new Matrix3x2( + value.Value.M11, value.Value.M12, + value.Value.M21, value.Value.M22, + value.Value.M31, value.Value.M32) + : null; + + static Matrix4x4? ToMatrix4x4(Fb.Mat4x4? value) + => value.HasValue + ? new Matrix4x4( + value.Value.M11, value.Value.M12, value.Value.M13, value.Value.M14, + value.Value.M21, value.Value.M22, value.Value.M23, value.Value.M24, + value.Value.M31, value.Value.M32, value.Value.M33, value.Value.M34, + value.Value.M41, value.Value.M42, value.Value.M43, value.Value.M44) + : null; + } +} diff --git a/source/LottieRuntime/LottieRuntime.projitems b/source/LottieRuntime/LottieRuntime.projitems new file mode 100644 index 00000000..bfd579d4 --- /dev/null +++ b/source/LottieRuntime/LottieRuntime.projitems @@ -0,0 +1,14 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 7c8b1d02-4e5f-4a76-9c3b-2f0d8e6a41b9 + + + LottieRuntime + + + + + diff --git a/source/LottieRuntime/LottieRuntime.shproj b/source/LottieRuntime/LottieRuntime.shproj new file mode 100644 index 00000000..fb5026ae --- /dev/null +++ b/source/LottieRuntime/LottieRuntime.shproj @@ -0,0 +1,13 @@ + + + + 7c8b1d02-4e5f-4a76-9c3b-2f0d8e6a41b9 + 14.0 + + + + + + + + diff --git a/source/LottieRuntime/README.md b/source/LottieRuntime/README.md new file mode 100644 index 00000000..23d6015f --- /dev/null +++ b/source/LottieRuntime/README.md @@ -0,0 +1,108 @@ +# LottieRuntime (managed) + +A managed runtime interpreter for Lottie animations. + +`CompositionInterpreter` builds a `Windows.UI.Composition` visual tree from a +serialized composition. It is the C# counterpart of the native interpreter in +[`dlls/LottieRuntime`](../../dlls/LottieRuntime/README.md): both read the same +`.lcomp` buffer, make the same composition calls, and perform the same validation, +so an application can choose whichever one suits its language and packaging without +changing what appears on screen. + +## How it fits in + +Lottie-Windows turns a Lottie file into a `WinCompData` graph, which is a +description of a composition tree. Four consumers use that graph: + +| Consumer | When it runs | Cost | +| --- | --- | --- | +| `Instantiator` | Runtime, in the app | Ships the whole JSON reader and translator | +| `LottieGen` code generators | Build time | No runtime cost, but one class per animation | +| `LottieRuntime` (C++) | Runtime, in the app | One interpreter shared by every animation | +| `CompositionInterpreter` (C#) | Runtime, in the app | One interpreter shared by every animation | + +`CompositionSerializer` writes the graph into a FlatBuffer at build time and the +interpreter reads that buffer at runtime. The animation stays data, so it can be +swapped, downloaded or themed without a rebuild, and the amount of code in the +application does not grow with the number of animations. + +Note the difference from `CompositionDeserializer`, which is also managed and also +reads the same buffer: the deserializer rebuilds a `WinCompData` graph, which is the +object model that the translator and the code generators work in, while the +interpreter creates live composition objects and no intermediate model at all. The +deserializer is a tool; the interpreter is a player. + +## Producing a buffer + +``` +LottieGen -InputFile animation.json -Language flatbuffer -OutputFolder . +``` + +This writes `Animation.lcomp`. The format is described by +[`source/LottieFlatbuffer/lottie_comp.fbs`](../LottieFlatbuffer/lottie_comp.fbs). + +## Using it + +```csharp +var bytes = File.ReadAllBytes("Animation.lcomp"); +var root = CompositionInterpreter.LoadComposition(compositor, bytes); + +ElementCompositionPreview.SetElementChildVisual(element, root); + +// Drive the animation by setting Progress between 0 and 1. +CompositionInterpreter.ProgressPropertySet(root).InsertScalar("Progress", 0.5f); +``` + +The `Compositor` must belong to the calling thread, which must own a +`DispatcherQueue` that outlives the returned visual tree. + +## Design + +**One function per category, not per type.** A node's concrete type is known only +inside the function that creates it. The realization caches hold `Visual`, +`CompositionShape`, `CompositionBrush`, `CompositionGeometry` and so on, and +everything the interpreter does afterwards is declared on those types, so there is +no point at which it has to ask what a node actually is. The state that every +`CompositionObject` has is applied by one function that takes `CompositionObject`. + +**Runtime cost second.** Each node and string is realized at most once. Lookups are +bounds-checked array indexing into the buffer's node vectors, because the caches are +indexed identically to those vectors. The buffer is read in place. + +**Untrusted input.** A buffer may have come from a file or a download. It is checked +for the `LCMP` identifier, run through the FlatBuffers verifier, and every index read +out of it is range checked. Canvas geometries are checked for cycles and paths for +figures that do not begin and end. A malformed buffer is reported as a +`FlatBufferFormatException` and never reads outside the buffer. A buffer that needs a +newer schema, or a Universal API contract that this version of Windows does not have, +is reported as a `NotSupportedException` before any object is created. + +## Dependencies + +`Windows.UI.Composition`, Win2D for path geometry and the two effects that the +translator emits, and `Google.FlatBuffers` for the generated schema binding. The +native interpreter avoids Win2D by implementing the two Direct2D interop interfaces +itself; that is not available to managed code, so a consumer of this interpreter +takes the Win2D dependency, exactly as a consumer of `Instantiator` or of +`LottieGen`'s C# output already does. + +## Incorporate it in a build + +`LottieRuntime.projitems` is a shared project. Import it into the application +project alongside `CompDataFlatbuffer.projitems`, which contains the generated schema +binding and the format constants, and add a package reference to +`Google.FlatBuffers`. + +Define `WINAPPSDK` when building for the Windows App SDK, which switches the +interpreter from `Windows.UI.Composition` to `Microsoft.UI.Composition`. Define +`PUBLIC_LottieRuntime` to make the type public. + +## Tests + +`tests/CompDataFlatbuffer.Tests` dumps the interpreted tree in the same canonical +text as the `WinCompData` graph that the buffer was written from and asserts that the +two are equal, for every animation in the sample corpus and for a synthetic graph +that contains one of every node type. The same tests check that malformed, truncated +and corrupted buffers are rejected. The composition APIs only exist on Windows, so +the tests run against stand-ins for them, which is what makes the interpreter +testable on any platform. diff --git a/tests/CompDataFlatbuffer.Tests/CompDataFlatbuffer.Tests.csproj b/tests/CompDataFlatbuffer.Tests/CompDataFlatbuffer.Tests.csproj index 5eaab357..03c5c47e 100644 --- a/tests/CompDataFlatbuffer.Tests/CompDataFlatbuffer.Tests.csproj +++ b/tests/CompDataFlatbuffer.Tests/CompDataFlatbuffer.Tests.csproj @@ -21,6 +21,7 @@ + diff --git a/tests/CompDataFlatbuffer.Tests/CompositionApiStandIns.cs b/tests/CompDataFlatbuffer.Tests/CompositionApiStandIns.cs new file mode 100644 index 00000000..59bfc1e1 --- /dev/null +++ b/tests/CompDataFlatbuffer.Tests/CompositionApiStandIns.cs @@ -0,0 +1,1112 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// Stand-ins for the Windows.UI.Composition, Windows.UI.Xaml.Media and Win2D APIs +// that CompositionInterpreter is written against. +// +// The interpreter's whole job is to make the right sequence of calls into those +// APIs, and those APIs are only present on Windows. Rather than testing the +// interpreter by looking at pixels on a Windows machine, these types stand in for +// the real ones: they are declared in the real namespaces with the real names and +// the members that the interpreter uses, and they record what was done to them so +// that the resulting tree can be dumped and compared against the WinCompData graph +// that produced the buffer. +// +// They are deliberately dumb. They contain no logic beyond recording, so a test +// failure is always a fault in the interpreter rather than in the stand-ins. The +// same technique is used by tests/Mocks.cs, which mocks Win2D so that LottieGen's +// generated code can be compiled off Windows. +// +// Two details are modelled rather than merely recorded, because the dump depends +// on them: +// +// * A property that has never been set is distinguishable from one that has been +// set to its default value, which is what makes the dump comparable with the +// WinCompData dump. That is why the properties are nullable. +// * StartAnimation creates an AnimationController for every animation that is not +// an ExpressionAnimation, which is what Windows.UI.Composition does and what +// WinCompData models. +#nullable enable + +using System; +using System.Collections.Generic; +using System.IO; +using System.Numerics; +using Windows.Graphics.Effects; +using Windows.Storage.Streams; +using Windows.UI.Composition; +using Mgc = Microsoft.Graphics.Canvas; +using Mgcg = Microsoft.Graphics.Canvas.Geometry; + +namespace Windows.UI +{ + struct Color + { + public byte A { get; private set; } + + public byte R { get; private set; } + + public byte G { get; private set; } + + public byte B { get; private set; } + + public static Color FromArgb(byte a, byte r, byte g, byte b) + => new Color { A = a, R = r, G = g, B = b }; + } +} + +namespace Windows.Foundation.Metadata +{ + static class ApiInformation + { + // The version of the API contract that the stand-ins pretend to implement. + // It is well beyond any version that the translator asks for, so that a + // composition from the corpus always loads, and a test that wants the check + // to fail asks for a version that no version of Windows will ever have. + public const ushort UniversalApiContractVersion = 20; + + public static bool IsApiContractPresent(string contractName, ushort majorVersion) + => contractName == "Windows.Foundation.UniversalApiContract" && + majorVersion <= UniversalApiContractVersion; + } +} + +namespace Windows.Graphics.Effects +{ + interface IGraphicsEffectSource + { + } + + interface IGraphicsEffect : IGraphicsEffectSource + { + } +} + +namespace Windows.Storage.Streams +{ + interface IBuffer + { + byte[] Bytes { get; } + } + + interface IRandomAccessStream + { + byte[] Bytes { get; } + } + + sealed class ByteArrayBuffer : IBuffer + { + internal ByteArrayBuffer(byte[] bytes) => Bytes = bytes; + + public byte[] Bytes { get; } + } + + sealed class RandomAccessStream : IRandomAccessStream + { + internal RandomAccessStream(byte[] bytes) => Bytes = bytes; + + public byte[] Bytes { get; } + } + + // Stands in for the stream that a byte array is wrapped in on the way to + // LoadedImageSurface. The bytes are carried through unchanged so that the dump + // can show what the surface was given. + sealed class ByteArrayStream : MemoryStream + { + internal ByteArrayStream(byte[] bytes) + : base(bytes, writable: false) + { + Bytes = bytes; + } + + public byte[] Bytes { get; } + } +} + +namespace System.Runtime.InteropServices.WindowsRuntime +{ + static class WindowsRuntimeBufferExtensions + { + public static IBuffer AsBuffer(this byte[] source) => new ByteArrayBuffer(source); + + public static Stream AsStream(this IBuffer source) => new ByteArrayStream(source.Bytes); + } +} + +namespace System.IO +{ + static class WindowsRuntimeStreamExtensions + { + public static IRandomAccessStream AsRandomAccessStream(this Stream source) + => new RandomAccessStream(((ByteArrayStream)source).Bytes); + } +} + +namespace Microsoft.Graphics.Canvas +{ + // Values match CommunityToolkit.WinUI.Lottie.WinCompData.Mgc.CanvasComposite. + enum CanvasComposite + { + SourceOver, + DestinationOver, + SourceIn, + DestinationIn, + SourceOut, + DestinationOut, + SourceAtop, + DestinationAtop, + Xor, + Add, + Copy, + BoundedCopy, + MaskInvert, + } +} + +namespace Microsoft.Graphics.Canvas.Effects +{ + sealed class CompositeEffect : IGraphicsEffect + { + public Mgc.CanvasComposite Mode { get; set; } + + public IList Sources { get; } = new List(); + } + + sealed class GaussianBlurEffect : IGraphicsEffect + { + public float BlurAmount { get; set; } + + public IGraphicsEffectSource? Source { get; set; } + } +} + +namespace Microsoft.Graphics.Canvas.Geometry +{ + // Values match CommunityToolkit.WinUI.Lottie.WinCompData.Mgcg.CanvasFigureLoop. + enum CanvasFigureLoop + { + Open, + Closed, + } + + // Values match CommunityToolkit.WinUI.Lottie.WinCompData.Mgcg.CanvasFilledRegionDetermination. + enum CanvasFilledRegionDetermination + { + Alternate, + Winding, + } + + // Values match CommunityToolkit.WinUI.Lottie.WinCompData.Mgcg.CanvasGeometryCombine. + enum CanvasGeometryCombine + { + Union, + Exclude, + Intersect, + Xor, + } + + // The shape of a geometry is recorded rather than rasterized. The nested types + // mirror the ones in WinCompData.Mgcg.CanvasGeometry so that the two can be + // compared. + abstract class CanvasGeometry + { + public static CanvasGeometry CreateEllipse(object? device, float x, float y, float radiusX, float radiusY) + => new Ellipse(x, y, radiusX, radiusY); + + public static CanvasGeometry CreateRoundedRectangle( + object? device, float x, float y, float w, float h, float radiusX, float radiusY) + => new RoundedRectangle(x, y, w, h, radiusX, radiusY); + + public static CanvasGeometry CreateGroup( + object? device, CanvasGeometry[] geometries, CanvasFilledRegionDetermination filledRegionDetermination) + => new Group(geometries, filledRegionDetermination); + + public static CanvasGeometry CreatePath(CanvasPathBuilder builder) + => new Path(builder.Commands, builder.FilledRegionDetermination); + + public CanvasGeometry CombineWith( + CanvasGeometry other, Matrix3x2 matrix, CanvasGeometryCombine combineMode) + => new Combination(this, other, matrix, combineMode); + + public CanvasGeometry Transform(Matrix3x2 transformMatrix) + => new TransformedGeometry(this, transformMatrix); + + public sealed class Combination : CanvasGeometry + { + internal Combination(CanvasGeometry a, CanvasGeometry b, Matrix3x2 matrix, CanvasGeometryCombine combineMode) + => (A, B, Matrix, CombineMode) = (a, b, matrix, combineMode); + + public CanvasGeometry A { get; } + + public CanvasGeometry B { get; } + + public Matrix3x2 Matrix { get; } + + public CanvasGeometryCombine CombineMode { get; } + } + + public sealed class TransformedGeometry : CanvasGeometry + { + internal TransformedGeometry(CanvasGeometry sourceGeometry, Matrix3x2 transformMatrix) + => (SourceGeometry, TransformMatrix) = (sourceGeometry, transformMatrix); + + public CanvasGeometry SourceGeometry { get; } + + public Matrix3x2 TransformMatrix { get; } + } + + public sealed class Group : CanvasGeometry + { + internal Group(CanvasGeometry[] geometries, CanvasFilledRegionDetermination filledRegionDetermination) + => (Geometries, FilledRegionDetermination) = (geometries, filledRegionDetermination); + + public CanvasGeometry[] Geometries { get; } + + public CanvasFilledRegionDetermination FilledRegionDetermination { get; } + } + + public sealed class Path : CanvasGeometry + { + internal Path( + IReadOnlyList commands, + CanvasFilledRegionDetermination filledRegionDetermination) + => (Commands, FilledRegionDetermination) = (commands, filledRegionDetermination); + + public IReadOnlyList Commands { get; } + + public CanvasFilledRegionDetermination FilledRegionDetermination { get; } + } + + public sealed class Ellipse : CanvasGeometry + { + internal Ellipse(float x, float y, float radiusX, float radiusY) + => (X, Y, RadiusX, RadiusY) = (x, y, radiusX, radiusY); + + public float X { get; } + + public float Y { get; } + + public float RadiusX { get; } + + public float RadiusY { get; } + } + + public sealed class RoundedRectangle : CanvasGeometry + { + internal RoundedRectangle(float x, float y, float w, float h, float radiusX, float radiusY) + => (X, Y, W, H, RadiusX, RadiusY) = (x, y, w, h, radiusX, radiusY); + + public float X { get; } + + public float Y { get; } + + public float W { get; } + + public float H { get; } + + public float RadiusX { get; } + + public float RadiusY { get; } + } + } + + sealed class CanvasPathBuilder : IDisposable + { + readonly List _commands = new List(); + + public CanvasPathBuilder(object? device) + { + } + + internal IReadOnlyList Commands => _commands; + + internal CanvasFilledRegionDetermination FilledRegionDetermination { get; private set; } + + public void SetFilledRegionDetermination(CanvasFilledRegionDetermination value) + => FilledRegionDetermination = value; + + public void BeginFigure(Vector2 startPoint) => _commands.Add(new Command.BeginFigure(startPoint)); + + public void EndFigure(CanvasFigureLoop figureLoop) => _commands.Add(new Command.EndFigure(figureLoop)); + + public void AddLine(Vector2 endPoint) => _commands.Add(new Command.AddLine(endPoint)); + + public void AddCubicBezier(Vector2 controlPoint1, Vector2 controlPoint2, Vector2 endPoint) + => _commands.Add(new Command.AddCubicBezier(controlPoint1, controlPoint2, endPoint)); + + public void Dispose() + { + } + + internal abstract class Command + { + internal sealed class BeginFigure : Command + { + internal BeginFigure(Vector2 startPoint) => StartPoint = startPoint; + + public Vector2 StartPoint { get; } + } + + internal sealed class EndFigure : Command + { + internal EndFigure(CanvasFigureLoop figureLoop) => FigureLoop = figureLoop; + + public CanvasFigureLoop FigureLoop { get; } + } + + internal sealed class AddLine : Command + { + internal AddLine(Vector2 endPoint) => EndPoint = endPoint; + + public Vector2 EndPoint { get; } + } + + internal sealed class AddCubicBezier : Command + { + internal AddCubicBezier(Vector2 controlPoint1, Vector2 controlPoint2, Vector2 endPoint) + => (ControlPoint1, ControlPoint2, EndPoint) = (controlPoint1, controlPoint2, endPoint); + + public Vector2 ControlPoint1 { get; } + + public Vector2 ControlPoint2 { get; } + + public Vector2 EndPoint { get; } + } + } + } +} + +namespace Windows.UI.Xaml.Media +{ + // The two loaded image surfaces are separate types here, unlike in the real API + // where one type is returned by both factories, so that the dump can tell them + // apart in the same way that the WinCompData dump does. + abstract class LoadedImageSurface : ICompositionSurface + { + public static LoadedImageSurface StartLoadFromUri(Uri uri) => new LoadedImageSurfaceFromUri(uri); + + public static LoadedImageSurface StartLoadFromStream(IRandomAccessStream stream) + => new LoadedImageSurfaceFromStream(stream.Bytes); + } + + sealed class LoadedImageSurfaceFromUri : LoadedImageSurface + { + internal LoadedImageSurfaceFromUri(Uri uri) => Uri = uri; + + public Uri Uri { get; } + } + + sealed class LoadedImageSurfaceFromStream : LoadedImageSurface + { + internal LoadedImageSurfaceFromStream(byte[] bytes) => Bytes = bytes; + + public byte[] Bytes { get; } + } +} + +namespace Windows.UI.Composition +{ + // Values match CommunityToolkit.WinUI.Lottie.WinCompData.CompositionBorderMode. + enum CompositionBorderMode + { + Inherit = 0, + Soft = 1, + Hard = 2, + } + + // Values match CommunityToolkit.WinUI.Lottie.WinCompData.CompositionStrokeCap. + enum CompositionStrokeCap + { + Flat, + Square, + Round, + Triangle, + } + + // Values match CommunityToolkit.WinUI.Lottie.WinCompData.CompositionStrokeLineJoin. + enum CompositionStrokeLineJoin + { + Miter, + Bevel, + Round, + MiterOrBevel, + } + + // Values match CommunityToolkit.WinUI.Lottie.WinCompData.CompositionGradientExtendMode. + enum CompositionGradientExtendMode + { + Clamp = 0, + Wrap = 1, + Mirror = 2, + } + + // Values match CommunityToolkit.WinUI.Lottie.WinCompData.CompositionColorSpace. + enum CompositionColorSpace + { + Auto = 0, + Hsl = 1, + Rgb = 2, + HslLinear = 3, + RgbLinear = 4, + } + + // Values match CommunityToolkit.WinUI.Lottie.WinCompData.CompositionMappingMode. + enum CompositionMappingMode + { + Absolute = 0, + Relative = 1, + } + + // Values match CommunityToolkit.WinUI.Lottie.WinCompData.CompositionDropShadowSourcePolicy. + enum CompositionDropShadowSourcePolicy + { + Default = 0, + InheritFromVisualContent = 1, + } + + interface ICompositionSurface + { + } + + abstract class CompositionObject + { + readonly List _animators = new List(); + + private protected CompositionObject() + => Properties = this as CompositionPropertySet ?? new CompositionPropertySet(this); + + public string? Comment { get; set; } + + public CompositionPropertySet Properties { get; } + + internal IReadOnlyList Animators => _animators; + + public void StartAnimation(string propertyName, CompositionAnimation animation) + => StartAnimation(propertyName, animation, null); + + public void StartAnimation( + string propertyName, CompositionAnimation animation, AnimationController? animationController) + { + // A controller comes into existence with the animation, unless the + // animation is an expression, which is driven by its inputs rather than by + // a clock and so has nothing to control. + var controller = animationController ?? + (animation is ExpressionAnimation ? null : new AnimationController(this, propertyName)); + + _animators.Add(new Animator(propertyName, animation, controller)); + } + + public AnimationController? TryGetAnimationController(string propertyName) + { + foreach (var animator in _animators) + { + if (animator.AnimatedProperty == propertyName) + { + return animator.Controller; + } + } + + return null; + } + + internal sealed class Animator + { + internal Animator(string animatedProperty, CompositionAnimation animation, AnimationController? controller) + => (AnimatedProperty, Animation, Controller) = (animatedProperty, animation, controller); + + public string AnimatedProperty { get; } + + public CompositionAnimation Animation { get; } + + public AnimationController? Controller { get; } + } + } + + sealed class AnimationController : CompositionObject + { + internal AnimationController(CompositionObject targetObject, string targetProperty) + => (TargetObject, TargetProperty) = (targetObject, targetProperty); + + internal AnimationController() + { + } + + public bool IsCustom => TargetObject is null; + + public CompositionObject? TargetObject { get; } + + public string? TargetProperty { get; } + + public bool IsPaused { get; private set; } + + public void Pause() => IsPaused = true; + } + + enum PropertySetValueType + { + Color, + Scalar, + Vector2, + Vector3, + Vector4, + } + + sealed class CompositionPropertySet : CompositionObject + { + // A SortedDictionary with the default comparer, exactly as WinCompData uses, + // so that the dump of a property set is in the same order on both sides. + readonly SortedDictionary _names = + new SortedDictionary(); + + readonly Dictionary _values = new Dictionary(); + + internal CompositionPropertySet(CompositionObject? owner) => Owner = owner; + + public CompositionObject? Owner { get; } + + internal IReadOnlyDictionary Names => _names; + + public void InsertColor(string propertyName, UI.Color value) + => Insert(propertyName, PropertySetValueType.Color, value); + + public void InsertScalar(string propertyName, float value) + => Insert(propertyName, PropertySetValueType.Scalar, value); + + public void InsertVector2(string propertyName, Vector2 value) + => Insert(propertyName, PropertySetValueType.Vector2, value); + + public void InsertVector3(string propertyName, Vector3 value) + => Insert(propertyName, PropertySetValueType.Vector3, value); + + public void InsertVector4(string propertyName, Vector4 value) + => Insert(propertyName, PropertySetValueType.Vector4, value); + + internal object GetValue(string propertyName) => _values[propertyName]; + + void Insert(string propertyName, PropertySetValueType type, object value) + { + _names[propertyName] = type; + _values[propertyName] = value; + } + } + + sealed class CompositionPath + { + public CompositionPath(Mgcg.CanvasGeometry source) => Source = source; + + public Mgcg.CanvasGeometry Source { get; } + } + + sealed class CompositionEffectSourceParameter : IGraphicsEffectSource + { + public CompositionEffectSourceParameter(string name) => Name = name; + + public string Name { get; } + } + + abstract class Visual : CompositionObject + { + public CompositionBorderMode? BorderMode { get; set; } + + public Vector3? CenterPoint { get; set; } + + public CompositionClip? Clip { get; set; } + + public bool? IsVisible { get; set; } + + public Vector3? Offset { get; set; } + + public float? Opacity { get; set; } + + public float? RotationAngleInDegrees { get; set; } + + public Vector3? RotationAxis { get; set; } + + public Vector3? Scale { get; set; } + + public Vector2? Size { get; set; } + + public Matrix4x4? TransformMatrix { get; set; } + } + + sealed class VisualCollection + { + readonly List _visuals = new List(); + + internal IReadOnlyList Visuals => _visuals; + + public void InsertAtTop(Visual visual) => _visuals.Add(visual); + } + + class ContainerVisual : Visual + { + public VisualCollection Children { get; } = new VisualCollection(); + } + + sealed class ShapeVisual : ContainerVisual + { + public IList Shapes { get; } = new List(); + + public CompositionViewBox? ViewBox { get; set; } + } + + sealed class SpriteVisual : ContainerVisual + { + public CompositionBrush? Brush { get; set; } + + public CompositionShadow? Shadow { get; set; } + } + + sealed class LayerVisual : ContainerVisual + { + public CompositionShadow? Shadow { get; set; } + } + + abstract class CompositionShape : CompositionObject + { + public Vector2? CenterPoint { get; set; } + + public Vector2? Offset { get; set; } + + public float? RotationAngleInDegrees { get; set; } + + public Vector2? Scale { get; set; } + + public Matrix3x2? TransformMatrix { get; set; } + } + + sealed class CompositionContainerShape : CompositionShape + { + public IList Shapes { get; } = new List(); + } + + sealed class CompositionSpriteShape : CompositionShape + { + public CompositionBrush? FillBrush { get; set; } + + public CompositionBrush? StrokeBrush { get; set; } + + public CompositionGeometry? Geometry { get; set; } + + public bool? IsStrokeNonScaling { get; set; } + + public float? StrokeDashOffset { get; set; } + + public IList StrokeDashArray { get; } = new List(); + + public CompositionStrokeCap? StrokeDashCap { get; set; } + + public CompositionStrokeCap? StrokeStartCap { get; set; } + + public CompositionStrokeCap? StrokeEndCap { get; set; } + + public CompositionStrokeLineJoin? StrokeLineJoin { get; set; } + + public float? StrokeMiterLimit { get; set; } + + public float? StrokeThickness { get; set; } + } + + abstract class CompositionGeometry : CompositionObject + { + public float? TrimStart { get; set; } + + public float? TrimEnd { get; set; } + + public float? TrimOffset { get; set; } + } + + sealed class CompositionPathGeometry : CompositionGeometry + { + public CompositionPath? Path { get; set; } + } + + sealed class CompositionRectangleGeometry : CompositionGeometry + { + public Vector2? Offset { get; set; } + + public Vector2? Size { get; set; } + } + + sealed class CompositionRoundedRectangleGeometry : CompositionGeometry + { + public Vector2? Offset { get; set; } + + public Vector2? Size { get; set; } + + public Vector2? CornerRadius { get; set; } + } + + sealed class CompositionEllipseGeometry : CompositionGeometry + { + public Vector2? Center { get; set; } + + public Vector2? Radius { get; set; } + } + + abstract class CompositionBrush : CompositionObject + { + } + + sealed class CompositionColorBrush : CompositionBrush + { + public UI.Color? Color { get; set; } + } + + abstract class CompositionGradientBrush : CompositionBrush + { + public Vector2? AnchorPoint { get; set; } + + public Vector2? CenterPoint { get; set; } + + public IList ColorStops { get; } = new List(); + + public CompositionGradientExtendMode? ExtendMode { get; set; } + + public CompositionColorSpace? InterpolationSpace { get; set; } + + public CompositionMappingMode? MappingMode { get; set; } + + public Vector2? Offset { get; set; } + + public float? RotationAngleInDegrees { get; set; } + + public Vector2? Scale { get; set; } + + public Matrix3x2? TransformMatrix { get; set; } + } + + sealed class CompositionLinearGradientBrush : CompositionGradientBrush + { + public Vector2? StartPoint { get; set; } + + public Vector2? EndPoint { get; set; } + } + + sealed class CompositionRadialGradientBrush : CompositionGradientBrush + { + public Vector2? EllipseCenter { get; set; } + + public Vector2? EllipseRadius { get; set; } + + public Vector2? GradientOriginOffset { get; set; } + } + + sealed class CompositionMaskBrush : CompositionBrush + { + public CompositionBrush? Source { get; set; } + + public CompositionBrush? Mask { get; set; } + } + + sealed class CompositionSurfaceBrush : CompositionBrush + { + internal CompositionSurfaceBrush(ICompositionSurface? surface) => Surface = surface; + + public ICompositionSurface? Surface { get; set; } + } + + sealed class CompositionEffectBrush : CompositionBrush + { + readonly Dictionary _sources = new Dictionary(); + readonly CompositionEffectFactory _factory; + + internal CompositionEffectBrush(CompositionEffectFactory factory) => _factory = factory; + + public void SetSourceParameter(string name, CompositionBrush source) => _sources[name] = source; + + internal CompositionEffectFactory GetEffectFactory() => _factory; + + internal CompositionBrush? GetSourceParameter(string name) + => _sources.TryGetValue(name, out var result) ? result : null; + } + + sealed class CompositionEffectFactory : CompositionObject + { + internal CompositionEffectFactory(IGraphicsEffect effect) => Effect = effect; + + internal IGraphicsEffect Effect { get; } + + public CompositionEffectBrush CreateBrush() => new CompositionEffectBrush(this); + } + + sealed class CompositionColorGradientStop : CompositionObject + { + public UI.Color? Color { get; set; } + + public float? Offset { get; set; } + } + + sealed class CompositionViewBox : CompositionObject + { + public Vector2? Size { get; set; } + } + + abstract class CompositionClip : CompositionObject + { + public Vector2? CenterPoint { get; set; } + + public Vector2? Scale { get; set; } + } + + sealed class InsetClip : CompositionClip + { + public float? LeftInset { get; set; } + + public float? RightInset { get; set; } + + public float? TopInset { get; set; } + + public float? BottomInset { get; set; } + } + + sealed class CompositionGeometricClip : CompositionClip + { + public CompositionGeometry? Geometry { get; set; } + } + + abstract class CompositionShadow : CompositionObject + { + } + + sealed class DropShadow : CompositionShadow + { + public float? BlurRadius { get; set; } + + public UI.Color? Color { get; set; } + + public CompositionBrush? Mask { get; set; } + + public Vector3? Offset { get; set; } + + public float? Opacity { get; set; } + + public CompositionDropShadowSourcePolicy? SourcePolicy { get; set; } + } + + sealed class CompositionVisualSurface : CompositionObject, ICompositionSurface + { + public Visual? SourceVisual { get; set; } + + public Vector2? SourceSize { get; set; } + + public Vector2? SourceOffset { get; set; } + } + + abstract class CompositionEasingFunction : CompositionObject + { + } + + sealed class LinearEasingFunction : CompositionEasingFunction + { + } + + sealed class CubicBezierEasingFunction : CompositionEasingFunction + { + internal CubicBezierEasingFunction(Vector2 controlPoint1, Vector2 controlPoint2) + => (ControlPoint1, ControlPoint2) = (controlPoint1, controlPoint2); + + public Vector2 ControlPoint1 { get; } + + public Vector2 ControlPoint2 { get; } + } + + sealed class StepEasingFunction : CompositionEasingFunction + { + public int? StepCount { get; set; } + + public int? InitialStep { get; set; } + + public int? FinalStep { get; set; } + + public bool? IsInitialStepSingleFrame { get; set; } + + public bool? IsFinalStepSingleFrame { get; set; } + } + + abstract class CompositionAnimation : CompositionObject + { + readonly SortedDictionary _referenceParameters = + new SortedDictionary(StringComparer.Ordinal); + + public string? Target { get; set; } + + internal IReadOnlyDictionary ReferenceParameters => _referenceParameters; + + public void SetReferenceParameter(string key, CompositionObject compositionObject) + => _referenceParameters[key] = compositionObject; + } + + sealed class ExpressionAnimation : CompositionAnimation + { + internal ExpressionAnimation(string expression) => Expression = expression; + + public string Expression { get; } + } + + abstract class KeyFrameAnimation : CompositionAnimation + { + // Key frames are held in progress order and one deep per progress, which is + // what Windows.UI.Composition does and what makes the dump canonical. + readonly SortedList _keyFrames = new SortedList(); + + public TimeSpan Duration { get; set; } + + internal IEnumerable KeyFrames => _keyFrames.Values; + + public void InsertExpressionKeyFrame(float progress, string expression, CompositionEasingFunction? easing) + => Insert(new ExpressionKeyFrame(progress, easing, expression)); + + private protected void Insert(KeyFrame keyFrame) => _keyFrames[keyFrame.Progress] = keyFrame; + + internal abstract class KeyFrame + { + private protected KeyFrame(float progress, CompositionEasingFunction? easing) + => (Progress, Easing) = (progress, easing); + + public float Progress { get; } + + public CompositionEasingFunction? Easing { get; } + } + + internal sealed class ExpressionKeyFrame : KeyFrame + { + internal ExpressionKeyFrame(float progress, CompositionEasingFunction? easing, string expression) + : base(progress, easing) + => Expression = expression; + + public string Expression { get; } + } + + internal sealed class ValueKeyFrame : KeyFrame + { + internal ValueKeyFrame(float progress, CompositionEasingFunction? easing, object value) + : base(progress, easing) + => Value = value; + + public object Value { get; } + } + } + + sealed class ScalarKeyFrameAnimation : KeyFrameAnimation + { + public void InsertKeyFrame(float progress, float value, CompositionEasingFunction? easing) + => Insert(new ValueKeyFrame(progress, easing, value)); + } + + sealed class Vector2KeyFrameAnimation : KeyFrameAnimation + { + public void InsertKeyFrame(float progress, Vector2 value, CompositionEasingFunction? easing) + => Insert(new ValueKeyFrame(progress, easing, value)); + } + + sealed class Vector3KeyFrameAnimation : KeyFrameAnimation + { + public void InsertKeyFrame(float progress, Vector3 value, CompositionEasingFunction? easing) + => Insert(new ValueKeyFrame(progress, easing, value)); + } + + sealed class Vector4KeyFrameAnimation : KeyFrameAnimation + { + public void InsertKeyFrame(float progress, Vector4 value, CompositionEasingFunction? easing) + => Insert(new ValueKeyFrame(progress, easing, value)); + } + + sealed class ColorKeyFrameAnimation : KeyFrameAnimation + { + public CompositionColorSpace? InterpolationColorSpace { get; set; } + + public void InsertKeyFrame(float progress, UI.Color value, CompositionEasingFunction? easing) + => Insert(new ValueKeyFrame(progress, easing, value)); + } + + sealed class BooleanKeyFrameAnimation : KeyFrameAnimation + { + public void InsertKeyFrame(float progress, bool value) + => Insert(new ValueKeyFrame(progress, null, value)); + } + + sealed class PathKeyFrameAnimation : KeyFrameAnimation + { + public void InsertKeyFrame(float progress, CompositionPath value, CompositionEasingFunction? easing) + => Insert(new ValueKeyFrame(progress, easing, value)); + } + + sealed class Compositor + { + public ContainerVisual CreateContainerVisual() => new ContainerVisual(); + + public ShapeVisual CreateShapeVisual() => new ShapeVisual(); + + public SpriteVisual CreateSpriteVisual() => new SpriteVisual(); + + public LayerVisual CreateLayerVisual() => new LayerVisual(); + + public CompositionContainerShape CreateContainerShape() => new CompositionContainerShape(); + + public CompositionSpriteShape CreateSpriteShape() => new CompositionSpriteShape(); + + public CompositionPathGeometry CreatePathGeometry() => new CompositionPathGeometry(); + + public CompositionRectangleGeometry CreateRectangleGeometry() => new CompositionRectangleGeometry(); + + public CompositionRoundedRectangleGeometry CreateRoundedRectangleGeometry() + => new CompositionRoundedRectangleGeometry(); + + public CompositionEllipseGeometry CreateEllipseGeometry() => new CompositionEllipseGeometry(); + + public CompositionColorBrush CreateColorBrush() => new CompositionColorBrush(); + + public CompositionLinearGradientBrush CreateLinearGradientBrush() => new CompositionLinearGradientBrush(); + + public CompositionRadialGradientBrush CreateRadialGradientBrush() => new CompositionRadialGradientBrush(); + + public CompositionMaskBrush CreateMaskBrush() => new CompositionMaskBrush(); + + public CompositionSurfaceBrush CreateSurfaceBrush(ICompositionSurface? surface) + => new CompositionSurfaceBrush(surface); + + public CompositionEffectFactory CreateEffectFactory(IGraphicsEffect effect) + => new CompositionEffectFactory(effect); + + public CompositionColorGradientStop CreateColorGradientStop() => new CompositionColorGradientStop(); + + public CompositionViewBox CreateViewBox() => new CompositionViewBox(); + + public InsetClip CreateInsetClip() => new InsetClip(); + + public CompositionGeometricClip CreateGeometricClip() => new CompositionGeometricClip(); + + public DropShadow CreateDropShadow() => new DropShadow(); + + public CompositionVisualSurface CreateVisualSurface() => new CompositionVisualSurface(); + + public CompositionPropertySet CreatePropertySet() => new CompositionPropertySet(null); + + public AnimationController CreateAnimationController() => new AnimationController(); + + public LinearEasingFunction CreateLinearEasingFunction() => new LinearEasingFunction(); + + public CubicBezierEasingFunction CreateCubicBezierEasingFunction(Vector2 controlPoint1, Vector2 controlPoint2) + => new CubicBezierEasingFunction(controlPoint1, controlPoint2); + + public StepEasingFunction CreateStepEasingFunction() => new StepEasingFunction(); + + public ExpressionAnimation CreateExpressionAnimation(string expression) => new ExpressionAnimation(expression); + + public ScalarKeyFrameAnimation CreateScalarKeyFrameAnimation() => new ScalarKeyFrameAnimation(); + + public Vector2KeyFrameAnimation CreateVector2KeyFrameAnimation() => new Vector2KeyFrameAnimation(); + + public Vector3KeyFrameAnimation CreateVector3KeyFrameAnimation() => new Vector3KeyFrameAnimation(); + + public Vector4KeyFrameAnimation CreateVector4KeyFrameAnimation() => new Vector4KeyFrameAnimation(); + + public ColorKeyFrameAnimation CreateColorKeyFrameAnimation() => new ColorKeyFrameAnimation(); + + public BooleanKeyFrameAnimation CreateBooleanKeyFrameAnimation() => new BooleanKeyFrameAnimation(); + + public PathKeyFrameAnimation CreatePathKeyFrameAnimation() => new PathKeyFrameAnimation(); + } +} diff --git a/tests/CompDataFlatbuffer.Tests/CoverageTests.cs b/tests/CompDataFlatbuffer.Tests/CoverageTests.cs index b06281cf..547b6d54 100644 --- a/tests/CompDataFlatbuffer.Tests/CoverageTests.cs +++ b/tests/CompDataFlatbuffer.Tests/CoverageTests.cs @@ -61,6 +61,23 @@ public void EveryNodeTypeSurvivesARoundTrip() Assert.Equal(expected, actual); } + [Fact] + public void EveryNodeTypeIsInterpreted() + { + var root = BuildGraph(); + + // The interpreter has a branch per node type just as the deserializer does, + // so it is held to the same standard: every node type that a graph can + // contain must come out of the buffer unchanged. + var expected = CompositionTreeDumper.Dump(root); + var actual = InterpretedTreeDumper.Dump( + LottieRuntime.CompositionInterpreter.LoadComposition( + new Windows.UI.Composition.Compositor(), + CompositionSerializer.Serialize(root, 14, null, null, 100, 100))); + + Assert.Equal(expected, actual); + } + // Builds a graph containing one of everything. It is not a sensible animation; // its only job is to reach every branch of the serializer and the deserializer. static Visual BuildGraph() diff --git a/tests/CompDataFlatbuffer.Tests/InterpretedTreeDumper.cs b/tests/CompDataFlatbuffer.Tests/InterpretedTreeDumper.cs new file mode 100644 index 00000000..c323243d --- /dev/null +++ b/tests/CompDataFlatbuffer.Tests/InterpretedTreeDumper.cs @@ -0,0 +1,805 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Numerics; +using System.Text; +using Microsoft.Graphics.Canvas.Geometry; +using Windows.Graphics.Effects; +using Windows.UI.Composition; +using Windows.UI.Xaml.Media; +using Mgce = Microsoft.Graphics.Canvas.Effects; +using Wui = Windows.UI; + +namespace CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Tests +{ + /// + /// Renders a tree of composition objects as canonical text. + /// + /// + /// This is for the tree that + /// CompositionInterpreter builds. It writes the same text for the same + /// animation, which is what lets the interpreter be compared against the + /// WinCompData graph that the buffer was written from: the graph and the + /// interpreted tree are different object models, but if the two dumps are equal + /// then the interpreter created the same objects, gave them the same values, and + /// shared them in the same way. + /// + /// The two dumpers are deliberately not factored into one. Each is a plain + /// description of one object model, and keeping them separate means neither can + /// paper over a difference by describing both models in the same terms. + /// + static class InterpretedTreeDumper + { + /// + /// Renders an interpreted tree as canonical text. + /// + /// The root of the tree. + /// The canonical text of the tree. + public static string Dump(Visual root) + { + var writer = new Writer(); + writer.WriteVisual(root); + return writer.ToString(); + } + + sealed class Writer + { + readonly StringBuilder _builder = new StringBuilder(); + + readonly Dictionary _ids = + new Dictionary(ReferenceEqualityComparer.Instance); + + int _indent; + + public override string ToString() => _builder.ToString(); + + public void WriteVisual(Visual? value) + { + if (WriteNodeHeader(value, "Visual")) + { + return; + } + + WriteCompositionObject(value!); + + WriteOptional("BorderMode", value!.BorderMode); + WriteOptional("CenterPoint", value.CenterPoint); + WriteChild("Clip", value.Clip, WriteClip); + WriteOptional("IsVisible", value.IsVisible); + WriteOptional("Offset", value.Offset); + WriteOptional("Opacity", value.Opacity); + WriteOptional("RotationAngleInDegrees", value.RotationAngleInDegrees); + WriteOptional("RotationAxis", value.RotationAxis); + WriteOptional("Scale", value.Scale); + WriteOptional("Size", value.Size); + WriteOptional("TransformMatrix", value.TransformMatrix); + + if (value is ContainerVisual container) + { + WriteList("Children", container.Children.Visuals, WriteVisual); + } + + switch (value) + { + case ShapeVisual shapeVisual: + WriteList("Shapes", shapeVisual.Shapes, WriteShape); + WriteChild("ViewBox", shapeVisual.ViewBox, WriteViewBox); + break; + case SpriteVisual spriteVisual: + WriteChild("Brush", spriteVisual.Brush, WriteBrush); + WriteChild("Shadow", spriteVisual.Shadow, WriteShadow); + break; + case LayerVisual layerVisual: + WriteChild("Shadow", layerVisual.Shadow, WriteShadow); + break; + case ContainerVisual _: + break; + default: + throw new InvalidOperationException($"Unexpected visual {value.GetType().Name}."); + } + + _indent--; + } + + public void WriteShape(CompositionShape? value) + { + if (WriteNodeHeader(value, "Shape")) + { + return; + } + + WriteCompositionObject(value!); + + WriteOptional("CenterPoint", value!.CenterPoint); + WriteOptional("Offset", value.Offset); + WriteOptional("RotationAngleInDegrees", value.RotationAngleInDegrees); + WriteOptional("Scale", value.Scale); + WriteOptional("TransformMatrix", value.TransformMatrix); + + switch (value) + { + case CompositionContainerShape containerShape: + WriteList("Shapes", containerShape.Shapes, WriteShape); + break; + case CompositionSpriteShape spriteShape: + WriteChild("FillBrush", spriteShape.FillBrush, WriteBrush); + WriteChild("StrokeBrush", spriteShape.StrokeBrush, WriteBrush); + WriteChild("Geometry", spriteShape.Geometry, WriteGeometry); + WriteOptional("IsStrokeNonScaling", spriteShape.IsStrokeNonScaling); + WriteOptional("StrokeDashOffset", spriteShape.StrokeDashOffset); + WriteFloats("StrokeDashArray", spriteShape.StrokeDashArray); + WriteOptional("StrokeDashCap", spriteShape.StrokeDashCap); + WriteOptional("StrokeStartCap", spriteShape.StrokeStartCap); + WriteOptional("StrokeEndCap", spriteShape.StrokeEndCap); + WriteOptional("StrokeLineJoin", spriteShape.StrokeLineJoin); + WriteOptional("StrokeMiterLimit", spriteShape.StrokeMiterLimit); + WriteOptional("StrokeThickness", spriteShape.StrokeThickness); + break; + default: + throw new InvalidOperationException($"Unexpected shape {value.GetType().Name}."); + } + + _indent--; + } + + public void WriteGeometry(CompositionGeometry? value) + { + if (WriteNodeHeader(value, "Geometry")) + { + return; + } + + WriteCompositionObject(value!); + + WriteOptional("TrimStart", value!.TrimStart); + WriteOptional("TrimEnd", value.TrimEnd); + WriteOptional("TrimOffset", value.TrimOffset); + + switch (value) + { + case CompositionPathGeometry pathGeometry: + WriteChild("Path", pathGeometry.Path?.Source, WriteCanvasGeometry); + break; + case CompositionRoundedRectangleGeometry roundedRectangle: + WriteOptional("Offset", roundedRectangle.Offset); + WriteOptional("Size", roundedRectangle.Size); + Write("CornerRadius", roundedRectangle.CornerRadius ?? default); + break; + case CompositionRectangleGeometry rectangle: + WriteOptional("Offset", rectangle.Offset); + WriteOptional("Size", rectangle.Size); + break; + case CompositionEllipseGeometry ellipse: + Write("Center", ellipse.Center ?? default); + Write("Radius", ellipse.Radius ?? default); + break; + default: + throw new InvalidOperationException($"Unexpected geometry {value.GetType().Name}."); + } + + _indent--; + } + + public void WriteBrush(CompositionBrush? value) + { + if (WriteNodeHeader(value, "Brush")) + { + return; + } + + WriteCompositionObject(value!); + + switch (value) + { + case CompositionColorBrush colorBrush: + WriteOptional("Color", colorBrush.Color); + break; + case CompositionMaskBrush maskBrush: + WriteChild("Source", maskBrush.Source, WriteBrush); + WriteChild("Mask", maskBrush.Mask, WriteBrush); + break; + case CompositionSurfaceBrush surfaceBrush: + WriteChild("Surface", surfaceBrush.Surface, WriteSurface); + break; + case CompositionEffectBrush effectBrush: + var effect = effectBrush.GetEffectFactory().Effect; + WriteChild("Effect", effect, WriteEffect); + + foreach (var source in SourcesOf(effect)) + { + WriteChild( + $"Source({source.Name})", + effectBrush.GetSourceParameter(source.Name), + WriteBrush); + } + + break; + case CompositionLinearGradientBrush _: + case CompositionRadialGradientBrush _: + break; + default: + throw new InvalidOperationException($"Unexpected brush {value!.GetType().Name}."); + } + + if (value is CompositionGradientBrush gradientBrush) + { + WriteOptional("AnchorPoint", gradientBrush.AnchorPoint); + WriteOptional("CenterPoint", gradientBrush.CenterPoint); + WriteList("ColorStops", gradientBrush.ColorStops, WriteGradientStop); + WriteOptional("ExtendMode", gradientBrush.ExtendMode); + WriteOptional("InterpolationSpace", gradientBrush.InterpolationSpace); + WriteOptional("MappingMode", gradientBrush.MappingMode); + WriteOptional("Offset", gradientBrush.Offset); + WriteOptional("RotationAngleInDegrees", gradientBrush.RotationAngleInDegrees); + WriteOptional("Scale", gradientBrush.Scale); + WriteOptional("TransformMatrix", gradientBrush.TransformMatrix); + + switch (gradientBrush) + { + case CompositionLinearGradientBrush linear: + WriteOptional("StartPoint", linear.StartPoint); + WriteOptional("EndPoint", linear.EndPoint); + break; + case CompositionRadialGradientBrush radial: + WriteOptional("EllipseCenter", radial.EllipseCenter); + WriteOptional("EllipseRadius", radial.EllipseRadius); + WriteOptional("GradientOriginOffset", radial.GradientOriginOffset); + break; + } + } + + _indent--; + } + + public void WriteGradientStop(CompositionColorGradientStop? value) + { + if (WriteNodeHeader(value, "GradientStop")) + { + return; + } + + WriteCompositionObject(value!); + Write("Color", value!.Color ?? default); + Write("Offset", value.Offset ?? default); + _indent--; + } + + public void WriteViewBox(CompositionViewBox? value) + { + if (WriteNodeHeader(value, "ViewBox")) + { + return; + } + + WriteCompositionObject(value!); + Write("Size", value!.Size ?? default); + _indent--; + } + + public void WriteClip(CompositionClip? value) + { + if (WriteNodeHeader(value, "Clip")) + { + return; + } + + WriteCompositionObject(value!); + WriteOptional("CenterPoint", value!.CenterPoint); + WriteOptional("Scale", value.Scale); + + switch (value) + { + case InsetClip inset: + WriteOptional("LeftInset", inset.LeftInset); + WriteOptional("RightInset", inset.RightInset); + WriteOptional("TopInset", inset.TopInset); + WriteOptional("BottomInset", inset.BottomInset); + break; + case CompositionGeometricClip geometric: + WriteChild("Geometry", geometric.Geometry, WriteGeometry); + break; + default: + throw new InvalidOperationException($"Unexpected clip {value.GetType().Name}."); + } + + _indent--; + } + + public void WriteShadow(CompositionShadow? value) + { + if (WriteNodeHeader(value, "Shadow")) + { + return; + } + + WriteCompositionObject(value!); + + switch (value) + { + case DropShadow dropShadow: + WriteOptional("BlurRadius", dropShadow.BlurRadius); + WriteOptional("Color", dropShadow.Color); + WriteChild("Mask", dropShadow.Mask, WriteBrush); + WriteOptional("Offset", dropShadow.Offset); + WriteOptional("Opacity", dropShadow.Opacity); + WriteOptional("SourcePolicy", dropShadow.SourcePolicy); + break; + default: + throw new InvalidOperationException($"Unexpected shadow {value!.GetType().Name}."); + } + + _indent--; + } + + public void WriteSurface(ICompositionSurface? value) + { + if (WriteNodeHeader(value, "Surface")) + { + return; + } + + switch (value) + { + case CompositionVisualSurface visualSurface: + WriteCompositionObject(visualSurface); + WriteChild("SourceVisual", visualSurface.SourceVisual, WriteVisual); + WriteOptional("SourceSize", visualSurface.SourceSize); + WriteOptional("SourceOffset", visualSurface.SourceOffset); + break; + case LoadedImageSurfaceFromUri fromUri: + WriteLine($"Kind: FromUri"); + WriteLine($"Uri: {fromUri.Uri}"); + break; + case LoadedImageSurfaceFromStream fromStream: + WriteLine($"Kind: FromStream"); + WriteLine($"Bytes: {Convert.ToBase64String(fromStream.Bytes)}"); + break; + default: + throw new InvalidOperationException($"Unexpected surface {value!.GetType().Name}."); + } + + _indent--; + } + + public void WriteEffect(IGraphicsEffect? value) + { + if (WriteNodeHeader(value, "Effect")) + { + return; + } + + WriteLine($"Sources: {string.Join(", ", SourcesOf(value!).Select(s => s.Name))}"); + + switch (value) + { + case Mgce.CompositeEffect composite: + Write("Mode", composite.Mode); + break; + case Mgce.GaussianBlurEffect blur: + Write("BlurAmount", blur.BlurAmount); + break; + default: + throw new InvalidOperationException($"Unexpected effect {value!.GetType().Name}."); + } + + _indent--; + } + + public void WriteEasing(CompositionEasingFunction? value) + { + if (WriteNodeHeader(value, "Easing")) + { + return; + } + + WriteCompositionObject(value!); + + switch (value) + { + case LinearEasingFunction _: + break; + case CubicBezierEasingFunction cubicBezier: + Write("ControlPoint1", cubicBezier.ControlPoint1); + Write("ControlPoint2", cubicBezier.ControlPoint2); + break; + case StepEasingFunction step: + WriteOptional("StepCount", step.StepCount); + WriteOptional("InitialStep", step.InitialStep); + WriteOptional("FinalStep", step.FinalStep); + WriteOptional("IsInitialStepSingleFrame", step.IsInitialStepSingleFrame); + WriteOptional("IsFinalStepSingleFrame", step.IsFinalStepSingleFrame); + break; + default: + throw new InvalidOperationException($"Unexpected easing {value!.GetType().Name}."); + } + + _indent--; + } + + public void WriteAnimation(CompositionAnimation? value) + { + if (WriteNodeHeader(value, "Animation")) + { + return; + } + + WriteCompositionObject(value!); + WriteLine($"Target: {Text(value!.Target)}"); + + foreach (var parameter in value.ReferenceParameters.OrderBy(p => p.Key, StringComparer.Ordinal)) + { + WriteChild($"Reference({parameter.Key})", parameter.Value, WriteCompositionObjectReference); + } + + switch (value) + { + case ExpressionAnimation expression: + WriteLine($"Expression: {expression.Expression}"); + break; + case KeyFrameAnimation keyFrameAnimation: + WriteLine($"Duration: {keyFrameAnimation.Duration.Ticks}"); + + if (value is ColorKeyFrameAnimation color) + { + Write("InterpolationColorSpace", color.InterpolationColorSpace ?? CompositionColorSpace.Auto); + } + + foreach (var keyFrame in keyFrameAnimation.KeyFrames) + { + WriteKeyFrame(keyFrame); + } + + break; + default: + throw new InvalidOperationException($"Unexpected animation {value.GetType().Name}."); + } + + _indent--; + } + + void WriteKeyFrame(KeyFrameAnimation.KeyFrame value) + { + WriteLine($"KeyFrame @{Text(value.Progress)}"); + _indent++; + WriteChild("Easing", value.Easing, WriteEasing); + + switch (value) + { + case KeyFrameAnimation.ExpressionKeyFrame expression: + WriteLine($"Expression: {expression.Expression}"); + break; + case KeyFrameAnimation.ValueKeyFrame valueKeyFrame: + if (valueKeyFrame.Value is CompositionPath path) + { + WriteChild("Value", path.Source, WriteCanvasGeometry); + } + else + { + WriteLine($"Value: {Text(valueKeyFrame.Value)}"); + } + + break; + default: + throw new InvalidOperationException($"Unexpected key frame {value.GetType().Name}."); + } + + _indent--; + } + + public void WriteCanvasGeometry(CanvasGeometry? value) + { + if (WriteNodeHeader(value, "CanvasGeometry")) + { + return; + } + + switch (value) + { + case CanvasGeometry.Combination combination: + WriteChild("A", combination.A, WriteCanvasGeometry); + WriteChild("B", combination.B, WriteCanvasGeometry); + Write("CombineMode", combination.CombineMode); + Write("Matrix", combination.Matrix); + break; + case CanvasGeometry.TransformedGeometry transformed: + WriteChild("Source", transformed.SourceGeometry, WriteCanvasGeometry); + Write("TransformMatrix", transformed.TransformMatrix); + break; + case CanvasGeometry.Group group: + Write("FilledRegionDetermination", group.FilledRegionDetermination); + WriteList("Geometries", group.Geometries, WriteCanvasGeometry); + break; + case CanvasGeometry.Path path: + Write("FilledRegionDetermination", path.FilledRegionDetermination); + + foreach (var command in path.Commands) + { + WriteLine(Text(command)); + } + + break; + case CanvasGeometry.Ellipse ellipse: + WriteLine( + $"Ellipse: {Text(ellipse.X)} {Text(ellipse.Y)} " + + $"{Text(ellipse.RadiusX)} {Text(ellipse.RadiusY)}"); + break; + case CanvasGeometry.RoundedRectangle rectangle: + WriteLine( + $"RoundedRectangle: {Text(rectangle.X)} {Text(rectangle.Y)} " + + $"{Text(rectangle.W)} {Text(rectangle.H)} " + + $"{Text(rectangle.RadiusX)} {Text(rectangle.RadiusY)}"); + break; + default: + throw new InvalidOperationException($"Unexpected canvas geometry {value!.GetType().Name}."); + } + + _indent--; + } + + void WriteCompositionObjectReference(CompositionObject? value) + { + switch (value) + { + case null: + WriteLine("null"); + break; + case Visual visual: + WriteVisual(visual); + break; + case CompositionShape shape: + WriteShape(shape); + break; + case CompositionGeometry geometry: + WriteGeometry(geometry); + break; + case CompositionBrush brush: + WriteBrush(brush); + break; + case CompositionAnimation animation: + WriteAnimation(animation); + break; + case CompositionEasingFunction easing: + WriteEasing(easing); + break; + case CompositionPropertySet propertySet: + WritePropertySet(propertySet); + break; + case CompositionVisualSurface surface: + WriteSurface(surface); + break; + case CompositionClip clip: + WriteClip(clip); + break; + case AnimationController controller: + WriteController(controller); + break; + case CompositionShadow shadow: + WriteShadow(shadow); + break; + case CompositionColorGradientStop stop: + WriteGradientStop(stop); + break; + case CompositionViewBox viewBox: + WriteViewBox(viewBox); + break; + default: + throw new InvalidOperationException($"Unexpected reference to {value.GetType().Name}."); + } + } + + void WritePropertySet(CompositionPropertySet? value) + { + if (WriteNodeHeader(value, "PropertySet")) + { + return; + } + + WritePropertySetContents(value!); + _indent--; + } + + void WriteController(AnimationController? value) + { + if (WriteNodeHeader(value, "Controller")) + { + return; + } + + WriteLine($"IsCustom: {value!.IsCustom}"); + WriteLine($"IsPaused: {value.IsPaused}"); + WriteLine($"TargetProperty: {Text(value.TargetProperty)}"); + WriteCompositionObject(value); + _indent--; + } + + void WriteCompositionObject(CompositionObject value) + { + if (value.Comment is not null) + { + WriteLine($"Comment: {value.Comment}"); + } + + var properties = value.Properties; + if (value is not CompositionPropertySet && + (properties.Names.Count > 0 || properties.Animators.Count > 0)) + { + WriteLine("Properties"); + _indent++; + WritePropertySetContents(properties); + _indent--; + } + + foreach (var animator in value.Animators) + { + WriteLine($"Animator: {animator.AnimatedProperty}"); + _indent++; + WriteChild("Animation", animator.Animation, WriteAnimation); + + if (animator.Controller is not null) + { + WriteChild("Controller", animator.Controller, WriteController); + } + + _indent--; + } + } + + void WritePropertySetContents(CompositionPropertySet value) + { + foreach (var (name, _) in value.Names.Select(n => (n.Key, n.Value))) + { + WriteLine($"{name}: {Text(value.GetValue(name))}"); + } + + foreach (var animator in value.Animators) + { + WriteLine($"Animator: {animator.AnimatedProperty}"); + _indent++; + WriteChild("Animation", animator.Animation, WriteAnimation); + _indent--; + } + } + + // The sources of an effect, in the order that they were given to it. + static IEnumerable SourcesOf(IGraphicsEffect effect) + => effect switch + { + Mgce.CompositeEffect composite => composite.Sources.Cast(), + Mgce.GaussianBlurEffect blur => new[] { (CompositionEffectSourceParameter)blur.Source! }, + _ => throw new InvalidOperationException($"Unexpected effect {effect.GetType().Name}."), + }; + + bool WriteNodeHeader(object? value, string category) + { + if (value is null) + { + WriteLine($"{category}: null"); + return true; + } + + if (_ids.TryGetValue(value, out var existing)) + { + WriteLine($"{category} #{existing} (shared)"); + return true; + } + + var id = _ids.Count; + _ids.Add(value, id); + WriteLine($"{category} #{id} {TypeNameOf(value)}"); + _indent++; + return false; + } + + // The names of the stand-in types are the names of the real composition + // types, which are also the names of the WinCompData types, so the two + // dumps name the same object identically. + static string TypeNameOf(object value) + => value switch + { + LoadedImageSurfaceFromUri _ => "FromUri", + LoadedImageSurfaceFromStream _ => "FromStream", + _ => value.GetType().Name, + }; + + void WriteChild(string name, T? value, Action write) + where T : class + { + WriteLine($"{name}:"); + _indent++; + write(value); + _indent--; + } + + void WriteList(string name, IEnumerable values, Action write) + where T : class + { + var array = values.ToArray(); + if (array.Length == 0) + { + return; + } + + WriteLine($"{name}: [{array.Length}]"); + _indent++; + foreach (var value in array) + { + write(value); + } + + _indent--; + } + + void WriteOptional(string name, T? value) + where T : struct + { + if (value.HasValue) + { + WriteLine($"{name}: {Text(value.Value)}"); + } + } + + void Write(string name, T value) + where T : struct + => WriteLine($"{name}: {Text(value)}"); + + void WriteFloats(string name, IEnumerable values) + { + var array = values.ToArray(); + if (array.Length > 0) + { + WriteLine($"{name}: {string.Join(", ", array.Select(v => Text(v)))}"); + } + } + + void WriteLine(string text) + => _builder.Append(' ', _indent * 2).Append(text).Append('\n'); + + static string Text(object? value) + => value switch + { + null => "null", + float f => f.ToString("R", CultureInfo.InvariantCulture), + Vector2 v => $"<{Text(v.X)}, {Text(v.Y)}>", + Vector3 v => $"<{Text(v.X)}, {Text(v.Y)}, {Text(v.Z)}>", + Vector4 v => $"<{Text(v.X)}, {Text(v.Y)}, {Text(v.Z)}, {Text(v.W)}>", + Matrix3x2 m => + $"[{Text(m.M11)}, {Text(m.M12)}, {Text(m.M21)}, " + + $"{Text(m.M22)}, {Text(m.M31)}, {Text(m.M32)}]", + Matrix4x4 m => + $"[{Text(m.M11)}, {Text(m.M12)}, {Text(m.M13)}, {Text(m.M14)}, " + + $"{Text(m.M21)}, {Text(m.M22)}, {Text(m.M23)}, {Text(m.M24)}, " + + $"{Text(m.M31)}, {Text(m.M32)}, {Text(m.M33)}, {Text(m.M34)}, " + + $"{Text(m.M41)}, {Text(m.M42)}, {Text(m.M43)}, {Text(m.M44)}]", + Wui.Color c => $"#{c.A:X2}{c.R:X2}{c.G:X2}{c.B:X2}", + CanvasPathBuilder.Command.BeginFigure begin => $"BeginFigure {Text(begin.StartPoint)}", + CanvasPathBuilder.Command.EndFigure end => $"EndFigure {end.FigureLoop}", + CanvasPathBuilder.Command.AddLine line => $"AddLine {Text(line.EndPoint)}", + CanvasPathBuilder.Command.AddCubicBezier bezier => + $"AddCubicBezier {Text(bezier.ControlPoint1)} " + + $"{Text(bezier.ControlPoint2)} {Text(bezier.EndPoint)}", + _ => value.ToString() ?? string.Empty, + }; + } + + sealed class ReferenceEqualityComparer : IEqualityComparer + { + internal static readonly ReferenceEqualityComparer Instance = new ReferenceEqualityComparer(); + + ReferenceEqualityComparer() + { + } + + bool IEqualityComparer.Equals(object? x, object? y) => ReferenceEquals(x, y); + + int IEqualityComparer.GetHashCode(object obj) + => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj); + } + } +} diff --git a/tests/CompDataFlatbuffer.Tests/InterpreterTests.cs b/tests/CompDataFlatbuffer.Tests/InterpreterTests.cs new file mode 100644 index 00000000..224cbf6a --- /dev/null +++ b/tests/CompDataFlatbuffer.Tests/InterpreterTests.cs @@ -0,0 +1,214 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Linq; +using CommunityToolkit.WinUI.Lottie.LottieRuntime; +using Google.FlatBuffers; +using Windows.UI.Composition; +using Xunit; +using Fb = CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema; + +namespace CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Tests +{ + /// + /// Verifies that the managed interpreter builds the composition that the buffer + /// describes. + /// + /// + /// The interpreter goes straight from bytes to composition objects, so unlike the + /// deserializer it cannot be checked by comparing WinCompData graphs. Instead the + /// tree it builds is dumped in the same canonical text as the graph that the + /// buffer was written from, and the two are compared. Equal text means the + /// interpreter created the same objects, gave them the same values, and shared + /// them in the same way, which is the whole of what an interpreter has to get + /// right. + /// + /// The composition APIs that the interpreter calls only exist on Windows, so this + /// runs against the stand-ins in CompositionApiStandIns.cs. That means these tests + /// verify the calls that the interpreter makes rather than the pixels that Windows + /// draws; the pixels are the same as the deserializer's precisely because the + /// calls are. + /// + public class InterpreterTests + { + [Theory] + [MemberData(nameof(Corpus.FileNames), MemberType = typeof(Corpus))] + public void InterpretedTreeMatchesTheGraph(string fileName) + { + var translated = Corpus.Translate(fileName); + + var expected = CompositionTreeDumper.Dump(translated.RootVisual); + var actual = InterpretedTreeDumper.Dump(Interpret(translated.Serialize())); + + Assert.Equal(expected, actual); + } + + [Theory] + [MemberData(nameof(Corpus.FileNames), MemberType = typeof(Corpus))] + public void InterpretationIsRepeatable(string fileName) + { + var buffer = Corpus.Translate(fileName).Serialize(); + + // Interpreting the same buffer twice must produce the same tree. A buffer + // is read in place, so this also shows that reading it does not modify it. + Assert.Equal( + InterpretedTreeDumper.Dump(Interpret(buffer)), + InterpretedTreeDumper.Dump(Interpret(buffer))); + } + + [Theory] + [MemberData(nameof(Corpus.FileNames), MemberType = typeof(Corpus))] + public void ProgressPropertySetHoldsTheProgressProperty(string fileName) + { + var root = Interpret(Corpus.Translate(fileName).Serialize()); + + // Every translated animation is driven by a single Progress property on the + // root visual's property set. A host that cannot find it cannot play the + // animation, so this is checked separately from the dump. + Assert.Contains( + "Progress", + CompositionInterpreter.ProgressPropertySet(root).Names.Keys, + StringComparer.Ordinal); + } + + [Fact] + public void EmptyBufferIsRejected() + => Assert.Throws(() => Interpret(Array.Empty())); + + [Fact] + public void GarbageIsRejected() + { + var garbage = new byte[512]; + new Random(0).NextBytes(garbage); + + Assert.Throws(() => Interpret(garbage)); + } + + [Fact] + public void BufferWithTheWrongFileIdentifierIsRejected() + { + var buffer = Corpus.Translate("LightBulb.json").Serialize(); + + // The file identifier sits at offset 4, immediately after the root offset. + buffer[4] = (byte)'X'; + + Assert.Throws(() => Interpret(buffer)); + } + + [Fact] + public void BufferWithNoRootVisualIsRejected() + => Assert.Throws( + () => Interpret(CreateComposition(Format.Version, 0, withRootVisual: false))); + + [Fact] + public void NewerSchemaVersionIsRejected() + { + // A buffer written by a newer serializer may use fields that this build + // does not know about, so loading it would silently lose part of the + // animation. It is refused instead. + var exception = Assert.Throws( + () => Interpret(CreateComposition((ushort)(Format.Version + 1), 0))); + + Assert.Contains($"{Format.Version + 1}", exception.Message, StringComparison.Ordinal); + } + + [Fact] + public void CurrentSchemaVersionIsAccepted() + => Assert.NotNull(Interpret(CreateComposition(Format.Version, 0))); + + [Fact] + public void UnavailableApiContractIsRejected() + { + // An animation that needs composition APIs that this version of Windows + // does not have is refused rather than built into a tree that cannot run. + var exception = Assert.Throws( + () => Interpret(CreateComposition(Format.Version, ushort.MaxValue))); + + Assert.Contains("UniversalApiContract", exception.Message, StringComparison.Ordinal); + } + + [Theory] + [MemberData(nameof(Corpus.FileNames), MemberType = typeof(Corpus))] + public void TruncatedBuffersAreRejected(string fileName) + { + var buffer = Corpus.Translate(fileName).Serialize(); + + // Truncation is checked at a spread of lengths rather than at every length, + // which would make the test run for minutes without finding anything more. + for (var length = 0; length < buffer.Length; length += Math.Max(1, buffer.Length / 64)) + { + var truncated = new byte[length]; + Array.Copy(buffer, truncated, length); + + Assert.Throws(() => Interpret(truncated)); + } + } + + [Theory] + [MemberData(nameof(Corpus.FileNames), MemberType = typeof(Corpus))] + public void CorruptedBuffersDoNotCrash(string fileName) + { + var original = Corpus.Translate(fileName).Serialize(); + var random = new Random(fileName.GetHashCode(StringComparison.Ordinal)); + + // Unlike truncation, a flipped bit may leave a buffer that is still valid, + // just describing a different animation. So the requirement here is weaker: + // the interpreter either succeeds or reports a format error, but never + // throws something else and never reads outside the buffer. + for (var i = 0; i < 200; i++) + { + var corrupted = (byte[])original.Clone(); + corrupted[random.Next(corrupted.Length)] ^= (byte)(1 << random.Next(8)); + + try + { + Interpret(corrupted); + } + catch (FlatBufferFormatException) + { + } + catch (NotSupportedException) + { + // A flipped bit can also land in the schema version or the required + // API contract version. + } + } + } + + static Visual Interpret(byte[] buffer) + => CompositionInterpreter.LoadComposition(new Compositor(), buffer); + + // Builds the smallest possible composition: one empty container visual. Used by + // the tests of the header, which have nothing to do with the contents. + static byte[] CreateComposition(ushort schemaVersion, ushort requiredUapVersion, bool withRootVisual = true) + { + var builder = new FlatBufferBuilder(256); + + Fb.Visual.StartVisual(builder); + Fb.Visual.AddKind(builder, Fb.VisualKind.Container); + var visual = Fb.Visual.EndVisual(builder); + + var visuals = Fb.LottieComposition.CreateVisualsVector(builder, new[] { visual }); + + Fb.LottieComposition.StartLottieComposition(builder); + Fb.LottieComposition.AddSchemaVersion(builder, schemaVersion); + Fb.LottieComposition.AddRequiredUapVersion(builder, requiredUapVersion); + + if (withRootVisual) + { + Fb.LottieComposition.AddRootVisual(builder, 0); + } + + Fb.LottieComposition.AddVisuals(builder, visuals); + var composition = Fb.LottieComposition.EndLottieComposition(builder); + + Fb.LottieComposition.FinishLottieCompositionBuffer(builder, composition); + + return builder.SizedByteArray(); + } + } +} From 44b3258001269791979ac89983f6f4df0e2573d8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:20:52 +0000 Subject: [PATCH 28/39] Simplify property set name enumeration in the interpreted tree dumper Co-authored-by: jonwis <18537118+jonwis@users.noreply.github.com> --- tests/CompDataFlatbuffer.Tests/InterpretedTreeDumper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CompDataFlatbuffer.Tests/InterpretedTreeDumper.cs b/tests/CompDataFlatbuffer.Tests/InterpretedTreeDumper.cs index c323243d..82035b07 100644 --- a/tests/CompDataFlatbuffer.Tests/InterpretedTreeDumper.cs +++ b/tests/CompDataFlatbuffer.Tests/InterpretedTreeDumper.cs @@ -653,7 +653,7 @@ void WriteCompositionObject(CompositionObject value) void WritePropertySetContents(CompositionPropertySet value) { - foreach (var (name, _) in value.Names.Select(n => (n.Key, n.Value))) + foreach (var name in value.Names.Keys) { WriteLine($"{name}: {Text(value.GetValue(name))}"); } From 731b12c8f9cb948e54c8464c02b32b5511ffff66 Mon Sep 17 00:00:00 2001 From: Jon Wiswall Date: Wed, 2 Sep 2026 12:40:35 -0700 Subject: [PATCH 29/39] Improve managed runtime Native AOT integration Split runtime schema dependencies from tool-side serialization, avoid copying byte-array buffers, and process keyframes in one pass. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 408cb9b9-d2b1-4a7a-bc3e-f6ab25b8793f --- .../CompDataFlatbuffer.projitems | 11 +- .../CompDataFlatbufferRuntime.projitems | 18 +++ .../LottieRuntime/CompositionInterpreter.cs | 134 +++++++++++------- source/LottieRuntime/LottieRuntime.projitems | 4 + source/LottieRuntime/README.md | 6 +- 5 files changed, 111 insertions(+), 62 deletions(-) create mode 100644 source/CompDataFlatbuffer/CompDataFlatbufferRuntime.projitems diff --git a/source/CompDataFlatbuffer/CompDataFlatbuffer.projitems b/source/CompDataFlatbuffer/CompDataFlatbuffer.projitems index 90d3537b..69bfe016 100644 --- a/source/CompDataFlatbuffer/CompDataFlatbuffer.projitems +++ b/source/CompDataFlatbuffer/CompDataFlatbuffer.projitems @@ -8,16 +8,13 @@ CompDataFlatbuffer + - - - - - - diff --git a/source/CompDataFlatbuffer/CompDataFlatbufferRuntime.projitems b/source/CompDataFlatbuffer/CompDataFlatbufferRuntime.projitems new file mode 100644 index 00000000..01949a2a --- /dev/null +++ b/source/CompDataFlatbuffer/CompDataFlatbufferRuntime.projitems @@ -0,0 +1,18 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + true + + + CompDataFlatbuffer + + + + + + + + diff --git a/source/LottieRuntime/CompositionInterpreter.cs b/source/LottieRuntime/CompositionInterpreter.cs index 123061a1..b7071b40 100644 --- a/source/LottieRuntime/CompositionInterpreter.cs +++ b/source/LottieRuntime/CompositionInterpreter.cs @@ -125,6 +125,24 @@ enum CanvasGeometryState Realized, } + /// + /// Builds the visual tree described by a serialized composition. + /// + /// The compositor that creates the objects. + /// A buffer produced by CompositionSerializer. + /// The root of the visual tree. + /// The buffer is not a well formed composition. + /// The composition needs a newer schema or a newer version of Windows. + public static Wc.Visual LoadComposition(Wc.Compositor compositor, byte[] bytes) + { + if (bytes is null) + { + throw new ArgumentNullException(nameof(bytes)); + } + + return LoadComposition(compositor, new ByteBuffer(bytes), bytes.Length); + } + /// /// Builds the visual tree described by a serialized composition. /// @@ -134,6 +152,9 @@ enum CanvasGeometryState /// The buffer is not a well formed composition. /// The composition needs a newer schema or a newer version of Windows. public static Wc.Visual LoadComposition(Wc.Compositor compositor, ReadOnlySpan bytes) + => LoadComposition(compositor, new ByteBuffer(bytes.ToArray()), bytes.Length); + + static Wc.Visual LoadComposition(Wc.Compositor compositor, ByteBuffer buffer, int length) { if (compositor is null) { @@ -142,9 +163,7 @@ public static Wc.Visual LoadComposition(Wc.Compositor compositor, ReadOnlySpan /// Returns the property set that drives an interpreted composition. /// - /// A root returned by . + /// A root returned by LoadComposition. /// The property set that holds the animation's Progress property. public static Wc.CompositionPropertySet ProgressPropertySet(Wc.Visual root) { @@ -1507,35 +1526,34 @@ void ApplyGradientBrushProperties(Wc.CompositionGradientBrush target, Fb.Brush t Wc.CompositionAnimation CreateKeyFrameAnimation(Fb.Animation table) { Wc.KeyFrameAnimation result; + Wc.ScalarKeyFrameAnimation? scalar = null; + Wc.Vector2KeyFrameAnimation? vector2 = null; + Wc.Vector3KeyFrameAnimation? vector3 = null; + Wc.Vector4KeyFrameAnimation? vector4 = null; + Wc.ColorKeyFrameAnimation? color = null; + Wc.BooleanKeyFrameAnimation? boolean = null; + Wc.PathKeyFrameAnimation? path = null; switch (table.Kind) { case Fb.AnimationKind.Scalar: - var scalar = _compositor.CreateScalarKeyFrameAnimation(); - ForEachValueKeyFrame(table, frame => - scalar.InsertKeyFrame(frame.Progress, frame.Scalar, GetEasing(frame.Easing))); + scalar = _compositor.CreateScalarKeyFrameAnimation(); result = scalar; break; case Fb.AnimationKind.Vector2: - var vector2 = _compositor.CreateVector2KeyFrameAnimation(); - ForEachValueKeyFrame(table, frame => - vector2.InsertKeyFrame(frame.Progress, ToVector2(frame.Vector), GetEasing(frame.Easing))); + vector2 = _compositor.CreateVector2KeyFrameAnimation(); result = vector2; break; case Fb.AnimationKind.Vector3: - var vector3 = _compositor.CreateVector3KeyFrameAnimation(); - ForEachValueKeyFrame(table, frame => - vector3.InsertKeyFrame(frame.Progress, ToVector3(frame.Vector), GetEasing(frame.Easing))); + vector3 = _compositor.CreateVector3KeyFrameAnimation(); result = vector3; break; case Fb.AnimationKind.Vector4: - var vector4 = _compositor.CreateVector4KeyFrameAnimation(); - ForEachValueKeyFrame(table, frame => - vector4.InsertKeyFrame(frame.Progress, ToVector4(frame.Vector), GetEasing(frame.Easing))); + vector4 = _compositor.CreateVector4KeyFrameAnimation(); result = vector4; break; case Fb.AnimationKind.Color: - var color = _compositor.CreateColorKeyFrameAnimation(); + color = _compositor.CreateColorKeyFrameAnimation(); if (table.InterpolationColorSpace.HasValue) { @@ -1543,40 +1561,20 @@ Wc.CompositionAnimation CreateKeyFrameAnimation(Fb.Animation table) (Wc.CompositionColorSpace)table.InterpolationColorSpace.Value; } - ForEachValueKeyFrame(table, frame => - color.InsertKeyFrame( - frame.Progress, - ToColor(Require(frame.Color, "color")), - GetEasing(frame.Easing))); result = color; break; case Fb.AnimationKind.Boolean: - var boolean = _compositor.CreateBooleanKeyFrameAnimation(); - - // A boolean cannot be interpolated, so it has no easing. - ForEachValueKeyFrame(table, frame => - boolean.InsertKeyFrame(frame.Progress, frame.Scalar != 0)); + boolean = _compositor.CreateBooleanKeyFrameAnimation(); result = boolean; break; case Fb.AnimationKind.Path: - var path = _compositor.CreatePathKeyFrameAnimation(); - ForEachValueKeyFrame(table, frame => - path.InsertKeyFrame( - frame.Progress, - new Wc.CompositionPath( - GetCanvasGeometry(frame.Path) - ?? throw new FlatBufferFormatException("A path key frame has no path.")), - GetEasing(frame.Easing))); + path = _compositor.CreatePathKeyFrameAnimation(); result = path; break; default: throw new FlatBufferFormatException($"Unsupported animation kind {table.Kind}."); } - // Expression key frames are inserted through the base type, so they do not - // have to be repeated in every branch above. They are inserted after the - // value key frames, which is safe because a key frame animation is a map - // keyed on progress rather than a list. for (var i = 0; i < table.KeyFramesLength; i++) { var frame = Require(table.KeyFrames(i), "key frame"); @@ -1588,6 +1586,51 @@ Wc.CompositionAnimation CreateKeyFrameAnimation(Fb.Animation table) GetRequiredString(frame.Expression), GetEasing(frame.Easing)); } + else if (frame.Kind == Fb.KeyFrameKind.Value) + { + switch (table.Kind) + { + case Fb.AnimationKind.Scalar: + scalar!.InsertKeyFrame(frame.Progress, frame.Scalar, GetEasing(frame.Easing)); + break; + case Fb.AnimationKind.Vector2: + vector2!.InsertKeyFrame( + frame.Progress, + ToVector2(frame.Vector), + GetEasing(frame.Easing)); + break; + case Fb.AnimationKind.Vector3: + vector3!.InsertKeyFrame( + frame.Progress, + ToVector3(frame.Vector), + GetEasing(frame.Easing)); + break; + case Fb.AnimationKind.Vector4: + vector4!.InsertKeyFrame( + frame.Progress, + ToVector4(frame.Vector), + GetEasing(frame.Easing)); + break; + case Fb.AnimationKind.Color: + color!.InsertKeyFrame( + frame.Progress, + ToColor(Require(frame.Color, "color")), + GetEasing(frame.Easing)); + break; + case Fb.AnimationKind.Boolean: + // A boolean cannot be interpolated, so it has no easing. + boolean!.InsertKeyFrame(frame.Progress, frame.Scalar != 0); + break; + case Fb.AnimationKind.Path: + path!.InsertKeyFrame( + frame.Progress, + new Wc.CompositionPath( + GetCanvasGeometry(frame.Path) + ?? throw new FlatBufferFormatException("A path key frame has no path.")), + GetEasing(frame.Easing)); + break; + } + } } result.Duration = TimeSpan.FromTicks(table.DurationTicks); @@ -1595,19 +1638,6 @@ Wc.CompositionAnimation CreateKeyFrameAnimation(Fb.Animation table) return result; } - void ForEachValueKeyFrame(Fb.Animation table, Action apply) - { - for (var i = 0; i < table.KeyFramesLength; i++) - { - var frame = Require(table.KeyFrames(i), "key frame"); - - if (frame.Kind == Fb.KeyFrameKind.Value) - { - apply(frame); - } - } - } - // --------------------------------------------------------------------- // Paths. // diff --git a/source/LottieRuntime/LottieRuntime.projitems b/source/LottieRuntime/LottieRuntime.projitems index bfd579d4..403fb1e2 100644 --- a/source/LottieRuntime/LottieRuntime.projitems +++ b/source/LottieRuntime/LottieRuntime.projitems @@ -8,6 +8,10 @@ LottieRuntime + diff --git a/source/LottieRuntime/README.md b/source/LottieRuntime/README.md index 23d6015f..3b94437e 100644 --- a/source/LottieRuntime/README.md +++ b/source/LottieRuntime/README.md @@ -89,9 +89,9 @@ takes the Win2D dependency, exactly as a consumer of `Instantiator` or of ## Incorporate it in a build `LottieRuntime.projitems` is a shared project. Import it into the application -project alongside `CompDataFlatbuffer.projitems`, which contains the generated schema -binding and the format constants, and add a package reference to -`Google.FlatBuffers`. +project and add a package reference to `Google.FlatBuffers`. The runtime imports +the generated schema binding and format constants without bringing in the +tool-side serializer, deserializer, or `WinCompData` model. Define `WINAPPSDK` when building for the Windows App SDK, which switches the interpreter from `Windows.UI.Composition` to `Microsoft.UI.Composition`. Define From 9ebcc9e42118491cd312b56f6677e44e90f09138 Mon Sep 17 00:00:00 2001 From: Jon Wiswall Date: Tue, 29 Sep 2026 23:31:56 -0700 Subject: [PATCH 30/39] Remove ineffective Visual Studio setup step --- .github/workflows/dotnet-build.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index 8c787688..ee79e8db 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -31,12 +31,6 @@ jobs: with: fetch-depth: 0 - # Set up a VS build environment. - - name: Setup Environment Variables - run: | - "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat" -no_logo - shell: cmd - # Install NuGet tools. - name: Setup NuGet uses: NuGet/setup-nuget@v2 @@ -169,4 +163,4 @@ jobs: **/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_PACKAGE_PUSH_TOKEN }} - --skip-duplicate \ No newline at end of file + --skip-duplicate From 247865119de7fe141e1d6a18159c116f26180c85 Mon Sep 17 00:00:00 2001 From: Jon Wiswall Date: Mon, 12 Oct 2026 16:39:40 -0700 Subject: [PATCH 31/39] Refactor native Lottie runtime integration Remove the managed and COM loader implementations, add projection-selectable native source, emit multi-UAP FlatBuffer variants, and consolidate the validation tool under source/LottieRuntime. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: adcabe6f-aa5c-4e06-948d-b49e5adae80c --- Lottie-Windows.sln | 24 + LottieGen/DotnetTool/LottieGen.csproj | 13 +- README.md | 2 +- build/RegenerateFlatbuffers.ps1 | 2 +- dlls/LottieRuntime/LottieRuntime.def | 6 - dlls/LottieRuntime/LottieRuntime.manifest | 14 - dlls/LottieRuntime/LottieRuntime.vcxproj | 99 - dlls/LottieRuntime/LottieRuntimeCom.cpp | 138 -- dlls/LottieRuntime/LottieRuntimeCom.h | 25 - dlls/LottieRuntime/README.md | 180 -- dlls/LottieRuntimeTool/README.md | 54 - dlls/README.md | 8 - source/LottieFlatbuffer/README.md | 7 +- source/LottieGen/LottieJsonFileProcessor.cs | 79 +- source/LottieGenExe/Main.cs | 14 +- .../LottieRuntime/CompositionInterpreter.cs | 1828 ----------------- .../Generated/lottie_comp_generated.h | 0 .../LottieRuntime/LottieRuntime.cpp | 64 +- .../LottieRuntime/LottieRuntime.h | 27 +- source/LottieRuntime/LottieRuntime.projitems | 18 - source/LottieRuntime/LottieRuntime.shproj | 13 - .../LottieRuntime/LottieRuntimeTool.cpp | 48 +- .../LottieRuntime/LottieRuntimeTool.vcxproj | 31 +- source/LottieRuntime/README.md | 174 +- .../CompDataFlatbuffer.Tests.csproj | 1 - .../CompositionApiStandIns.cs | 1112 ---------- .../CompDataFlatbuffer.Tests/CoverageTests.cs | 17 - .../InterpretedTreeDumper.cs | 805 -------- .../InterpreterTests.cs | 214 -- 29 files changed, 284 insertions(+), 4733 deletions(-) delete mode 100644 dlls/LottieRuntime/LottieRuntime.def delete mode 100644 dlls/LottieRuntime/LottieRuntime.manifest delete mode 100644 dlls/LottieRuntime/LottieRuntime.vcxproj delete mode 100644 dlls/LottieRuntime/LottieRuntimeCom.cpp delete mode 100644 dlls/LottieRuntime/LottieRuntimeCom.h delete mode 100644 dlls/LottieRuntime/README.md delete mode 100644 dlls/LottieRuntimeTool/README.md delete mode 100644 source/LottieRuntime/CompositionInterpreter.cs rename {dlls => source}/LottieRuntime/Generated/lottie_comp_generated.h (100%) rename {dlls => source}/LottieRuntime/LottieRuntime.cpp (97%) rename {dlls => source}/LottieRuntime/LottieRuntime.h (69%) delete mode 100644 source/LottieRuntime/LottieRuntime.projitems delete mode 100644 source/LottieRuntime/LottieRuntime.shproj rename dlls/LottieRuntimeTool/LottieRuntime.cpp => source/LottieRuntime/LottieRuntimeTool.cpp (84%) rename dlls/LottieRuntimeTool/LottieRuntime.vcxproj => source/LottieRuntime/LottieRuntimeTool.vcxproj (69%) delete mode 100644 tests/CompDataFlatbuffer.Tests/CompositionApiStandIns.cs delete mode 100644 tests/CompDataFlatbuffer.Tests/InterpretedTreeDumper.cs delete mode 100644 tests/CompDataFlatbuffer.Tests/InterpreterTests.cs diff --git a/Lottie-Windows.sln b/Lottie-Windows.sln index a1b9997f..e7b0b362 100644 --- a/Lottie-Windows.sln +++ b/Lottie-Windows.sln @@ -173,6 +173,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LottieSamples", "LottieSamp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lottie-Windows-UwpNET", "Lottie-Windows\Lottie-Windows-UwpNET\Lottie-Windows-UwpNET.csproj", "{FA1977D8-5C6F-DF3A-0926-C12A1A415024}" EndProject +Project("{BC8A1FFA-BEE3-4634-8014-F334798102B3}") = "LottieRuntimeTool", "source\LottieRuntime\LottieRuntimeTool.vcxproj", "{9A7B4C2E-1F3D-4B7A-9C3E-2D5A8F0B2E48}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution BETA|Any CPU = BETA|Any CPU @@ -192,6 +194,27 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9A7B4C2E-1F3D-4B7A-9C3E-2D5A8F0B2E48}.BETA|Any CPU.ActiveCfg = Release|x64 + {9A7B4C2E-1F3D-4B7A-9C3E-2D5A8F0B2E48}.BETA|ARM.ActiveCfg = Release|ARM64 + {9A7B4C2E-1F3D-4B7A-9C3E-2D5A8F0B2E48}.BETA|ARM64.ActiveCfg = Release|ARM64 + {9A7B4C2E-1F3D-4B7A-9C3E-2D5A8F0B2E48}.BETA|ARM64.Build.0 = Release|ARM64 + {9A7B4C2E-1F3D-4B7A-9C3E-2D5A8F0B2E48}.BETA|x64.ActiveCfg = Release|x64 + {9A7B4C2E-1F3D-4B7A-9C3E-2D5A8F0B2E48}.BETA|x64.Build.0 = Release|x64 + {9A7B4C2E-1F3D-4B7A-9C3E-2D5A8F0B2E48}.BETA|x86.ActiveCfg = Release|x64 + {9A7B4C2E-1F3D-4B7A-9C3E-2D5A8F0B2E48}.Debug|Any CPU.ActiveCfg = Debug|x64 + {9A7B4C2E-1F3D-4B7A-9C3E-2D5A8F0B2E48}.Debug|ARM.ActiveCfg = Debug|ARM64 + {9A7B4C2E-1F3D-4B7A-9C3E-2D5A8F0B2E48}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {9A7B4C2E-1F3D-4B7A-9C3E-2D5A8F0B2E48}.Debug|ARM64.Build.0 = Debug|ARM64 + {9A7B4C2E-1F3D-4B7A-9C3E-2D5A8F0B2E48}.Debug|x64.ActiveCfg = Debug|x64 + {9A7B4C2E-1F3D-4B7A-9C3E-2D5A8F0B2E48}.Debug|x64.Build.0 = Debug|x64 + {9A7B4C2E-1F3D-4B7A-9C3E-2D5A8F0B2E48}.Debug|x86.ActiveCfg = Debug|x64 + {9A7B4C2E-1F3D-4B7A-9C3E-2D5A8F0B2E48}.Release|Any CPU.ActiveCfg = Release|x64 + {9A7B4C2E-1F3D-4B7A-9C3E-2D5A8F0B2E48}.Release|ARM.ActiveCfg = Release|ARM64 + {9A7B4C2E-1F3D-4B7A-9C3E-2D5A8F0B2E48}.Release|ARM64.ActiveCfg = Release|ARM64 + {9A7B4C2E-1F3D-4B7A-9C3E-2D5A8F0B2E48}.Release|ARM64.Build.0 = Release|ARM64 + {9A7B4C2E-1F3D-4B7A-9C3E-2D5A8F0B2E48}.Release|x64.ActiveCfg = Release|x64 + {9A7B4C2E-1F3D-4B7A-9C3E-2D5A8F0B2E48}.Release|x64.Build.0 = Release|x64 + {9A7B4C2E-1F3D-4B7A-9C3E-2D5A8F0B2E48}.Release|x86.ActiveCfg = Release|x64 {5120EFD7-A556-46BF-8D56-F65F1EF9A305}.BETA|Any CPU.ActiveCfg = BETA|x86 {5120EFD7-A556-46BF-8D56-F65F1EF9A305}.BETA|ARM.ActiveCfg = BETA|ARM {5120EFD7-A556-46BF-8D56-F65F1EF9A305}.BETA|ARM.Build.0 = BETA|ARM @@ -685,6 +708,7 @@ Global {FC89273A-B2DA-4625-8A73-EF02A658D65E} = {AB232F35-AAF7-4AE2-B1D2-45DD9BC2F7D7} {7654A857-9A99-4185-9F8E-DD0CE662AF23} = {AB232F35-AAF7-4AE2-B1D2-45DD9BC2F7D7} {DC7F7A39-B7A7-40B4-937A-601460CAE38C} = {AB232F35-AAF7-4AE2-B1D2-45DD9BC2F7D7} + {9A7B4C2E-1F3D-4B7A-9C3E-2D5A8F0B2E48} = {AB232F35-AAF7-4AE2-B1D2-45DD9BC2F7D7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {51B9BB4C-5196-41CF-950C-12B04AD8A61C} diff --git a/LottieGen/DotnetTool/LottieGen.csproj b/LottieGen/DotnetTool/LottieGen.csproj index 79e61622..00f3dfc4 100644 --- a/LottieGen/DotnetTool/LottieGen.csproj +++ b/LottieGen/DotnetTool/LottieGen.csproj @@ -39,15 +39,10 @@ - - - - - - - - - + + + + diff --git a/README.md b/README.md index 15b643b5..1efd23a3 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Lottie-Windows consists of 3 related products: This repo also contains source code for **[samples](/LottieViewer)**. -It also contains **[LottieRuntime](/dlls/LottieRuntime)**, a native C++/WinRT interpreter that +It also contains **[LottieRuntime](/source/LottieRuntime)**, native C++/WinRT source that builds the composition tree at runtime from a serialized animation produced by `LottieGen -Language flatbuffer`. It keeps the animation as data, like the JSON path does, but without shipping a JSON parser or the translator. See diff --git a/build/RegenerateFlatbuffers.ps1 b/build/RegenerateFlatbuffers.ps1 index 1a2e0efc..0de938e3 100644 --- a/build/RegenerateFlatbuffers.ps1 +++ b/build/RegenerateFlatbuffers.ps1 @@ -25,7 +25,7 @@ $expectedCppVersion = "25.12.19" $repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..") $schema = Join-Path $repoRoot "source/LottieFlatbuffer/lottie_comp.fbs" $csharpOut = Join-Path $repoRoot "source/CompDataFlatbuffer/Generated" -$cppOut = Join-Path $repoRoot "dlls/LottieRuntime/Generated" +$cppOut = Join-Path $repoRoot "source/LottieRuntime/Generated" $actualCSharpVersion = (& $CSharpFlatc --version) -replace '^flatc version ', '' if ($actualCSharpVersion.Trim() -ne $expectedCSharpVersion) { diff --git a/dlls/LottieRuntime/LottieRuntime.def b/dlls/LottieRuntime/LottieRuntime.def deleted file mode 100644 index 7c435ed5..00000000 --- a/dlls/LottieRuntime/LottieRuntime.def +++ /dev/null @@ -1,6 +0,0 @@ -; Classic COM entry points must have undecorated names for CoCreateInstance's -; loader (and for the test's GetProcAddress-based activation) to find them. -LIBRARY LottieRuntime -EXPORTS - DllGetClassObject PRIVATE - DllCanUnloadNow PRIVATE diff --git a/dlls/LottieRuntime/LottieRuntime.manifest b/dlls/LottieRuntime/LottieRuntime.manifest deleted file mode 100644 index d93a09f5..00000000 --- a/dlls/LottieRuntime/LottieRuntime.manifest +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - diff --git a/dlls/LottieRuntime/LottieRuntime.vcxproj b/dlls/LottieRuntime/LottieRuntime.vcxproj deleted file mode 100644 index 5cad979f..00000000 --- a/dlls/LottieRuntime/LottieRuntime.vcxproj +++ /dev/null @@ -1,99 +0,0 @@ - - - - - Debug - x64 - - - Release - x64 - - - Debug - ARM64 - - - Release - ARM64 - - - - - {6E0A5B4C-2F1D-4B7A-9C3E-1D5A8F0B2E47} - LottieRuntime - DynamicLibrary - v143 - Unicode - 10.0.22621.0 - - - 10.0.17763.0 - $(MSBuildThisFileDirectory)..\..\external\flatbuffers\include - $(MSBuildThisFileDirectory)flatbuffers\include - - - - - - - true - false - true - - - - - stdcpp20 - WIN32_LEAN_AND_MEAN;NOMINMAX;%(PreprocessorDefinitions) - $(MSBuildThisFileDirectory);$(FlatBuffersIncludeDirectory);%(AdditionalIncludeDirectories) - Level4 - true - - - Sync - - - MinSpace - true - true - - - d2d1.lib;dxguid.lib;shcore.lib;shlwapi.lib;windowsapp.lib;%(AdditionalDependencies) - LottieRuntime.def - - - true - true - - - - - - - - - - - - - - - - - - - - - - false - - - - - diff --git a/dlls/LottieRuntime/LottieRuntimeCom.cpp b/dlls/LottieRuntime/LottieRuntimeCom.cpp deleted file mode 100644 index b82fef6c..00000000 --- a/dlls/LottieRuntime/LottieRuntimeCom.cpp +++ /dev/null @@ -1,138 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -// The classic COM entry point declared in LottieRuntimeCom.h. This is the only -// translation unit that needs to know both LoadComposition and classic COM, so it -// stays out of LottieRuntime.cpp. - -#include - -#include "LottieRuntimeCom.h" -#include "LottieRuntime.h" - -#include -#include -#include - -using namespace winrt::Windows::UI::Composition; - - -namespace -{ - // The caller owns the thread's DispatcherQueue. It must outlive every Visual - // returned by this loader. - struct LottieCompositionLoader : winrt::implements - { - STDMETHODIMP LoadComposition(UINT32 length, BYTE const* buffer, REFIID riid, void** result) noexcept override - { - if (result == nullptr) - { - return E_POINTER; - } - - *result = nullptr; - - if (buffer == nullptr && length != 0) - { - return E_INVALIDARG; - } - - try - { - if (!m_compositor) - { - if (!winrt::Windows::System::DispatcherQueue::GetForCurrentThread()) - { - return E_ILLEGAL_METHOD_CALL; - } - - m_compositor = Compositor(); - } - - auto const span = std::span(reinterpret_cast(buffer), length); - auto const visual = CommunityToolkit::WinUI::Lottie::LoadComposition(m_compositor, span); - return visual.as<::IUnknown>()->QueryInterface(riid, result); - } - catch (...) - { - return winrt::to_hresult(); - } - } - - private: - Compositor m_compositor{ nullptr }; - }; - - // A minimal class factory: this DLL only ever creates LottieCompositionLoader, - // so there is no per-CLSID dispatch to do. - struct ClassFactory : winrt::implements - { - STDMETHODIMP CreateInstance(::IUnknown* outer, REFIID riid, void** result) noexcept override - { - if (result == nullptr) - { - return E_POINTER; - } - - *result = nullptr; - - if (outer != nullptr) - { - return CLASS_E_NOAGGREGATION; - } - - try - { - return winrt::make()->QueryInterface(riid, result); - } - catch (...) - { - return winrt::to_hresult(); - } - } - - STDMETHODIMP LockServer(BOOL lock) noexcept override - { - if (lock) - { - ++winrt::get_module_lock(); - } - else - { - --winrt::get_module_lock(); - } - - return S_OK; - } - }; -} - -extern "C" HRESULT __stdcall DllGetClassObject(REFCLSID rclsid, REFIID riid, void** result) -{ - if (result == nullptr) - { - return E_POINTER; - } - - *result = nullptr; - - if (rclsid != CLSID_LottieCompositionLoader) - { - return CLASS_E_CLASSNOTAVAILABLE; - } - - try - { - return winrt::make()->QueryInterface(riid, result); - } - catch (...) - { - return winrt::to_hresult(); - } -} - -extern "C" HRESULT __stdcall DllCanUnloadNow() -{ - return winrt::get_module_lock() == 0 ? S_OK : S_FALSE; -} diff --git a/dlls/LottieRuntime/LottieRuntimeCom.h b/dlls/LottieRuntime/LottieRuntimeCom.h deleted file mode 100644 index d4997a9d..00000000 --- a/dlls/LottieRuntime/LottieRuntimeCom.h +++ /dev/null @@ -1,25 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#pragma once - -#include -#include - -// COM interface for DLL activation. LottieRuntime.manifest declares the class for -// registration-free COM activation. -// -// riid selects the interface returned for the root Visual. -MIDL_INTERFACE("3874B71B-05E6-42A9-B838-1AE7F0BA4E1C") -ILottieCompositionLoader : public IUnknown -{ -public: - // buffer contains LottieGen -Language flatbuffer output. Validation precedes - // every read; malformed data returns E_INVALIDARG. - STDMETHOD(LoadComposition)(UINT32 length, BYTE const* buffer, REFIID riid, void** result) = 0; -}; - -// Registration-free clients do not link an import library. -constexpr CLSID CLSID_LottieCompositionLoader = { - 0x9f7b7089, 0xf512, 0x4bbc, { 0x81, 0xfe, 0x4f, 0xf1, 0x2b, 0x4d, 0xa1, 0x59 } }; diff --git a/dlls/LottieRuntime/README.md b/dlls/LottieRuntime/README.md deleted file mode 100644 index ac9a5d20..00000000 --- a/dlls/LottieRuntime/README.md +++ /dev/null @@ -1,180 +0,0 @@ -# LottieRuntime - -A native runtime interpreter for Lottie animations. - -`LottieRuntime` builds a `Windows.UI.Composition` visual tree from a serialized -composition. The DLL consumes FlatBuffer data through a classic COM interface. -Applications can also link the implementation directly. - -## How it fits in - -Lottie-Windows already turns a Lottie file into a `WinCompData` graph, which is a -description of a composition tree. Three consumers use that graph: - -| Consumer | When it runs | Cost | -| --- | --- | --- | -| `Instantiator` | Runtime, in the app | Ships the whole JSON reader and translator | -| `LottieGen` code generators | Build time | No runtime cost, but one class per animation | -| `LottieRuntime` | Runtime, in the app | One interpreter shared by every animation | - -`CompositionSerializer` writes the `WinCompData` graph into a -FlatBuffer at build time, and `LottieRuntime` reads that buffer at runtime. The -animation stays data, so it can be swapped, downloaded or themed without a rebuild, -and the code that reads it is the same size no matter how many animations an -application has. - -``` -Lottie JSON --> LottieCompositionReader --> LottieData - | - v - LottieToWinComp - | - v - WinCompData graph - / | \ - / | \ - Instantiator UIDataCodeGen CompositionSerializer - (runtime C#) (C#/C++/CX) | - v - .lcomp buffer - | - v - LottieRuntime - (runtime C++) -``` - -Because the FlatBuffer is written from the same graph the other two consume, all -three produce the same visual tree. The round trip tests in -`tests/CompDataFlatbuffer.Tests` assert exactly that. - -## Producing a buffer - -``` -LottieGen -InputFile animation.json -Language flatbuffer -OutputFolder . -``` - -This writes `Animation.lcomp`. The format is described by -[`source/LottieFlatbuffer/lottie_comp.fbs`](../../source/LottieFlatbuffer/lottie_comp.fbs). - -## Using the DLL - -```cpp -#include - -auto bytes = ReadFileBytes(L"Animation.lcomp"); -winrt::com_ptr loader; -winrt::check_hresult(CoCreateInstance( - CLSID_LottieCompositionLoader, - nullptr, - CLSCTX_INPROC_SERVER, - __uuidof(ILottieCompositionLoader), - loader.put_void())); - -winrt::Windows::UI::Composition::Visual root{ nullptr }; -winrt::check_hresult(loader->LoadComposition( - static_cast(bytes.size()), - reinterpret_cast(bytes.data()), - winrt::guid_of(), - winrt::put_abi(root))); -``` - -`LottieRuntime.manifest` declares the private COM assembly. Activate that manifest -before `CoCreateInstance`; `LottieRuntime.exe` contains the complete activation -sequence. No system registration is required. The calling thread must own a -`DispatcherQueue` that outlives the returned visual tree. - -`LottieRuntime.h` is the C++ API for applications linking the implementation -directly. It is not exported by `LottieRuntime.dll`. - -## Design - -**Least derived interfaces.** A node's concrete type is known only inside the -function that creates it. The realization caches hold `Visual`, -`CompositionShape`, `CompositionBrush`, `CompositionGeometry` and so on, and every -call the interpreter makes afterwards is declared on those types. There is no point -at which the interpreter asks what a node actually is, because the buffer already -said, and the answer was consumed at the moment of creation. The one exception is -`ObjectCategory::Surface` in an object reference, where the buffer permits an image -surface that is not a `CompositionObject`; that is a validation check, not a -downcast in the normal path. - -**Size first.** There is one function per node *category*, not per node type, and -the state shared by every node is applied by a single function that takes -`CompositionObject`. Everything is in one translation unit, so `/OPT:REF` discards -the code for whatever an application never reaches. - -**Runtime cost second.** Each node and string is realized at most once. Lookups use -O(1) bounds-checked array indexing into the FlatBuffer's node vectors. The buffer -is read in place; no intermediate object model is built. - -**Transient memory third.** Realization caches are dense vectors matching the -FlatBuffer's node vectors. D2D geometries are cached while the graph is built so -shared geometry remains shared and cycles are rejected. - -**Untrusted input.** A buffer may have come from a file or a download. It is checked -for the `LCMP` identifier, run through the FlatBuffers verifier, and every index -read out of it is range checked. A malformed buffer throws -`winrt::hresult_invalid_argument`; it never reads outside the buffer. The declared -Universal API contract is checked before graph hydration. - -## Dependencies - -Direct2D, for path geometry, and the composition effect interop interface, for the -two effects the translator emits. There is no dependency on Win2D: the interpreter -implements `IGeometrySource2DInterop` and `IGraphicsEffectD2D1Interop` itself, which -is a few dozen lines and avoids pulling a large component into every application. - -The FlatBuffers C++ headers come from the pinned `external/flatbuffers` submodule. -The checked-in C++ binding is generated by `flatc` from that commit. The checked-in -C# binding remains on `Google.FlatBuffers` 25.2.10 from Microsoft's configured -NuGet source. `build/RegenerateFlatbuffers.ps1` validates both generator versions. - -## Incorporate it in a build - -`LottieGen.nupkg` contains the complete native source set under -`LottieRuntime/`. - -### C++/WinRT - -Add `LottieRuntime.cpp` to the consuming native project. Add `LottieRuntime/` -and `LottieRuntime/flatbuffers/include/` to its include paths. Build as C++20 -and link: - -```text -d2d1.lib -dxguid.lib -shcore.lib -shlwapi.lib -windowsapp.lib -``` - -Include `LottieRuntime.h` and call `LoadComposition` with the application's -`Compositor`. This direct-link path does not use COM activation. - -### Managed applications - -A managed application can also use the C# interpreter in -[`source/LottieRuntime`](../../source/LottieRuntime/README.md), which reads the same -buffer and performs the same validation, and needs no COM activation. Use the DLL -instead when the application should not take a Win2D dependency, or when the same -animation is loaded from both native and managed code. - -Add the packaged `LottieRuntime.vcxproj` to the solution. The project builds -`LottieRuntime.dll`, including the `ILottieCompositionLoader` implementation. -Copy both files into every managed application output directory: - -```text -LottieRuntime.dll -LottieRuntime.manifest -``` - -The managed application activates `LottieRuntime.manifest`, calls -`CoCreateInstance(CLSID_LottieCompositionLoader)`, and invokes -`ILottieCompositionLoader::LoadComposition`. The calling thread owns the -`DispatcherQueue` for the lifetime of the returned visual tree. - -## Status - -Debug and Release x64 builds are supported. `LottieRuntime.exe` validates, dumps, -or displays a composition through registration-free activation. -`tests/CompDataFlatbuffer.Tests` verifies serializer round trips. diff --git a/dlls/LottieRuntimeTool/README.md b/dlls/LottieRuntimeTool/README.md deleted file mode 100644 index 289bbf05..00000000 --- a/dlls/LottieRuntimeTool/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# LottieRuntime tool - -`LottieRuntime.exe` loads FlatBuffer compositions through the registration-free -`ILottieCompositionLoader` COM interface. - -## Build - -```powershell -& "C:\Program Files\Microsoft Visual Studio\18\Professional\Common7\Tools\Launch-VsDevShell.ps1" -SkipAutomaticLocation -msbuild .\dlls\LottieRuntimeTool\LottieRuntime.vcxproj /p:Platform=x64 /p:Configuration=Release -``` - -The executable is written to: - -```text -dlls\LottieRuntimeTool\x64\Release\LottieRuntime.exe -``` - -## Convert Lottie JSON - -```powershell -$output = Join-Path $env:TEMP "lottie-runtime" -New-Item -ItemType Directory -Force $output | Out-Null - -dotnet run --project .\LottieGen\DotnetTool\LottieGen.csproj -- ` - -InputFile .\LottieSamples\Assets\LottieLogo1.json ` - -Language flatbuffer ` - -OutputFolder $output -``` - -The command writes `$output\LottieLogo1.lcomp`. - -## Load, inspect, or display - -Validate the composition: - -```powershell -.\dlls\LottieRuntimeTool\x64\Release\LottieRuntime.exe ` - "$output\LottieLogo1.lcomp" -``` - -Print the interpreted Composition object hierarchy: - -```powershell -.\dlls\LottieRuntimeTool\x64\Release\LottieRuntime.exe ` - "$output\LottieLogo1.lcomp" --dump -``` - -Display and animate the interpreted visual: - -```powershell -.\dlls\LottieRuntimeTool\x64\Release\LottieRuntime.exe ` - "$output\LottieLogo1.lcomp" --show -``` diff --git a/dlls/README.md b/dlls/README.md index 6e09e866..e795c3ae 100644 --- a/dlls/README.md +++ b/dlls/README.md @@ -25,11 +25,3 @@ But we don't actually use the DLLs in any of the other projects; instead, we mus ### Summary * Shared projects give us the equivalent of C++ static libs. * The dlls directory enforces modularity. - -## LottieRuntime - -`LottieRuntime` is the exception to everything above. It is a real C++ DLL that an -application is meant to consume, rather than a modularity check, and it is not part -of `Lottie-Windows.sln` because it needs the C++ toolset and the FlatBuffers headers. -It reads the serialized form of a `WinCompData` graph and builds the composition -tree at runtime. See [LottieRuntime/README.md](LottieRuntime/README.md). diff --git a/source/LottieFlatbuffer/README.md b/source/LottieFlatbuffer/README.md index d5547614..c07d70e3 100644 --- a/source/LottieFlatbuffer/README.md +++ b/source/LottieFlatbuffer/README.md @@ -70,6 +70,11 @@ package. Both are currently 25.2.10. | --- | --- | | `source/CompDataFlatbuffer/CompositionSerializer.cs` | Writes a graph | | `source/CompDataFlatbuffer/CompositionDeserializer.cs` | Reads one back, in C# | -| `dlls/LottieRuntime` | Reads one back, in C++/WinRT | +| `source/LottieRuntime` | Reads one back using statically compiled WinUI 2 or WinUI 3 C++/WinRT source | | `LottieGen -Language flatbuffer` | Produces a `.lcomp` file | | `tests/CompDataFlatbuffer.Tests` | Asserts a graph survives the round trip unchanged | + +When the requested UAP range needs different translated graphs, LottieGen writes +one `.lcomp` file per range. The file names contain the supported range, such as +`.uap12-plus.lcomp` or `.uap7-11.lcomp`; the consumer selects the appropriate +variant before calling the native reader. diff --git a/source/LottieGen/LottieJsonFileProcessor.cs b/source/LottieGen/LottieJsonFileProcessor.cs index e778c86c..d21b5726 100644 --- a/source/LottieGen/LottieJsonFileProcessor.cs +++ b/source/LottieGen/LottieJsonFileProcessor.cs @@ -438,31 +438,68 @@ bool TryGenerateFlatbuffer(LottieComposition lottieComposition, string outputFil return false; } - // NOTE: this only writes the latest version of a multi-version translation. - var translationResult = _translationResults[0]; - var sourceMetadata = new SourceMetadata(translationResult.SourceMetadata); - var result = TryWriteBinaryFile( - outputFilePath, - CompositionSerializer.Serialize( - translationResult.RootVisual!, - checked((ushort)translationResult.MinimumRequiredUapVersion), - sourceMetadata.LottieMetadata, - sourceMetadata.PropertyBindings, - (float)lottieComposition.Width, - (float)lottieComposition.Height)); + var result = true; + var maximumUapVersion = _options.TargetUapVersion ?? uint.MaxValue; + + for (var i = 0; i < _translationResults.Count; i++) + { + var translationResult = _translationResults[i]; + var sourceMetadata = new SourceMetadata(translationResult.SourceMetadata); + var minimumUapVersion = translationResult.MinimumRequiredUapVersion; + var maximumVariantUapVersion = i == 0 + ? maximumUapVersion + : _translationResults[i - 1].MinimumRequiredUapVersion - 1; + var variantPath = _translationResults.Count == 1 + ? outputFilePath + : GetFlatbufferVariantPath( + outputFilePath, + minimumUapVersion, + maximumVariantUapVersion); + + if (!TryWriteBinaryFile( + variantPath, + CompositionSerializer.Serialize( + translationResult.RootVisual!, + checked((ushort)minimumUapVersion), + sourceMetadata.LottieMetadata, + sourceMetadata.PropertyBindings, + (float)lottieComposition.Width, + (float)lottieComposition.Height))) + { + result = false; + continue; + } - if (result) - { using (_reporter.InfoStream.Lock()) { - _reporter.WriteInfo($"Flatbuffer data for class {_className} written to:"); - _reporter.WriteInfo(InfoType.FilePath, $" {outputFilePath}"); + _reporter.WriteInfo( + $"Flatbuffer data for class {_className}, UAP versions " + + (maximumVariantUapVersion == uint.MaxValue + ? $"{minimumUapVersion} and later" + : $"{minimumUapVersion} through {maximumVariantUapVersion}") + + ", written to:"); + _reporter.WriteInfo(InfoType.FilePath, $" {variantPath}"); } } return result; } + static string GetFlatbufferVariantPath( + string outputFilePath, + uint minimumUapVersion, + uint maximumUapVersion) + { + var directory = System.IO.Path.GetDirectoryName(outputFilePath); + var fileName = System.IO.Path.GetFileNameWithoutExtension(outputFilePath); + var extension = System.IO.Path.GetExtension(outputFilePath); + var range = maximumUapVersion == uint.MaxValue + ? $"uap{minimumUapVersion}-plus" + : $"uap{minimumUapVersion}-{maximumUapVersion}"; + + return System.IO.Path.Combine(directory ?? string.Empty, $"{fileName}.{range}{extension}"); + } + bool TryGenerateWincompDgml( LottieComposition lottieComposition, string outputFilePath) @@ -861,7 +898,15 @@ bool TryEnsureTranslated(LottieComposition lottieComposition) var translationResult = LottieToMultiVersionWinCompTranslator.TryTranslateLottieComposition( lottieComposition: lottieComposition, configuration: configuration, - minimumUapVersion: _options.WinUIVersion.Major >= 3 ? uint.MaxValue : _minimumUapVersion); + minimumUapVersion: + _options.WinUIVersion.Major < 3 || + !_options.Languages.Any( + language => + language == Language.CSharp || + language == Language.Cx || + language == Language.Cppwinrt) + ? _minimumUapVersion + : uint.MaxValue); _translationResults = translationResult.TranslationResults; _translationIssues = translationResult.Issues; diff --git a/source/LottieGenExe/Main.cs b/source/LottieGenExe/Main.cs index 4fe843e2..7b2cfe5e 100644 --- a/source/LottieGenExe/Main.cs +++ b/source/LottieGenExe/Main.cs @@ -75,10 +75,18 @@ RunResult Run() _reporter.ErrorStream.WriteLine(_options.ErrorDescription); return RunResult.InvalidUsage; } - else if (_options.WinUIVersion.Major >= 3 && (_options.MinimumUapVersion.HasValue || _options.TargetUapVersion.HasValue)) + else if ( + _options.WinUIVersion.Major >= 3 && + _options.Languages.Any( + language => + language == Language.CSharp || + language == Language.Cx || + language == Language.Cppwinrt) && + (_options.MinimumUapVersion.HasValue || _options.TargetUapVersion.HasValue)) { - // WinUI3 does not permit setting a minimum or target version because WinUI3 implies - // the latest version only. + // WinUI3 source code implies the latest version only. FlatBuffer output is + // projection-neutral, so it can still request a UAP range for consumers + // that select the matching data variant. _reporter.WriteError($"WinUI versions of 3 and above cannot be used with {nameof(_options.MinimumUapVersion)} or {nameof(_options.TargetUapVersion)}."); return RunResult.InvalidUsage; } diff --git a/source/LottieRuntime/CompositionInterpreter.cs b/source/LottieRuntime/CompositionInterpreter.cs deleted file mode 100644 index b7071b40..00000000 --- a/source/LottieRuntime/CompositionInterpreter.cs +++ /dev/null @@ -1,1828 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable enable - -using System; -using System.Collections.Generic; -using System.IO; -using System.Numerics; -using System.Runtime.InteropServices.WindowsRuntime; -using CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer; -using Google.FlatBuffers; -using Microsoft.Graphics.Canvas.Geometry; -using Windows.Foundation.Metadata; -using Windows.Graphics.Effects; -using Fb = CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema; -using Mgc = Microsoft.Graphics.Canvas; -using Mgce = Microsoft.Graphics.Canvas.Effects; -#if WINAPPSDK -using Wc = Microsoft.UI.Composition; -using Wm = Microsoft.UI.Xaml.Media; -#else -using Wc = Windows.UI.Composition; -using Wm = Windows.UI.Xaml.Media; -#endif -using Wui = Windows.UI; - -namespace CommunityToolkit.WinUI.Lottie.LottieRuntime -{ - /// - /// Builds a tree directly from a serialized composition. - /// - /// - /// This is the managed counterpart of the native interpreter in - /// dlls/LottieRuntime. Both read the same FlatBuffer and build the same - /// visual tree, and both are structured the same way, so that a change to the - /// format only ever has to be made twice rather than in a different shape each - /// time. - /// - /// It differs from Instantiator in that it never builds a WinCompData - /// graph: the buffer is read in place and the composition objects are created as - /// the walk reaches them. That makes an animation data rather than code, so it can - /// be downloaded, themed or swapped without a rebuild, and the amount of code in - /// the application does not grow with the number of animations. - /// - /// A buffer is untrusted input. It is checked for the LCMP identifier, run - /// through the FlatBuffers verifier, and every index read out of it is range - /// checked. A malformed buffer is reported as a - /// ; a buffer that needs something this - /// build cannot provide is reported as a . - /// Neither reads outside the buffer. - /// -#if PUBLIC_LottieRuntime - public -#endif - sealed class CompositionInterpreter - { - // The smallest buffer that could possibly be a composition: a root offset - // followed by the four byte file identifier. Checked before the identifier is - // read so that a short buffer is reported as a format error. - const int MinimumBufferLength = 8; - - readonly Wc.Compositor _compositor; - readonly Fb.LottieComposition _root; - readonly string[] _strings; - - // Realization caches. These are indexed identically to the node vectors in the - // buffer, so looking up an already realized node is an array index rather than - // a hash lookup, and a node that is reached by more than one path is realized - // exactly once. Each holds the least derived type that a reference to that - // category needs, which is what keeps the interpreter free of downcasts. - readonly Wc.Visual?[] _visuals; - readonly Wc.CompositionShape?[] _shapes; - readonly Wc.CompositionGeometry?[] _geometries; - readonly CanvasGeometry?[] _canvasGeometries; - readonly CanvasGeometryState[] _canvasGeometryStates; - readonly Wc.CompositionBrush?[] _brushes; - readonly Wc.CompositionColorGradientStop?[] _gradientStops; - readonly Wc.CompositionViewBox?[] _viewBoxes; - readonly Wc.CompositionClip?[] _clips; - readonly Wc.CompositionShadow?[] _shadows; - readonly Wc.ICompositionSurface?[] _surfaces; - readonly IGraphicsEffect?[] _effects; - readonly Wc.CompositionEasingFunction?[] _easings; - readonly Wc.CompositionAnimation?[] _animations; - readonly Wc.CompositionPropertySet?[] _propertySets; - readonly Wc.AnimationController?[] _controllers; - - CompositionInterpreter(Wc.Compositor compositor, Fb.LottieComposition root) - { - _compositor = compositor; - _root = root; - - _strings = new string[root.StringsLength]; - for (var i = 0; i < _strings.Length; i++) - { - _strings[i] = root.Strings(i) ?? string.Empty; - } - - _visuals = new Wc.Visual?[root.VisualsLength]; - _shapes = new Wc.CompositionShape?[root.ShapesLength]; - _geometries = new Wc.CompositionGeometry?[root.GeometriesLength]; - _canvasGeometries = new CanvasGeometry?[root.CanvasGeometriesLength]; - _canvasGeometryStates = new CanvasGeometryState[root.CanvasGeometriesLength]; - _brushes = new Wc.CompositionBrush?[root.BrushesLength]; - _gradientStops = new Wc.CompositionColorGradientStop?[root.GradientStopsLength]; - _viewBoxes = new Wc.CompositionViewBox?[root.ViewBoxesLength]; - _clips = new Wc.CompositionClip?[root.ClipsLength]; - _shadows = new Wc.CompositionShadow?[root.ShadowsLength]; - _surfaces = new Wc.ICompositionSurface?[root.SurfacesLength]; - _effects = new IGraphicsEffect?[root.EffectsLength]; - _easings = new Wc.CompositionEasingFunction?[root.EasingsLength]; - _animations = new Wc.CompositionAnimation?[root.AnimationsLength]; - _propertySets = new Wc.CompositionPropertySet?[root.PropertySetsLength]; - _controllers = new Wc.AnimationController?[root.ControllersLength]; - } - - // A canvas geometry is built from other canvas geometries, so the walk has to - // be able to tell a shared geometry from a cycle. - enum CanvasGeometryState - { - Unrealized, - Realizing, - Realized, - } - - /// - /// Builds the visual tree described by a serialized composition. - /// - /// The compositor that creates the objects. - /// A buffer produced by CompositionSerializer. - /// The root of the visual tree. - /// The buffer is not a well formed composition. - /// The composition needs a newer schema or a newer version of Windows. - public static Wc.Visual LoadComposition(Wc.Compositor compositor, byte[] bytes) - { - if (bytes is null) - { - throw new ArgumentNullException(nameof(bytes)); - } - - return LoadComposition(compositor, new ByteBuffer(bytes), bytes.Length); - } - - /// - /// Builds the visual tree described by a serialized composition. - /// - /// The compositor that creates the objects. - /// A buffer produced by CompositionSerializer. - /// The root of the visual tree. - /// The buffer is not a well formed composition. - /// The composition needs a newer schema or a newer version of Windows. - public static Wc.Visual LoadComposition(Wc.Compositor compositor, ReadOnlySpan bytes) - => LoadComposition(compositor, new ByteBuffer(bytes.ToArray()), bytes.Length); - - static Wc.Visual LoadComposition(Wc.Compositor compositor, ByteBuffer buffer, int length) - { - if (compositor is null) - { - throw new ArgumentNullException(nameof(compositor)); - } - - // The buffer is untrusted, so nothing is read out of it until the verifier - // has agreed that every offset in it is inside it. - if (length < MinimumBufferLength || - !Fb.LottieComposition.LottieCompositionBufferHasIdentifier(buffer)) - { - throw new FlatBufferFormatException("The buffer is not a composition."); - } - - Fb.LottieComposition root; - - // Verification proves that the buffer is structurally sound, but it cannot - // prove that the indices stored in it point at the right things. Neither - // the verifier nor the reader reports a malformed buffer purely by - // returning false; both also throw. Everything that reads the buffer is - // therefore done inside a handler, so that a corrupt buffer always surfaces - // as a format error and never as an exception that a caller could not have - // anticipated. - try - { - if (!Fb.LottieComposition.VerifyLottieComposition(buffer)) - { - throw new FlatBufferFormatException("The buffer is not a valid composition."); - } - - root = Fb.LottieComposition.GetRootAsLottieComposition(buffer); - } - catch (Exception e) when (IsBufferFailure(e)) - { - throw new FlatBufferFormatException("The buffer is malformed.", e); - } - - // A newer schema may store things in fields this build does not read, so a - // buffer that declares one is refused rather than silently misinterpreted. - if (root.SchemaVersion > Format.Version) - { - throw new NotSupportedException( - $"Schema version {root.SchemaVersion} is newer than the supported version {Format.Version}."); - } - - // The translator records the API contract that the graph needs. Checking it - // before anything is created means that an animation which cannot run on - // this version of Windows is refused rather than half built. - var requiredUapVersion = root.RequiredUapVersion; - if (requiredUapVersion != 0 && - !ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", requiredUapVersion)) - { - throw new NotSupportedException( - $"The composition requires UniversalApiContract {requiredUapVersion}."); - } - - try - { - var interpreter = new CompositionInterpreter(compositor, root); - - return interpreter.GetVisual(root.RootVisual) - ?? throw new FlatBufferFormatException("The composition has no root visual."); - } - catch (Exception e) when (IsBufferFailure(e)) - { - throw new FlatBufferFormatException("The buffer is malformed.", e); - } - } - - /// - /// Returns the property set that drives an interpreted composition. - /// - /// A root returned by LoadComposition. - /// The property set that holds the animation's Progress property. - public static Wc.CompositionPropertySet ProgressPropertySet(Wc.Visual root) - { - if (root is null) - { - throw new ArgumentNullException(nameof(root)); - } - - return root.Properties; - } - - // The failures that reading a structurally valid but semantically wrong buffer - // can produce. Reading past the end of the buffer is not a safety problem - // because ByteBuffer is bounds checked; the only thing being corrected here is - // the type of the exception. - static bool IsBufferFailure(Exception e) - => e is ArgumentException or IndexOutOfRangeException or - InvalidOperationException or OverflowException or - FormatException or NullReferenceException; - - // --------------------------------------------------------------------- - // Table accessors. Every index is bounds checked, because a buffer that - // passes verification can still contain an out of range index. - // --------------------------------------------------------------------- - static T Require(T? value, string what) - where T : struct - => value ?? throw new FlatBufferFormatException($"A {what} is missing."); - - static uint RequireIndex(uint index, string what) - => index != Format.NullIndex - ? index - : throw new FlatBufferFormatException($"A required {what} is missing."); - - string? GetString(uint index) - => index == Format.NullIndex - ? null - : index < (uint)_strings.Length - ? _strings[index] - : throw new FlatBufferFormatException($"String index {index} is out of range."); - - string GetRequiredString(uint index) - => GetString(index) ?? throw new FlatBufferFormatException("A required string is missing."); - - // --------------------------------------------------------------------- - // Shared state. - // --------------------------------------------------------------------- - // Applies the state that every CompositionObject has. Called after the object - // has been placed in its realization cache, so that a cycle terminates. - void InitializeCompositionObject(Wc.CompositionObject target, Fb.CompObj? source) - { - if (source is null) - { - return; - } - - var value = source.Value; - - if (GetString(value.Comment) is string comment) - { - target.Comment = comment; - } - - // The property set is realized through the object that owns it, so that the - // owner and its property set can never both create it. - if (value.Properties != Format.NullIndex) - { - RealizePropertySet(value.Properties, target.Properties); - } - - // Animations are started last, because setting a property stops any - // animation that is running on it. - for (var i = 0; i < value.AnimatorsLength; i++) - { - var animator = Require(value.Animators(i), "animator"); - var property = GetRequiredString(animator.Property); - var animation = GetAnimation(animator.Animation) - ?? throw new FlatBufferFormatException($"Animator '{property}' has no animation."); - - // A non custom controller comes into existence when the animation is - // started, so only a custom one is passed in. Either way the resulting - // controller is cached under the index that the buffer gave it. - var controllerIndex = animator.Controller; - var customController = controllerIndex != Format.NullIndex && IsCustomController(controllerIndex) - ? GetController(controllerIndex) - : null; - - // An expression animation is driven by its inputs and so cannot have a - // controller. This is checked here in order to reject the buffer rather - // than build an unusable tree. - if (customController is not null && animation is Wc.ExpressionAnimation) - { - throw new FlatBufferFormatException( - $"Animator '{property}' gives a controller to an expression animation."); - } - - if (customController is null) - { - target.StartAnimation(property, animation); - } - else - { - target.StartAnimation(property, animation, customController); - } - - if (controllerIndex != Format.NullIndex && customController is null) - { - RealizeController( - controllerIndex, - target.TryGetAnimationController(property) - ?? throw new FlatBufferFormatException($"Animator '{property}' has no controller.")); - } - } - } - - bool IsCustomController(uint index) - => GetControllerTable(index).IsCustom; - - Fb.Controller GetControllerTable(uint index) - => index < (uint)_controllers.Length - ? Require(_root.Controllers((int)index), "controller") - : throw new FlatBufferFormatException($"Controller index {index} is out of range."); - - void RealizeController(uint index, Wc.AnimationController controller) - { - if (_controllers[index] is not null) - { - return; - } - - _controllers[index] = controller; - - var table = GetControllerTable(index); - - if (table.IsPaused) - { - controller.Pause(); - } - - InitializeCompositionObject(controller, table.Base); - } - - Wc.AnimationController? GetController(uint index) - { - if (index == Format.NullIndex) - { - return null; - } - - var table = GetControllerTable(index); - - if (_controllers[index] is null) - { - if (table.IsCustom) - { - RealizeController(index, _compositor.CreateAnimationController()); - } - else - { - // A non custom controller comes into existence when its target - // object starts the animation, so realizing the target realizes the - // controller. - GetObjectReference(table.TargetObject); - } - } - - return _controllers[index] - ?? throw new FlatBufferFormatException($"Controller {index} could not be realized."); - } - - // --------------------------------------------------------------------- - // Property sets. - // --------------------------------------------------------------------- - void RealizePropertySet(uint index, Wc.CompositionPropertySet propertySet) - { - if (index >= (uint)_propertySets.Length) - { - throw new FlatBufferFormatException($"Property set index {index} is out of range."); - } - - if (_propertySets[index] is not null) - { - return; - } - - // Cached before the values are applied, because a value may be an - // expression that refers back to this property set. - _propertySets[index] = propertySet; - - var table = Require(_root.PropertySets((int)index), "property set"); - - for (var i = 0; i < table.ValuesLength; i++) - { - var value = Require(table.Values(i), "property value"); - var name = GetRequiredString(value.Name); - - switch (value.Type) - { - case Fb.PropertyValueType.Color: - propertySet.InsertColor(name, ToColor(Require(value.Color, "color"))); - break; - case Fb.PropertyValueType.Scalar: - propertySet.InsertScalar(name, value.Scalar); - break; - case Fb.PropertyValueType.Vector2: - propertySet.InsertVector2(name, ToVector2(value.Vector)); - break; - case Fb.PropertyValueType.Vector3: - propertySet.InsertVector3(name, ToVector3(value.Vector)); - break; - case Fb.PropertyValueType.Vector4: - propertySet.InsertVector4(name, ToVector4(value.Vector)); - break; - case Fb.PropertyValueType.None: - break; - default: - throw new FlatBufferFormatException($"Unsupported property value type {value.Type}."); - } - } - - InitializeCompositionObject(propertySet, table.Base); - } - - Wc.CompositionPropertySet? GetPropertySet(uint index) - { - if (index == Format.NullIndex) - { - return null; - } - - if (index >= (uint)_propertySets.Length) - { - throw new FlatBufferFormatException($"Property set index {index} is out of range."); - } - - if (_propertySets[index] is null) - { - var table = Require(_root.PropertySets((int)index), "property set"); - - if (table.Owner == Format.NullIndex) - { - // A property set with no owner is a standalone one, which is how the - // translator exposes theming properties. - RealizePropertySet(index, _compositor.CreatePropertySet()); - } - else - { - // Realizing the owner realizes its property set as a side effect. - GetObjectReference(table.Owner); - } - } - - return _propertySets[index] - ?? throw new FlatBufferFormatException($"Property set {index} could not be realized."); - } - - // Resolves a packed (category, index) reference. Only used by the fields that - // can point at an object of any category. - Wc.CompositionObject? GetObjectReference(uint reference) - { - if (reference == Format.NullIndex) - { - return null; - } - - var category = Format.UnpackCategory(reference); - var index = (uint)Format.UnpackIndex(reference); - - return category switch - { - Fb.ObjectCategory.Visual => GetVisual(index), - Fb.ObjectCategory.Shape => GetShape(index), - Fb.ObjectCategory.Geometry => GetGeometry(index), - Fb.ObjectCategory.Brush => GetBrush(index), - Fb.ObjectCategory.Animation => GetAnimation(index), - Fb.ObjectCategory.Easing => GetEasing(index), - Fb.ObjectCategory.PropertySet => GetPropertySet(index), - - // Only a visual surface is a CompositionObject; an image surface is not, - // and nothing can legitimately reference one this way. - Fb.ObjectCategory.Surface => GetSurface(index) as Wc.CompositionObject - ?? throw new FlatBufferFormatException("An image surface cannot be referenced."), - Fb.ObjectCategory.Clip => GetClip(index), - Fb.ObjectCategory.Controller => GetController(index), - Fb.ObjectCategory.Shadow => GetShadow(index), - Fb.ObjectCategory.GradientStop => GetGradientStop(index), - Fb.ObjectCategory.ViewBox => GetViewBox(index), - _ => throw new FlatBufferFormatException($"Unsupported object category {category}."), - }; - } - - // --------------------------------------------------------------------- - // Visuals. - // --------------------------------------------------------------------- - Wc.Visual? GetVisual(uint index) - { - if (index == Format.NullIndex) - { - return null; - } - - if (index >= (uint)_visuals.Length) - { - throw new FlatBufferFormatException($"Visual index {index} is out of range."); - } - - if (_visuals[index] is Wc.Visual cached) - { - return cached; - } - - var table = Require(_root.Visuals((int)index), "visual"); - - // The concrete type is known only here. Everything below this point, - // including the recursive walk into the children, uses a base type. - Wc.ContainerVisual result = table.Kind switch - { - Fb.VisualKind.Shape => _compositor.CreateShapeVisual(), - Fb.VisualKind.Sprite => _compositor.CreateSpriteVisual(), - Fb.VisualKind.Layer => _compositor.CreateLayerVisual(), - Fb.VisualKind.Container => _compositor.CreateContainerVisual(), - _ => throw new FlatBufferFormatException($"Unsupported visual kind {table.Kind}."), - }; - - // Cached before anything it refers to is realized, so that a cycle - // terminates. - _visuals[index] = result; - - if (table.BorderMode.HasValue) - { - result.BorderMode = (Wc.CompositionBorderMode)table.BorderMode.Value; - } - - if (ToVector3(table.CenterPoint) is Vector3 centerPoint) - { - result.CenterPoint = centerPoint; - } - - if (table.Clip != Format.NullIndex) - { - result.Clip = GetClip(table.Clip); - } - - if (table.IsVisible.HasValue) - { - result.IsVisible = table.IsVisible.Value; - } - - if (ToVector3(table.Offset) is Vector3 offset) - { - result.Offset = offset; - } - - if (table.Opacity.HasValue) - { - result.Opacity = table.Opacity.Value; - } - - if (table.RotationAngleInDegrees.HasValue) - { - result.RotationAngleInDegrees = table.RotationAngleInDegrees.Value; - } - - if (ToVector3(table.RotationAxis) is Vector3 rotationAxis) - { - result.RotationAxis = rotationAxis; - } - - if (ToVector3(table.Scale) is Vector3 scale) - { - result.Scale = scale; - } - - if (ToVector2(table.Size) is Vector2 size) - { - result.Size = size; - } - - if (ToMatrix4x4(table.TransformMatrix) is Matrix4x4 transformMatrix) - { - result.TransformMatrix = transformMatrix; - } - - switch (result) - { - case Wc.ShapeVisual shapeVisual: - for (var i = 0; i < table.ShapesLength; i++) - { - shapeVisual.Shapes.Add( - GetShape(table.Shapes(i)) - ?? throw new FlatBufferFormatException("A shape is missing.")); - } - - if (table.ViewBox != Format.NullIndex) - { - shapeVisual.ViewBox = GetViewBox(table.ViewBox); - } - - break; - case Wc.SpriteVisual spriteVisual: - if (table.Brush != Format.NullIndex) - { - spriteVisual.Brush = GetBrush(table.Brush); - } - - if (table.Shadow != Format.NullIndex) - { - spriteVisual.Shadow = GetShadow(table.Shadow); - } - - break; - case Wc.LayerVisual layerVisual: - if (table.Shadow != Format.NullIndex) - { - layerVisual.Shadow = GetShadow(table.Shadow); - } - - break; - } - - for (var i = 0; i < table.ChildrenLength; i++) - { - result.Children.InsertAtTop( - GetVisual(table.Children(i)) - ?? throw new FlatBufferFormatException("A visual child is missing.")); - } - - InitializeCompositionObject(result, table.Base); - return result; - } - - // --------------------------------------------------------------------- - // Shapes. - // --------------------------------------------------------------------- - Wc.CompositionShape? GetShape(uint index) - { - if (index == Format.NullIndex) - { - return null; - } - - if (index >= (uint)_shapes.Length) - { - throw new FlatBufferFormatException($"Shape index {index} is out of range."); - } - - if (_shapes[index] is Wc.CompositionShape cached) - { - return cached; - } - - var table = Require(_root.Shapes((int)index), "shape"); - - Wc.CompositionShape result = table.Kind switch - { - Fb.ShapeKind.Sprite => _compositor.CreateSpriteShape(), - Fb.ShapeKind.Container => _compositor.CreateContainerShape(), - _ => throw new FlatBufferFormatException($"Unsupported shape kind {table.Kind}."), - }; - - _shapes[index] = result; - - if (ToVector2(table.CenterPoint) is Vector2 centerPoint) - { - result.CenterPoint = centerPoint; - } - - if (ToVector2(table.Offset) is Vector2 offset) - { - result.Offset = offset; - } - - if (table.RotationAngleInDegrees.HasValue) - { - result.RotationAngleInDegrees = table.RotationAngleInDegrees.Value; - } - - if (ToVector2(table.Scale) is Vector2 scale) - { - result.Scale = scale; - } - - if (ToMatrix3x2(table.TransformMatrix) is Matrix3x2 transformMatrix) - { - result.TransformMatrix = transformMatrix; - } - - switch (result) - { - case Wc.CompositionContainerShape containerShape: - for (var i = 0; i < table.ShapesLength; i++) - { - containerShape.Shapes.Add( - GetShape(table.Shapes(i)) - ?? throw new FlatBufferFormatException("A shape is missing.")); - } - - break; - case Wc.CompositionSpriteShape spriteShape: - ApplySpriteShapeProperties(spriteShape, table); - break; - } - - InitializeCompositionObject(result, table.Base); - return result; - } - - void ApplySpriteShapeProperties(Wc.CompositionSpriteShape target, Fb.Shape table) - { - if (table.FillBrush != Format.NullIndex) - { - target.FillBrush = GetBrush(table.FillBrush); - } - - if (table.StrokeBrush != Format.NullIndex) - { - target.StrokeBrush = GetBrush(table.StrokeBrush); - } - - if (table.Geometry != Format.NullIndex) - { - target.Geometry = GetGeometry(table.Geometry); - } - - if (table.IsStrokeNonScaling.HasValue) - { - target.IsStrokeNonScaling = table.IsStrokeNonScaling.Value; - } - - if (table.StrokeDashOffset.HasValue) - { - target.StrokeDashOffset = table.StrokeDashOffset.Value; - } - - for (var i = 0; i < table.StrokeDashArrayLength; i++) - { - target.StrokeDashArray.Add(table.StrokeDashArray(i)); - } - - if (table.StrokeDashCap.HasValue) - { - target.StrokeDashCap = (Wc.CompositionStrokeCap)table.StrokeDashCap.Value; - } - - if (table.StrokeStartCap.HasValue) - { - target.StrokeStartCap = (Wc.CompositionStrokeCap)table.StrokeStartCap.Value; - } - - if (table.StrokeEndCap.HasValue) - { - target.StrokeEndCap = (Wc.CompositionStrokeCap)table.StrokeEndCap.Value; - } - - if (table.StrokeLineJoin.HasValue) - { - target.StrokeLineJoin = (Wc.CompositionStrokeLineJoin)table.StrokeLineJoin.Value; - } - - if (table.StrokeMiterLimit.HasValue) - { - target.StrokeMiterLimit = table.StrokeMiterLimit.Value; - } - - if (table.StrokeThickness.HasValue) - { - target.StrokeThickness = table.StrokeThickness.Value; - } - } - - // --------------------------------------------------------------------- - // Geometries. - // --------------------------------------------------------------------- - Wc.CompositionGeometry? GetGeometry(uint index) - { - if (index == Format.NullIndex) - { - return null; - } - - if (index >= (uint)_geometries.Length) - { - throw new FlatBufferFormatException($"Geometry index {index} is out of range."); - } - - if (_geometries[index] is Wc.CompositionGeometry cached) - { - return cached; - } - - var table = Require(_root.Geometries((int)index), "geometry"); - - Wc.CompositionGeometry result = table.Kind switch - { - Fb.GeometryKind.Rectangle => _compositor.CreateRectangleGeometry(), - Fb.GeometryKind.RoundedRectangle => _compositor.CreateRoundedRectangleGeometry(), - Fb.GeometryKind.Ellipse => _compositor.CreateEllipseGeometry(), - Fb.GeometryKind.Path => _compositor.CreatePathGeometry(), - _ => throw new FlatBufferFormatException($"Unsupported geometry kind {table.Kind}."), - }; - - _geometries[index] = result; - - if (table.TrimStart.HasValue) - { - result.TrimStart = table.TrimStart.Value; - } - - if (table.TrimEnd.HasValue) - { - result.TrimEnd = table.TrimEnd.Value; - } - - if (table.TrimOffset.HasValue) - { - result.TrimOffset = table.TrimOffset.Value; - } - - switch (result) - { - case Wc.CompositionPathGeometry pathGeometry: - var path = GetCanvasGeometry(table.Path); - pathGeometry.Path = path is null ? null : new Wc.CompositionPath(path); - break; - case Wc.CompositionRoundedRectangleGeometry roundedRectangle: - if (ToVector2(table.Offset) is Vector2 roundedOffset) - { - roundedRectangle.Offset = roundedOffset; - } - - if (ToVector2(table.Size) is Vector2 roundedSize) - { - roundedRectangle.Size = roundedSize; - } - - roundedRectangle.CornerRadius = ToVector2(table.CornerRadius) ?? Vector2.Zero; - break; - case Wc.CompositionRectangleGeometry rectangle: - if (ToVector2(table.Offset) is Vector2 rectangleOffset) - { - rectangle.Offset = rectangleOffset; - } - - if (ToVector2(table.Size) is Vector2 rectangleSize) - { - rectangle.Size = rectangleSize; - } - - break; - case Wc.CompositionEllipseGeometry ellipse: - ellipse.Center = ToVector2(table.Center) ?? Vector2.Zero; - ellipse.Radius = ToVector2(table.Radius) ?? Vector2.Zero; - break; - } - - InitializeCompositionObject(result, table.Base); - return result; - } - - // --------------------------------------------------------------------- - // Brushes. - // --------------------------------------------------------------------- - Wc.CompositionBrush? GetBrush(uint index) - { - if (index == Format.NullIndex) - { - return null; - } - - if (index >= (uint)_brushes.Length) - { - throw new FlatBufferFormatException($"Brush index {index} is out of range."); - } - - if (_brushes[index] is Wc.CompositionBrush cached) - { - return cached; - } - - var table = Require(_root.Brushes((int)index), "brush"); - - Wc.CompositionBrush result; - switch (table.Kind) - { - case Fb.BrushKind.Color: - result = _compositor.CreateColorBrush(); - break; - case Fb.BrushKind.LinearGradient: - result = _compositor.CreateLinearGradientBrush(); - break; - case Fb.BrushKind.RadialGradient: - result = _compositor.CreateRadialGradientBrush(); - break; - case Fb.BrushKind.Mask: - result = _compositor.CreateMaskBrush(); - break; - case Fb.BrushKind.Surface: - // The surface is needed in order to construct the brush, so it is - // realized before the brush is placed in the cache. - result = _compositor.CreateSurfaceBrush( - GetSurface(RequireIndex(table.Surface, "surface")) - ?? throw new FlatBufferFormatException("A surface brush has no surface.")); - break; - case Fb.BrushKind.Effect: - var effect = GetEffect(RequireIndex(table.Effect, "effect")) - ?? throw new FlatBufferFormatException("An effect brush has no effect."); - result = _compositor.CreateEffectFactory(effect).CreateBrush(); - break; - default: - throw new FlatBufferFormatException($"Unsupported brush kind {table.Kind}."); - } - - _brushes[index] = result; - - switch (result) - { - case Wc.CompositionColorBrush colorBrush: - if (table.Color.HasValue) - { - colorBrush.Color = ToColor(table.Color.Value); - } - - break; - case Wc.CompositionMaskBrush maskBrush: - if (table.Source != Format.NullIndex) - { - maskBrush.Source = GetBrush(table.Source); - } - - if (table.Mask != Format.NullIndex) - { - maskBrush.Mask = GetBrush(table.Mask); - } - - break; - case Wc.CompositionEffectBrush effectBrush: - for (var i = 0; i < table.SourceParametersLength; i++) - { - var parameter = Require(table.SourceParameters(i), "source parameter"); - effectBrush.SetSourceParameter( - GetRequiredString(parameter.Name), - GetBrush(parameter.Brush) - ?? throw new FlatBufferFormatException("An effect source has no brush.")); - } - - break; - } - - if (result is Wc.CompositionGradientBrush gradientBrush) - { - ApplyGradientBrushProperties(gradientBrush, table); - } - - InitializeCompositionObject(result, table.Base); - return result; - } - - void ApplyGradientBrushProperties(Wc.CompositionGradientBrush target, Fb.Brush table) - { - if (ToVector2(table.AnchorPoint) is Vector2 anchorPoint) - { - target.AnchorPoint = anchorPoint; - } - - if (ToVector2(table.CenterPoint) is Vector2 centerPoint) - { - target.CenterPoint = centerPoint; - } - - for (var i = 0; i < table.ColorStopsLength; i++) - { - target.ColorStops.Add( - GetGradientStop(table.ColorStops(i)) - ?? throw new FlatBufferFormatException("A gradient stop is missing.")); - } - - if (table.ExtendMode.HasValue) - { - target.ExtendMode = (Wc.CompositionGradientExtendMode)table.ExtendMode.Value; - } - - if (table.InterpolationSpace.HasValue) - { - target.InterpolationSpace = (Wc.CompositionColorSpace)table.InterpolationSpace.Value; - } - - if (table.MappingMode.HasValue) - { - target.MappingMode = (Wc.CompositionMappingMode)table.MappingMode.Value; - } - - if (ToVector2(table.Offset) is Vector2 offset) - { - target.Offset = offset; - } - - if (table.RotationAngleInDegrees.HasValue) - { - target.RotationAngleInDegrees = table.RotationAngleInDegrees.Value; - } - - if (ToVector2(table.Scale) is Vector2 scale) - { - target.Scale = scale; - } - - if (ToMatrix3x2(table.TransformMatrix) is Matrix3x2 transformMatrix) - { - target.TransformMatrix = transformMatrix; - } - - switch (target) - { - case Wc.CompositionLinearGradientBrush linear: - if (ToVector2(table.StartPoint) is Vector2 startPoint) - { - linear.StartPoint = startPoint; - } - - if (ToVector2(table.EndPoint) is Vector2 endPoint) - { - linear.EndPoint = endPoint; - } - - break; - case Wc.CompositionRadialGradientBrush radial: - if (ToVector2(table.EllipseCenter) is Vector2 ellipseCenter) - { - radial.EllipseCenter = ellipseCenter; - } - - if (ToVector2(table.EllipseRadius) is Vector2 ellipseRadius) - { - radial.EllipseRadius = ellipseRadius; - } - - if (ToVector2(table.GradientOriginOffset) is Vector2 gradientOriginOffset) - { - radial.GradientOriginOffset = gradientOriginOffset; - } - - break; - } - } - - Wc.CompositionColorGradientStop? GetGradientStop(uint index) - { - if (index == Format.NullIndex) - { - return null; - } - - if (index >= (uint)_gradientStops.Length) - { - throw new FlatBufferFormatException($"Gradient stop index {index} is out of range."); - } - - if (_gradientStops[index] is Wc.CompositionColorGradientStop cached) - { - return cached; - } - - var table = Require(_root.GradientStops((int)index), "gradient stop"); - var result = _compositor.CreateColorGradientStop(); - _gradientStops[index] = result; - - result.Color = ToColor(Require(table.Color, "color")); - result.Offset = table.Offset; - - InitializeCompositionObject(result, table.Base); - return result; - } - - // --------------------------------------------------------------------- - // Clips, view boxes, shadows and surfaces. - // --------------------------------------------------------------------- - Wc.CompositionViewBox? GetViewBox(uint index) - { - if (index == Format.NullIndex) - { - return null; - } - - if (index >= (uint)_viewBoxes.Length) - { - throw new FlatBufferFormatException($"View box index {index} is out of range."); - } - - if (_viewBoxes[index] is Wc.CompositionViewBox cached) - { - return cached; - } - - var table = Require(_root.ViewBoxes((int)index), "view box"); - var result = _compositor.CreateViewBox(); - _viewBoxes[index] = result; - - result.Size = ToVector2(table.Size) ?? Vector2.Zero; - - InitializeCompositionObject(result, table.Base); - return result; - } - - Wc.CompositionClip? GetClip(uint index) - { - if (index == Format.NullIndex) - { - return null; - } - - if (index >= (uint)_clips.Length) - { - throw new FlatBufferFormatException($"Clip index {index} is out of range."); - } - - if (_clips[index] is Wc.CompositionClip cached) - { - return cached; - } - - var table = Require(_root.Clips((int)index), "clip"); - - Wc.CompositionClip result = table.Kind switch - { - Fb.ClipKind.Geometric => _compositor.CreateGeometricClip(), - Fb.ClipKind.Inset => _compositor.CreateInsetClip(), - _ => throw new FlatBufferFormatException($"Unsupported clip kind {table.Kind}."), - }; - - _clips[index] = result; - - if (ToVector2(table.CenterPoint) is Vector2 centerPoint) - { - result.CenterPoint = centerPoint; - } - - if (ToVector2(table.Scale) is Vector2 scale) - { - result.Scale = scale; - } - - switch (result) - { - case Wc.InsetClip inset: - if (table.LeftInset.HasValue) - { - inset.LeftInset = table.LeftInset.Value; - } - - if (table.RightInset.HasValue) - { - inset.RightInset = table.RightInset.Value; - } - - if (table.TopInset.HasValue) - { - inset.TopInset = table.TopInset.Value; - } - - if (table.BottomInset.HasValue) - { - inset.BottomInset = table.BottomInset.Value; - } - - break; - case Wc.CompositionGeometricClip geometric: - if (table.Geometry != Format.NullIndex) - { - geometric.Geometry = GetGeometry(table.Geometry); - } - - break; - } - - InitializeCompositionObject(result, table.Base); - return result; - } - - Wc.CompositionShadow? GetShadow(uint index) - { - if (index == Format.NullIndex) - { - return null; - } - - if (index >= (uint)_shadows.Length) - { - throw new FlatBufferFormatException($"Shadow index {index} is out of range."); - } - - if (_shadows[index] is Wc.CompositionShadow cached) - { - return cached; - } - - var table = Require(_root.Shadows((int)index), "shadow"); - - Wc.CompositionShadow result = table.Kind switch - { - Fb.ShadowKind.Drop => _compositor.CreateDropShadow(), - _ => throw new FlatBufferFormatException($"Unsupported shadow kind {table.Kind}."), - }; - - _shadows[index] = result; - - if (result is Wc.DropShadow dropShadow) - { - if (table.BlurRadius.HasValue) - { - dropShadow.BlurRadius = table.BlurRadius.Value; - } - - if (table.Color.HasValue) - { - dropShadow.Color = ToColor(table.Color.Value); - } - - if (table.Mask != Format.NullIndex) - { - dropShadow.Mask = GetBrush(table.Mask); - } - - if (ToVector3(table.Offset) is Vector3 offset) - { - dropShadow.Offset = offset; - } - - if (table.Opacity.HasValue) - { - dropShadow.Opacity = table.Opacity.Value; - } - - if (table.SourcePolicy.HasValue) - { - dropShadow.SourcePolicy = (Wc.CompositionDropShadowSourcePolicy)table.SourcePolicy.Value; - } - } - - InitializeCompositionObject(result, table.Base); - return result; - } - - Wc.ICompositionSurface? GetSurface(uint index) - { - if (index == Format.NullIndex) - { - return null; - } - - if (index >= (uint)_surfaces.Length) - { - throw new FlatBufferFormatException($"Surface index {index} is out of range."); - } - - if (_surfaces[index] is Wc.ICompositionSurface cached) - { - return cached; - } - - var table = Require(_root.Surfaces((int)index), "surface"); - - switch (table.Kind) - { - case Fb.SurfaceKind.VisualSurface: - var visualSurface = _compositor.CreateVisualSurface(); - _surfaces[index] = visualSurface; - - if (table.SourceVisual != Format.NullIndex) - { - visualSurface.SourceVisual = GetVisual(table.SourceVisual); - } - - if (ToVector2(table.SourceSize) is Vector2 sourceSize) - { - visualSurface.SourceSize = sourceSize; - } - - if (ToVector2(table.SourceOffset) is Vector2 sourceOffset) - { - visualSurface.SourceOffset = sourceOffset; - } - - InitializeCompositionObject(visualSurface, table.Base); - return visualSurface; - - case Fb.SurfaceKind.LoadedImageFromUri: - var uri = GetRequiredString(table.Uri); - var fromUri = Wm.LoadedImageSurface.StartLoadFromUri(new Uri(uri, UriKind.RelativeOrAbsolute)); - _surfaces[index] = fromUri; - return fromUri; - - case Fb.SurfaceKind.LoadedImageFromStream: - var bytes = new byte[table.BytesLength]; - for (var i = 0; i < bytes.Length; i++) - { - bytes[i] = table.Bytes(i); - } - - var fromStream = Wm.LoadedImageSurface.StartLoadFromStream( - bytes.AsBuffer().AsStream().AsRandomAccessStream()); - _surfaces[index] = fromStream; - return fromStream; - - default: - throw new FlatBufferFormatException($"Unsupported surface kind {table.Kind}."); - } - } - - // --------------------------------------------------------------------- - // Effects. - // --------------------------------------------------------------------- - IGraphicsEffect? GetEffect(uint index) - { - if (index == Format.NullIndex) - { - return null; - } - - if (index >= (uint)_effects.Length) - { - throw new FlatBufferFormatException($"Effect index {index} is out of range."); - } - - if (_effects[index] is IGraphicsEffect cached) - { - return cached; - } - - var table = Require(_root.Effects((int)index), "effect"); - - // The sources are the names that the brush later binds real brushes to. The - // composition engine matches them up by name. - var sources = new List(table.SourcesLength); - for (var i = 0; i < table.SourcesLength; i++) - { - sources.Add(new Wc.CompositionEffectSourceParameter(GetRequiredString(table.Sources(i)))); - } - - IGraphicsEffect result; - switch (table.Kind) - { - case Fb.EffectKind.Composite: - var composite = new Mgce.CompositeEffect - { - Mode = (Mgc.CanvasComposite)table.Mode, - }; - - foreach (var source in sources) - { - composite.Sources.Add(source); - } - - result = composite; - break; - case Fb.EffectKind.GaussianBlur: - if (sources.Count != 1) - { - throw new FlatBufferFormatException("A blur effect must have exactly one source."); - } - - result = new Mgce.GaussianBlurEffect - { - BlurAmount = table.BlurAmount, - Source = sources[0], - }; - break; - default: - throw new FlatBufferFormatException($"Unsupported effect kind {table.Kind}."); - } - - _effects[index] = result; - return result; - } - - // --------------------------------------------------------------------- - // Easings. - // --------------------------------------------------------------------- - Wc.CompositionEasingFunction? GetEasing(uint index) - { - if (index == Format.NullIndex) - { - return null; - } - - if (index >= (uint)_easings.Length) - { - throw new FlatBufferFormatException($"Easing index {index} is out of range."); - } - - if (_easings[index] is Wc.CompositionEasingFunction cached) - { - return cached; - } - - var table = Require(_root.Easings((int)index), "easing"); - - Wc.CompositionEasingFunction result; - switch (table.Kind) - { - case Fb.EasingKind.Linear: - result = _compositor.CreateLinearEasingFunction(); - break; - case Fb.EasingKind.CubicBezier: - result = _compositor.CreateCubicBezierEasingFunction( - ToVector2(table.ControlPoint1) ?? Vector2.Zero, - ToVector2(table.ControlPoint2) ?? Vector2.Zero); - break; - case Fb.EasingKind.Step: - var step = _compositor.CreateStepEasingFunction(); - - if (table.StepCount.HasValue) - { - step.StepCount = table.StepCount.Value; - } - - if (table.InitialStep.HasValue) - { - step.InitialStep = table.InitialStep.Value; - } - - if (table.FinalStep.HasValue) - { - step.FinalStep = table.FinalStep.Value; - } - - if (table.IsInitialStepSingleFrame.HasValue) - { - step.IsInitialStepSingleFrame = table.IsInitialStepSingleFrame.Value; - } - - if (table.IsFinalStepSingleFrame.HasValue) - { - step.IsFinalStepSingleFrame = table.IsFinalStepSingleFrame.Value; - } - - result = step; - break; - default: - throw new FlatBufferFormatException($"Unsupported easing kind {table.Kind}."); - } - - _easings[index] = result; - - InitializeCompositionObject(result, table.Base); - return result; - } - - // --------------------------------------------------------------------- - // Animations. - // --------------------------------------------------------------------- - Wc.CompositionAnimation? GetAnimation(uint index) - { - if (index == Format.NullIndex) - { - return null; - } - - if (index >= (uint)_animations.Length) - { - throw new FlatBufferFormatException($"Animation index {index} is out of range."); - } - - if (_animations[index] is Wc.CompositionAnimation cached) - { - return cached; - } - - var table = Require(_root.Animations((int)index), "animation"); - - Wc.CompositionAnimation result = table.Kind == Fb.AnimationKind.Expression - ? _compositor.CreateExpressionAnimation(GetRequiredString(table.Expression)) - : CreateKeyFrameAnimation(table); - - _animations[index] = result; - - if (GetString(table.Target) is string target) - { - result.Target = target; - } - - for (var i = 0; i < table.ReferenceParametersLength; i++) - { - var parameter = Require(table.ReferenceParameters(i), "reference parameter"); - result.SetReferenceParameter( - GetRequiredString(parameter.Name), - GetObjectReference(parameter.Target) - ?? throw new FlatBufferFormatException("A reference parameter has no target.")); - } - - InitializeCompositionObject(result, table.Base); - return result; - } - - // Creates the animation and inserts its key frames in one place, because the - // concrete type is only needed in order to insert a value key frame and would - // otherwise have to be recovered with a cast. - Wc.CompositionAnimation CreateKeyFrameAnimation(Fb.Animation table) - { - Wc.KeyFrameAnimation result; - Wc.ScalarKeyFrameAnimation? scalar = null; - Wc.Vector2KeyFrameAnimation? vector2 = null; - Wc.Vector3KeyFrameAnimation? vector3 = null; - Wc.Vector4KeyFrameAnimation? vector4 = null; - Wc.ColorKeyFrameAnimation? color = null; - Wc.BooleanKeyFrameAnimation? boolean = null; - Wc.PathKeyFrameAnimation? path = null; - - switch (table.Kind) - { - case Fb.AnimationKind.Scalar: - scalar = _compositor.CreateScalarKeyFrameAnimation(); - result = scalar; - break; - case Fb.AnimationKind.Vector2: - vector2 = _compositor.CreateVector2KeyFrameAnimation(); - result = vector2; - break; - case Fb.AnimationKind.Vector3: - vector3 = _compositor.CreateVector3KeyFrameAnimation(); - result = vector3; - break; - case Fb.AnimationKind.Vector4: - vector4 = _compositor.CreateVector4KeyFrameAnimation(); - result = vector4; - break; - case Fb.AnimationKind.Color: - color = _compositor.CreateColorKeyFrameAnimation(); - - if (table.InterpolationColorSpace.HasValue) - { - color.InterpolationColorSpace = - (Wc.CompositionColorSpace)table.InterpolationColorSpace.Value; - } - - result = color; - break; - case Fb.AnimationKind.Boolean: - boolean = _compositor.CreateBooleanKeyFrameAnimation(); - result = boolean; - break; - case Fb.AnimationKind.Path: - path = _compositor.CreatePathKeyFrameAnimation(); - result = path; - break; - default: - throw new FlatBufferFormatException($"Unsupported animation kind {table.Kind}."); - } - - for (var i = 0; i < table.KeyFramesLength; i++) - { - var frame = Require(table.KeyFrames(i), "key frame"); - - if (frame.Kind == Fb.KeyFrameKind.Expression) - { - result.InsertExpressionKeyFrame( - frame.Progress, - GetRequiredString(frame.Expression), - GetEasing(frame.Easing)); - } - else if (frame.Kind == Fb.KeyFrameKind.Value) - { - switch (table.Kind) - { - case Fb.AnimationKind.Scalar: - scalar!.InsertKeyFrame(frame.Progress, frame.Scalar, GetEasing(frame.Easing)); - break; - case Fb.AnimationKind.Vector2: - vector2!.InsertKeyFrame( - frame.Progress, - ToVector2(frame.Vector), - GetEasing(frame.Easing)); - break; - case Fb.AnimationKind.Vector3: - vector3!.InsertKeyFrame( - frame.Progress, - ToVector3(frame.Vector), - GetEasing(frame.Easing)); - break; - case Fb.AnimationKind.Vector4: - vector4!.InsertKeyFrame( - frame.Progress, - ToVector4(frame.Vector), - GetEasing(frame.Easing)); - break; - case Fb.AnimationKind.Color: - color!.InsertKeyFrame( - frame.Progress, - ToColor(Require(frame.Color, "color")), - GetEasing(frame.Easing)); - break; - case Fb.AnimationKind.Boolean: - // A boolean cannot be interpolated, so it has no easing. - boolean!.InsertKeyFrame(frame.Progress, frame.Scalar != 0); - break; - case Fb.AnimationKind.Path: - path!.InsertKeyFrame( - frame.Progress, - new Wc.CompositionPath( - GetCanvasGeometry(frame.Path) - ?? throw new FlatBufferFormatException("A path key frame has no path.")), - GetEasing(frame.Easing)); - break; - } - } - } - - result.Duration = TimeSpan.FromTicks(table.DurationTicks); - - return result; - } - - // --------------------------------------------------------------------- - // Paths. - // - // A canvas geometry is not a CompositionObject, so it has no shared state and - // no animators; it is only ever a value. - // --------------------------------------------------------------------- - CanvasGeometry? GetCanvasGeometry(uint index) - { - if (index == Format.NullIndex) - { - return null; - } - - if (index >= (uint)_canvasGeometries.Length) - { - throw new FlatBufferFormatException($"Canvas geometry index {index} is out of range."); - } - - switch (_canvasGeometryStates[index]) - { - case CanvasGeometryState.Realized: - return _canvasGeometries[index]; - case CanvasGeometryState.Realizing: - // A geometry that is built from itself would never finish. - throw new FlatBufferFormatException($"Canvas geometry {index} is cyclic."); - } - - _canvasGeometryStates[index] = CanvasGeometryState.Realizing; - - var table = Require(_root.CanvasGeometries((int)index), "canvas geometry"); - - CanvasGeometry result; - switch (table.Kind) - { - case Fb.CanvasGeometryKind.Combination: - var a = GetCanvasGeometry(RequireIndex(table.A, "geometry")) - ?? throw new FlatBufferFormatException("A combination has no first geometry."); - var b = GetCanvasGeometry(RequireIndex(table.B, "geometry")) - ?? throw new FlatBufferFormatException("A combination has no second geometry."); - result = a.CombineWith( - b, - ToMatrix3x2(table.Matrix) ?? Matrix3x2.Identity, - (CanvasGeometryCombine)table.CombineMode); - break; - - case Fb.CanvasGeometryKind.TransformedGeometry: - var source = GetCanvasGeometry(RequireIndex(table.Source, "geometry")) - ?? throw new FlatBufferFormatException("A transformed geometry has no source."); - result = source.Transform(ToMatrix3x2(table.Matrix) ?? Matrix3x2.Identity); - break; - - case Fb.CanvasGeometryKind.Group: - var geometries = new CanvasGeometry[table.GeometriesLength]; - for (var i = 0; i < geometries.Length; i++) - { - geometries[i] = GetCanvasGeometry(table.Geometries(i)) - ?? throw new FlatBufferFormatException("A grouped geometry is missing."); - } - - result = CanvasGeometry.CreateGroup( - null, - geometries, - (CanvasFilledRegionDetermination)table.FillRule); - break; - - case Fb.CanvasGeometryKind.Path: - result = CreatePath(table); - break; - - case Fb.CanvasGeometryKind.Ellipse: - result = CanvasGeometry.CreateEllipse(null, table.X, table.Y, table.RadiusX, table.RadiusY); - break; - - case Fb.CanvasGeometryKind.RoundedRectangle: - result = CanvasGeometry.CreateRoundedRectangle( - null, table.X, table.Y, table.W, table.H, table.RadiusX, table.RadiusY); - break; - - default: - throw new FlatBufferFormatException($"Unsupported canvas geometry kind {table.Kind}."); - } - - _canvasGeometries[index] = result; - _canvasGeometryStates[index] = CanvasGeometryState.Realized; - return result; - } - - // Replays the flattened opcode and operand streams of a path. The operands of - // every command are consecutive in one array, which is why there is no table - // per command. - static CanvasGeometry CreatePath(Fb.CanvasGeometry table) - { - using var builder = new CanvasPathBuilder(null); - builder.SetFilledRegionDetermination((CanvasFilledRegionDetermination)table.FillRule); - - var operand = 0; - var inFigure = false; - - float Next() - => operand < table.OperandsLength - ? table.Operands(operand++) - : throw new FlatBufferFormatException("A path ran out of operands."); - - void RequireFigure(bool expected) - { - if (inFigure != expected) - { - throw new FlatBufferFormatException("A path has a figure that does not begin and end."); - } - } - - for (var i = 0; i < table.OpsLength; i++) - { - switch ((Fb.PathOp)table.Ops(i)) - { - case Fb.PathOp.BeginFigure: - RequireFigure(false); - builder.BeginFigure(new Vector2(Next(), Next())); - inFigure = true; - break; - case Fb.PathOp.EndFigure: - RequireFigure(true); - builder.EndFigure((CanvasFigureLoop)Next()); - inFigure = false; - break; - case Fb.PathOp.AddLine: - RequireFigure(true); - builder.AddLine(new Vector2(Next(), Next())); - break; - case Fb.PathOp.AddCubicBezier: - RequireFigure(true); - builder.AddCubicBezier( - new Vector2(Next(), Next()), - new Vector2(Next(), Next()), - new Vector2(Next(), Next())); - break; - default: - throw new FlatBufferFormatException($"Unsupported path opcode {table.Ops(i)}."); - } - } - - RequireFigure(false); - - return CanvasGeometry.CreatePath(builder); - } - - // --------------------------------------------------------------------- - // Value helpers. An absent struct field means the property was never set, - // so these return null rather than a default value. - // --------------------------------------------------------------------- - static Wui.Color ToColor(Fb.Color value) - => Wui.Color.FromArgb(value.A, value.R, value.G, value.B); - - static Vector2? ToVector2(Fb.Vec2? value) - => value.HasValue ? new Vector2(value.Value.X, value.Value.Y) : null; - - static Vector3? ToVector3(Fb.Vec3? value) - => value.HasValue ? new Vector3(value.Value.X, value.Value.Y, value.Value.Z) : null; - - static Vector2 ToVector2(Fb.Vec4? value) - => value.HasValue ? new Vector2(value.Value.X, value.Value.Y) : Vector2.Zero; - - static Vector3 ToVector3(Fb.Vec4? value) - => value.HasValue ? new Vector3(value.Value.X, value.Value.Y, value.Value.Z) : Vector3.Zero; - - static Vector4 ToVector4(Fb.Vec4? value) - => value.HasValue - ? new Vector4(value.Value.X, value.Value.Y, value.Value.Z, value.Value.W) - : Vector4.Zero; - - static Matrix3x2? ToMatrix3x2(Fb.Mat3x2? value) - => value.HasValue - ? new Matrix3x2( - value.Value.M11, value.Value.M12, - value.Value.M21, value.Value.M22, - value.Value.M31, value.Value.M32) - : null; - - static Matrix4x4? ToMatrix4x4(Fb.Mat4x4? value) - => value.HasValue - ? new Matrix4x4( - value.Value.M11, value.Value.M12, value.Value.M13, value.Value.M14, - value.Value.M21, value.Value.M22, value.Value.M23, value.Value.M24, - value.Value.M31, value.Value.M32, value.Value.M33, value.Value.M34, - value.Value.M41, value.Value.M42, value.Value.M43, value.Value.M44) - : null; - } -} diff --git a/dlls/LottieRuntime/Generated/lottie_comp_generated.h b/source/LottieRuntime/Generated/lottie_comp_generated.h similarity index 100% rename from dlls/LottieRuntime/Generated/lottie_comp_generated.h rename to source/LottieRuntime/Generated/lottie_comp_generated.h diff --git a/dlls/LottieRuntime/LottieRuntime.cpp b/source/LottieRuntime/LottieRuntime.cpp similarity index 97% rename from dlls/LottieRuntime/LottieRuntime.cpp rename to source/LottieRuntime/LottieRuntime.cpp index 209b9655..c0f097b7 100644 --- a/dlls/LottieRuntime/LottieRuntime.cpp +++ b/source/LottieRuntime/LottieRuntime.cpp @@ -32,6 +32,10 @@ // C++/WinRT requires before projected headers. #include +#if defined(LOTTIE_RUNTIME_WINUI2) == defined(LOTTIE_RUNTIME_WINUI3) +#error Define exactly one of LOTTIE_RUNTIME_WINUI2 or LOTTIE_RUNTIME_WINUI3. +#endif + #include "LottieRuntime.h" #include @@ -53,17 +57,28 @@ #include #include #include -#include #undef GetCurrentTime +#if defined(LOTTIE_RUNTIME_WINUI3) +#include +#include +#else +#include #include +#endif #include "Generated/lottie_comp_generated.h" namespace Fb = CommunityToolkit::WinUI::Lottie::CompDataFlatbuffer::Schema; using namespace winrt::Windows::Foundation::Numerics; +#if defined(LOTTIE_RUNTIME_WINUI3) +using namespace winrt::Microsoft::UI::Composition; +namespace XamlMedia = winrt::Microsoft::UI::Xaml::Media; +#else using namespace winrt::Windows::UI::Composition; +namespace XamlMedia = winrt::Windows::UI::Xaml::Media; +#endif namespace { @@ -75,6 +90,10 @@ namespace // version is rejected, because it may use fields that are not read here. constexpr uint16_t SupportedSchemaVersion = 1; + // Recursive composition graphs are invalid. Limit nesting even for acyclic + // graphs so a small malicious buffer cannot exhaust the native stack. + constexpr uint32_t MaximumTraversalDepth = 128; + // Object references that can point at more than one category pack the category // into the top four bits. constexpr uint32_t CategoryShift = 28; @@ -354,6 +373,28 @@ namespace } private: + class TraversalGuard + { + public: + explicit TraversalGuard(Interpreter& owner) : + m_owner(owner) + { + Check(m_owner.m_traversalDepth < MaximumTraversalDepth); + ++m_owner.m_traversalDepth; + } + + ~TraversalGuard() + { + --m_owner.m_traversalDepth; + } + + TraversalGuard(TraversalGuard const&) = delete; + TraversalGuard& operator=(TraversalGuard const&) = delete; + + private: + Interpreter& m_owner; + }; + template static size_t Count(T const* vector) { @@ -686,6 +727,7 @@ namespace return m_visuals[index]; } + TraversalGuard const traversalGuard(*this); auto const* source = At(m_root.visuals(), index); // The concrete type is known only here. Everything below this point, @@ -856,6 +898,7 @@ namespace return m_shapes[index]; } + TraversalGuard const traversalGuard(*this); auto const* source = At(m_root.shapes(), index); CompositionShape result{ nullptr }; @@ -1140,6 +1183,7 @@ namespace break; } + TraversalGuard const traversalGuard(*this); m_canvasGeometryStates[index] = CanvasGeometryState::Realizing; auto result = BuildGeometryCore(index); Check(result != nullptr); @@ -1720,7 +1764,7 @@ namespace auto const uri = source->uri(); Check(uri != NullIndex); m_surfaces[index] = - winrt::Windows::UI::Xaml::Media::LoadedImageSurface::StartLoadFromUri( + XamlMedia::LoadedImageSurface::StartLoadFromUri( winrt::Windows::Foundation::Uri(GetString(uri))); break; } @@ -1741,7 +1785,7 @@ namespace winrt::put_abi(stream))); m_surfaces[index] = - winrt::Windows::UI::Xaml::Media::LoadedImageSurface::StartLoadFromStream( + XamlMedia::LoadedImageSurface::StartLoadFromStream( stream); break; } @@ -2110,10 +2154,15 @@ namespace std::vector m_animations; std::vector m_propertySets; std::vector m_controllers; + uint32_t m_traversalDepth = 0; }; } -namespace CommunityToolkit::WinUI::Lottie +#if defined(LOTTIE_RUNTIME_WINUI3) +namespace CommunityToolkit::WinUI3::Lottie +#else +namespace CommunityToolkit::WinUI2::Lottie +#endif { Visual LoadComposition(Compositor const& compositor, std::span buffer) { @@ -2141,6 +2190,7 @@ namespace CommunityToolkit::WinUI::Lottie } auto const requiredUapVersion = root->required_uap_version(); +#if !defined(LOTTIE_RUNTIME_WINUI3) if (requiredUapVersion != 0 && !winrt::Windows::Foundation::Metadata::ApiInformation::IsApiContractPresent( L"Windows.Foundation.UniversalApiContract", @@ -2148,6 +2198,12 @@ namespace CommunityToolkit::WinUI::Lottie { ThrowUnsupported(); } +#else + // Windows App SDK carries its Composition implementation with the app rather + // than exposing it through the OS UniversalApiContract. The consumer selects + // a buffer variant whose required APIs are present in its WASDK version. + static_cast(requiredUapVersion); +#endif return Interpreter(compositor, *root).Run(); } diff --git a/dlls/LottieRuntime/LottieRuntime.h b/source/LottieRuntime/LottieRuntime.h similarity index 69% rename from dlls/LottieRuntime/LottieRuntime.h rename to source/LottieRuntime/LottieRuntime.h index 76b182cd..4ecf7297 100644 --- a/dlls/LottieRuntime/LottieRuntime.h +++ b/source/LottieRuntime/LottieRuntime.h @@ -9,12 +9,24 @@ #include #include -#include +#if defined(LOTTIE_RUNTIME_WINUI2) == defined(LOTTIE_RUNTIME_WINUI3) +#error Define exactly one of LOTTIE_RUNTIME_WINUI2 or LOTTIE_RUNTIME_WINUI3. +#endif -namespace CommunityToolkit::WinUI::Lottie +#if defined(LOTTIE_RUNTIME_WINUI3) +#include +namespace CommunityToolkit::WinUI3::Lottie +{ + using namespace winrt::Microsoft::UI::Composition; +#else +#include +namespace CommunityToolkit::WinUI2::Lottie { - // LoadComposition is the direct-link C++ API. - // DLL callers use ILottieCompositionLoader from LottieRuntimeCom.h. + using namespace winrt::Windows::UI::Composition; +#endif + + // Define LOTTIE_RUNTIME_WINUI2 for Windows.UI.Composition or + // LOTTIE_RUNTIME_WINUI3 for Microsoft.UI.Composition. // Builds the composition described by a serialized Lottie composition. // // The buffer is the output of LottieGen -Language flatbuffer, or equivalently of @@ -32,14 +44,13 @@ namespace CommunityToolkit::WinUI::Lottie // // Throws winrt::hresult_error with E_INVALIDARG if the buffer is not a well formed // composition, and E_NOTIMPL if it requires a feature this build does not have. - winrt::Windows::UI::Composition::Visual LoadComposition( - winrt::Windows::UI::Composition::Compositor const& compositor, + Visual LoadComposition( + Compositor const& compositor, std::span buffer); // The property set that drives the animation. Animating its "Progress" scalar from // 0 to 1 plays the animation once. // // Returns the root Visual's property set. - winrt::Windows::UI::Composition::CompositionPropertySet ProgressPropertySet( - winrt::Windows::UI::Composition::Visual const& root); + CompositionPropertySet ProgressPropertySet(Visual const& root); } diff --git a/source/LottieRuntime/LottieRuntime.projitems b/source/LottieRuntime/LottieRuntime.projitems deleted file mode 100644 index 403fb1e2..00000000 --- a/source/LottieRuntime/LottieRuntime.projitems +++ /dev/null @@ -1,18 +0,0 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - true - 7c8b1d02-4e5f-4a76-9c3b-2f0d8e6a41b9 - - - LottieRuntime - - - - - - diff --git a/source/LottieRuntime/LottieRuntime.shproj b/source/LottieRuntime/LottieRuntime.shproj deleted file mode 100644 index fb5026ae..00000000 --- a/source/LottieRuntime/LottieRuntime.shproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - 7c8b1d02-4e5f-4a76-9c3b-2f0d8e6a41b9 - 14.0 - - - - - - - - diff --git a/dlls/LottieRuntimeTool/LottieRuntime.cpp b/source/LottieRuntime/LottieRuntimeTool.cpp similarity index 84% rename from dlls/LottieRuntimeTool/LottieRuntime.cpp rename to source/LottieRuntime/LottieRuntimeTool.cpp index ad8b8daa..cd63a9bf 100644 --- a/dlls/LottieRuntimeTool/LottieRuntime.cpp +++ b/source/LottieRuntime/LottieRuntimeTool.cpp @@ -2,16 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -// Loads a serialized Lottie composition through the registration-free COM surface. +// Loads a serialized Lottie composition through the statically linked WinUI 2 reader. #include #include #include -#include #include -#include +#include #include #include @@ -47,40 +46,6 @@ namespace return controller; } - winrt::com_ptr CreateLoader() - { - wchar_t moduleDirectory[MAX_PATH]{}; - winrt::check_bool(GetModuleFileNameW(nullptr, moduleDirectory, MAX_PATH)); - winrt::check_bool(PathRemoveFileSpecW(moduleDirectory)); - - auto const manifestPath = - std::wstring(moduleDirectory) + L"\\LottieRuntime.manifest"; - - ACTCTXW context{ sizeof(ACTCTXW) }; - context.lpSource = manifestPath.c_str(); - context.lpAssemblyDirectory = moduleDirectory; - context.dwFlags = ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID; - - auto const activationContext = CreateActCtxW(&context); - winrt::check_bool(activationContext != INVALID_HANDLE_VALUE); - - ULONG_PTR cookie{}; - winrt::check_bool(ActivateActCtx(activationContext, &cookie)); - - winrt::com_ptr loader; - auto const result = CoCreateInstance( - CLSID_LottieCompositionLoader, - nullptr, - CLSCTX_INPROC_SERVER, - __uuidof(ILottieCompositionLoader), - loader.put_void()); - - DeactivateActCtx(0, cookie); - ReleaseActCtx(activationContext); - winrt::check_hresult(result); - return loader; - } - std::vector ReadFileBytes(wchar_t const* path) { std::ifstream file(path, std::ios::binary | std::ios::ate); @@ -322,13 +287,8 @@ int wmain(int argc, wchar_t* argv[]) auto dispatcherQueueController = CreateDispatcherQueue(); auto bytes = ReadFileBytes(argv[1]); - auto loader = CreateLoader(); - Visual root{ nullptr }; - winrt::check_hresult(loader->LoadComposition( - static_cast(bytes.size()), - reinterpret_cast(bytes.data()), - winrt::guid_of(), - winrt::put_abi(root))); + Compositor compositor; + auto root = CommunityToolkit::WinUI2::Lottie::LoadComposition(compositor, bytes); switch (mode) { diff --git a/dlls/LottieRuntimeTool/LottieRuntime.vcxproj b/source/LottieRuntime/LottieRuntimeTool.vcxproj similarity index 69% rename from dlls/LottieRuntimeTool/LottieRuntime.vcxproj rename to source/LottieRuntime/LottieRuntimeTool.vcxproj index ee8b65c3..ac309879 100644 --- a/dlls/LottieRuntimeTool/LottieRuntime.vcxproj +++ b/source/LottieRuntime/LottieRuntimeTool.vcxproj @@ -28,6 +28,10 @@ Unicode 10.0.22621.0 10.0.17763.0 + $(MSBuildThisFileDirectory)..\..\external\flatbuffers\include + $(MSBuildThisFileDirectory)flatbuffers\include @@ -43,15 +47,15 @@ stdcpp20 - WIN32_LEAN_AND_MEAN;NOMINMAX;%(PreprocessorDefinitions) - $(MSBuildThisFileDirectory)..\LottieRuntime;%(AdditionalIncludeDirectories) + WIN32_LEAN_AND_MEAN;NOMINMAX;LOTTIE_RUNTIME_WINUI2;%(PreprocessorDefinitions) + $(MSBuildThisFileDirectory);$(FlatBuffersIncludeDirectory);%(AdditionalIncludeDirectories) Level4 true Sync Console - CoreMessaging.lib;ole32.lib;shlwapi.lib;user32.lib;windowsapp.lib;%(AdditionalDependencies) + CoreMessaging.lib;d2d1.lib;dxguid.lib;ole32.lib;shcore.lib;shlwapi.lib;user32.lib;windowsapp.lib;%(AdditionalDependencies) @@ -70,28 +74,9 @@ + - - - {6E0A5B4C-2F1D-4B7A-9C3E-1D5A8F0B2E47} - false - - - - - - - - - - - - - diff --git a/source/LottieRuntime/README.md b/source/LottieRuntime/README.md index 3b94437e..072017e3 100644 --- a/source/LottieRuntime/README.md +++ b/source/LottieRuntime/README.md @@ -1,108 +1,102 @@ -# LottieRuntime (managed) +# LottieRuntime -A managed runtime interpreter for Lottie animations. +Native C++/WinRT source builders for serialized Lottie animations. -`CompositionInterpreter` builds a `Windows.UI.Composition` visual tree from a -serialized composition. It is the C# counterpart of the native interpreter in -[`dlls/LottieRuntime`](../../dlls/LottieRuntime/README.md): both read the same -`.lcomp` buffer, make the same composition calls, and perform the same validation, -so an application can choose whichever one suits its language and packaging without -changing what appears on screen. +The reader builds a Composition visual tree directly with the caller's +`Compositor`. There is no COM activation layer and no runtime DLL. The consumer +compiles `LottieRuntime.cpp` with exactly one projection definition: -## How it fits in +| Composition API | Preprocessor definition | +| --- | --- | +| `Windows.UI.Composition` (WinUI 2/UWP) | `LOTTIE_RUNTIME_WINUI2` | +| `Microsoft.UI.Composition` (WinUI 3/Windows App SDK) | `LOTTIE_RUNTIME_WINUI3` | -Lottie-Windows turns a Lottie file into a `WinCompData` graph, which is a -description of a composition tree. Four consumers use that graph: +The source and header reject builds where neither or both definitions are set. +This keeps the ABI-incompatible projections separate without duplicating the +interpreter. -| Consumer | When it runs | Cost | -| --- | --- | --- | -| `Instantiator` | Runtime, in the app | Ships the whole JSON reader and translator | -| `LottieGen` code generators | Build time | No runtime cost, but one class per animation | -| `LottieRuntime` (C++) | Runtime, in the app | One interpreter shared by every animation | -| `CompositionInterpreter` (C#) | Runtime, in the app | One interpreter shared by every animation | +## Producing buffers -`CompositionSerializer` writes the graph into a FlatBuffer at build time and the -interpreter reads that buffer at runtime. The animation stays data, so it can be -swapped, downloaded or themed without a rebuild, and the amount of code in the -application does not grow with the number of animations. +```powershell +LottieGen -InputFile animation.json -Language flatbuffer -OutputFolder . +``` + +If one translation covers the requested UAP range, this writes +`Animation.lcomp`. If fallbacks are required, LottieGen writes one file per +supported range, for example: + +```text +Animation.uap14-plus.lcomp +Animation.uap10-13.lcomp +Animation.uap7-9.lcomp +``` -Note the difference from `CompositionDeserializer`, which is also managed and also -reads the same buffer: the deserializer rebuilds a `WinCompData` graph, which is the -object model that the translator and the code generators work in, while the -interpreter creates live composition objects and no intermediate model at all. The -deserializer is a tool; the interpreter is a player. +The consumer selects the file matching its Composition API contract. The data is +not tied to either projection: the same variant can be read by the WinUI 2 or +WinUI 3 builder when that projection provides the APIs required by the variant. -## Producing a buffer +## Incorporating the reader +Add `LottieRuntime.cpp` to the native project, define the matching projection +flag, and add `LottieRuntime/` and `LottieRuntime/flatbuffers/include/` to its +include paths. Build as C++20 and link: + +```text +d2d1.lib +dxguid.lib +shcore.lib +shlwapi.lib +windowsapp.lib ``` -LottieGen -InputFile animation.json -Language flatbuffer -OutputFolder . + +Include `LottieRuntime.h`, create the compositor in the host, and pass it to the +reader: + +```cpp +#include + +auto bytes = ReadFileBytes(L"Animation.lcomp"); +auto root = CommunityToolkit::WinUI2::Lottie::LoadComposition(compositor, bytes); ``` -This writes `Animation.lcomp`. The format is described by -[`source/LottieFlatbuffer/lottie_comp.fbs`](../LottieFlatbuffer/lottie_comp.fbs). +The same projection definition must be visible to every file that includes +`LottieRuntime.h`. WinUI 3 builds use the corresponding +`CommunityToolkit::WinUI3::Lottie` namespace. The distinct namespaces allow both +projection-specific objects to be linked into the same binary. + +`LoadComposition` returns the root `Visual`. `ProgressPropertySet(root)` returns +the Composition property set containing the `Progress` scalar. XAML integration +and `IAnimatedVisual` wrapping remain the caller's responsibility. + +## Validation and safety + +The reader verifies the FlatBuffer identifier and structure, range-checks every +node index, rejects unsupported schema or API-contract versions, detects geometry +cycles, and limits recursive visual, shape, and geometry traversal to 128 levels. +Malformed input throws `winrt::hresult_invalid_argument`; unsupported content +throws `winrt::hresult_not_implemented`. -## Using it +## Validation tool -```csharp -var bytes = File.ReadAllBytes("Animation.lcomp"); -var root = CompositionInterpreter.LoadComposition(compositor, bytes); +`LottieRuntimeTool.vcxproj` builds `LottieRuntime.exe`, a WinUI 2 validation and +inspection harness that compiles the reader directly. -ElementCompositionPreview.SetElementChildVisual(element, root); +```powershell +& "C:\Program Files\Microsoft Visual Studio\18\Professional\Common7\Tools\Launch-VsDevShell.ps1" -SkipAutomaticLocation +msbuild .\source\LottieRuntime\LottieRuntimeTool.vcxproj /p:Platform=x64 /p:Configuration=Release +``` + +The executable is written to: -// Drive the animation by setting Progress between 0 and 1. -CompositionInterpreter.ProgressPropertySet(root).InsertScalar("Progress", 0.5f); +```text +source\LottieRuntime\x64\Release\LottieRuntime.exe ``` -The `Compositor` must belong to the calling thread, which must own a -`DispatcherQueue` that outlives the returned visual tree. - -## Design - -**One function per category, not per type.** A node's concrete type is known only -inside the function that creates it. The realization caches hold `Visual`, -`CompositionShape`, `CompositionBrush`, `CompositionGeometry` and so on, and -everything the interpreter does afterwards is declared on those types, so there is -no point at which it has to ask what a node actually is. The state that every -`CompositionObject` has is applied by one function that takes `CompositionObject`. - -**Runtime cost second.** Each node and string is realized at most once. Lookups are -bounds-checked array indexing into the buffer's node vectors, because the caches are -indexed identically to those vectors. The buffer is read in place. - -**Untrusted input.** A buffer may have come from a file or a download. It is checked -for the `LCMP` identifier, run through the FlatBuffers verifier, and every index read -out of it is range checked. Canvas geometries are checked for cycles and paths for -figures that do not begin and end. A malformed buffer is reported as a -`FlatBufferFormatException` and never reads outside the buffer. A buffer that needs a -newer schema, or a Universal API contract that this version of Windows does not have, -is reported as a `NotSupportedException` before any object is created. - -## Dependencies - -`Windows.UI.Composition`, Win2D for path geometry and the two effects that the -translator emits, and `Google.FlatBuffers` for the generated schema binding. The -native interpreter avoids Win2D by implementing the two Direct2D interop interfaces -itself; that is not available to managed code, so a consumer of this interpreter -takes the Win2D dependency, exactly as a consumer of `Instantiator` or of -`LottieGen`'s C# output already does. - -## Incorporate it in a build - -`LottieRuntime.projitems` is a shared project. Import it into the application -project and add a package reference to `Google.FlatBuffers`. The runtime imports -the generated schema binding and format constants without bringing in the -tool-side serializer, deserializer, or `WinCompData` model. - -Define `WINAPPSDK` when building for the Windows App SDK, which switches the -interpreter from `Windows.UI.Composition` to `Microsoft.UI.Composition`. Define -`PUBLIC_LottieRuntime` to make the type public. - -## Tests - -`tests/CompDataFlatbuffer.Tests` dumps the interpreted tree in the same canonical -text as the `WinCompData` graph that the buffer was written from and asserts that the -two are equal, for every animation in the sample corpus and for a synthetic graph -that contains one of every node type. The same tests check that malformed, truncated -and corrupted buffers are rejected. The composition APIs only exist on Windows, so -the tests run against stand-ins for them, which is what makes the interpreter -testable on any platform. +It validates a buffer by default, prints the interpreted Composition hierarchy +with `--dump`, or displays and animates it with `--show`: + +```powershell +.\source\LottieRuntime\x64\Release\LottieRuntime.exe Animation.lcomp +.\source\LottieRuntime\x64\Release\LottieRuntime.exe Animation.lcomp --dump +.\source\LottieRuntime\x64\Release\LottieRuntime.exe Animation.lcomp --show +``` diff --git a/tests/CompDataFlatbuffer.Tests/CompDataFlatbuffer.Tests.csproj b/tests/CompDataFlatbuffer.Tests/CompDataFlatbuffer.Tests.csproj index 03c5c47e..5eaab357 100644 --- a/tests/CompDataFlatbuffer.Tests/CompDataFlatbuffer.Tests.csproj +++ b/tests/CompDataFlatbuffer.Tests/CompDataFlatbuffer.Tests.csproj @@ -21,7 +21,6 @@ - diff --git a/tests/CompDataFlatbuffer.Tests/CompositionApiStandIns.cs b/tests/CompDataFlatbuffer.Tests/CompositionApiStandIns.cs deleted file mode 100644 index 59bfc1e1..00000000 --- a/tests/CompDataFlatbuffer.Tests/CompositionApiStandIns.cs +++ /dev/null @@ -1,1112 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -// Stand-ins for the Windows.UI.Composition, Windows.UI.Xaml.Media and Win2D APIs -// that CompositionInterpreter is written against. -// -// The interpreter's whole job is to make the right sequence of calls into those -// APIs, and those APIs are only present on Windows. Rather than testing the -// interpreter by looking at pixels on a Windows machine, these types stand in for -// the real ones: they are declared in the real namespaces with the real names and -// the members that the interpreter uses, and they record what was done to them so -// that the resulting tree can be dumped and compared against the WinCompData graph -// that produced the buffer. -// -// They are deliberately dumb. They contain no logic beyond recording, so a test -// failure is always a fault in the interpreter rather than in the stand-ins. The -// same technique is used by tests/Mocks.cs, which mocks Win2D so that LottieGen's -// generated code can be compiled off Windows. -// -// Two details are modelled rather than merely recorded, because the dump depends -// on them: -// -// * A property that has never been set is distinguishable from one that has been -// set to its default value, which is what makes the dump comparable with the -// WinCompData dump. That is why the properties are nullable. -// * StartAnimation creates an AnimationController for every animation that is not -// an ExpressionAnimation, which is what Windows.UI.Composition does and what -// WinCompData models. -#nullable enable - -using System; -using System.Collections.Generic; -using System.IO; -using System.Numerics; -using Windows.Graphics.Effects; -using Windows.Storage.Streams; -using Windows.UI.Composition; -using Mgc = Microsoft.Graphics.Canvas; -using Mgcg = Microsoft.Graphics.Canvas.Geometry; - -namespace Windows.UI -{ - struct Color - { - public byte A { get; private set; } - - public byte R { get; private set; } - - public byte G { get; private set; } - - public byte B { get; private set; } - - public static Color FromArgb(byte a, byte r, byte g, byte b) - => new Color { A = a, R = r, G = g, B = b }; - } -} - -namespace Windows.Foundation.Metadata -{ - static class ApiInformation - { - // The version of the API contract that the stand-ins pretend to implement. - // It is well beyond any version that the translator asks for, so that a - // composition from the corpus always loads, and a test that wants the check - // to fail asks for a version that no version of Windows will ever have. - public const ushort UniversalApiContractVersion = 20; - - public static bool IsApiContractPresent(string contractName, ushort majorVersion) - => contractName == "Windows.Foundation.UniversalApiContract" && - majorVersion <= UniversalApiContractVersion; - } -} - -namespace Windows.Graphics.Effects -{ - interface IGraphicsEffectSource - { - } - - interface IGraphicsEffect : IGraphicsEffectSource - { - } -} - -namespace Windows.Storage.Streams -{ - interface IBuffer - { - byte[] Bytes { get; } - } - - interface IRandomAccessStream - { - byte[] Bytes { get; } - } - - sealed class ByteArrayBuffer : IBuffer - { - internal ByteArrayBuffer(byte[] bytes) => Bytes = bytes; - - public byte[] Bytes { get; } - } - - sealed class RandomAccessStream : IRandomAccessStream - { - internal RandomAccessStream(byte[] bytes) => Bytes = bytes; - - public byte[] Bytes { get; } - } - - // Stands in for the stream that a byte array is wrapped in on the way to - // LoadedImageSurface. The bytes are carried through unchanged so that the dump - // can show what the surface was given. - sealed class ByteArrayStream : MemoryStream - { - internal ByteArrayStream(byte[] bytes) - : base(bytes, writable: false) - { - Bytes = bytes; - } - - public byte[] Bytes { get; } - } -} - -namespace System.Runtime.InteropServices.WindowsRuntime -{ - static class WindowsRuntimeBufferExtensions - { - public static IBuffer AsBuffer(this byte[] source) => new ByteArrayBuffer(source); - - public static Stream AsStream(this IBuffer source) => new ByteArrayStream(source.Bytes); - } -} - -namespace System.IO -{ - static class WindowsRuntimeStreamExtensions - { - public static IRandomAccessStream AsRandomAccessStream(this Stream source) - => new RandomAccessStream(((ByteArrayStream)source).Bytes); - } -} - -namespace Microsoft.Graphics.Canvas -{ - // Values match CommunityToolkit.WinUI.Lottie.WinCompData.Mgc.CanvasComposite. - enum CanvasComposite - { - SourceOver, - DestinationOver, - SourceIn, - DestinationIn, - SourceOut, - DestinationOut, - SourceAtop, - DestinationAtop, - Xor, - Add, - Copy, - BoundedCopy, - MaskInvert, - } -} - -namespace Microsoft.Graphics.Canvas.Effects -{ - sealed class CompositeEffect : IGraphicsEffect - { - public Mgc.CanvasComposite Mode { get; set; } - - public IList Sources { get; } = new List(); - } - - sealed class GaussianBlurEffect : IGraphicsEffect - { - public float BlurAmount { get; set; } - - public IGraphicsEffectSource? Source { get; set; } - } -} - -namespace Microsoft.Graphics.Canvas.Geometry -{ - // Values match CommunityToolkit.WinUI.Lottie.WinCompData.Mgcg.CanvasFigureLoop. - enum CanvasFigureLoop - { - Open, - Closed, - } - - // Values match CommunityToolkit.WinUI.Lottie.WinCompData.Mgcg.CanvasFilledRegionDetermination. - enum CanvasFilledRegionDetermination - { - Alternate, - Winding, - } - - // Values match CommunityToolkit.WinUI.Lottie.WinCompData.Mgcg.CanvasGeometryCombine. - enum CanvasGeometryCombine - { - Union, - Exclude, - Intersect, - Xor, - } - - // The shape of a geometry is recorded rather than rasterized. The nested types - // mirror the ones in WinCompData.Mgcg.CanvasGeometry so that the two can be - // compared. - abstract class CanvasGeometry - { - public static CanvasGeometry CreateEllipse(object? device, float x, float y, float radiusX, float radiusY) - => new Ellipse(x, y, radiusX, radiusY); - - public static CanvasGeometry CreateRoundedRectangle( - object? device, float x, float y, float w, float h, float radiusX, float radiusY) - => new RoundedRectangle(x, y, w, h, radiusX, radiusY); - - public static CanvasGeometry CreateGroup( - object? device, CanvasGeometry[] geometries, CanvasFilledRegionDetermination filledRegionDetermination) - => new Group(geometries, filledRegionDetermination); - - public static CanvasGeometry CreatePath(CanvasPathBuilder builder) - => new Path(builder.Commands, builder.FilledRegionDetermination); - - public CanvasGeometry CombineWith( - CanvasGeometry other, Matrix3x2 matrix, CanvasGeometryCombine combineMode) - => new Combination(this, other, matrix, combineMode); - - public CanvasGeometry Transform(Matrix3x2 transformMatrix) - => new TransformedGeometry(this, transformMatrix); - - public sealed class Combination : CanvasGeometry - { - internal Combination(CanvasGeometry a, CanvasGeometry b, Matrix3x2 matrix, CanvasGeometryCombine combineMode) - => (A, B, Matrix, CombineMode) = (a, b, matrix, combineMode); - - public CanvasGeometry A { get; } - - public CanvasGeometry B { get; } - - public Matrix3x2 Matrix { get; } - - public CanvasGeometryCombine CombineMode { get; } - } - - public sealed class TransformedGeometry : CanvasGeometry - { - internal TransformedGeometry(CanvasGeometry sourceGeometry, Matrix3x2 transformMatrix) - => (SourceGeometry, TransformMatrix) = (sourceGeometry, transformMatrix); - - public CanvasGeometry SourceGeometry { get; } - - public Matrix3x2 TransformMatrix { get; } - } - - public sealed class Group : CanvasGeometry - { - internal Group(CanvasGeometry[] geometries, CanvasFilledRegionDetermination filledRegionDetermination) - => (Geometries, FilledRegionDetermination) = (geometries, filledRegionDetermination); - - public CanvasGeometry[] Geometries { get; } - - public CanvasFilledRegionDetermination FilledRegionDetermination { get; } - } - - public sealed class Path : CanvasGeometry - { - internal Path( - IReadOnlyList commands, - CanvasFilledRegionDetermination filledRegionDetermination) - => (Commands, FilledRegionDetermination) = (commands, filledRegionDetermination); - - public IReadOnlyList Commands { get; } - - public CanvasFilledRegionDetermination FilledRegionDetermination { get; } - } - - public sealed class Ellipse : CanvasGeometry - { - internal Ellipse(float x, float y, float radiusX, float radiusY) - => (X, Y, RadiusX, RadiusY) = (x, y, radiusX, radiusY); - - public float X { get; } - - public float Y { get; } - - public float RadiusX { get; } - - public float RadiusY { get; } - } - - public sealed class RoundedRectangle : CanvasGeometry - { - internal RoundedRectangle(float x, float y, float w, float h, float radiusX, float radiusY) - => (X, Y, W, H, RadiusX, RadiusY) = (x, y, w, h, radiusX, radiusY); - - public float X { get; } - - public float Y { get; } - - public float W { get; } - - public float H { get; } - - public float RadiusX { get; } - - public float RadiusY { get; } - } - } - - sealed class CanvasPathBuilder : IDisposable - { - readonly List _commands = new List(); - - public CanvasPathBuilder(object? device) - { - } - - internal IReadOnlyList Commands => _commands; - - internal CanvasFilledRegionDetermination FilledRegionDetermination { get; private set; } - - public void SetFilledRegionDetermination(CanvasFilledRegionDetermination value) - => FilledRegionDetermination = value; - - public void BeginFigure(Vector2 startPoint) => _commands.Add(new Command.BeginFigure(startPoint)); - - public void EndFigure(CanvasFigureLoop figureLoop) => _commands.Add(new Command.EndFigure(figureLoop)); - - public void AddLine(Vector2 endPoint) => _commands.Add(new Command.AddLine(endPoint)); - - public void AddCubicBezier(Vector2 controlPoint1, Vector2 controlPoint2, Vector2 endPoint) - => _commands.Add(new Command.AddCubicBezier(controlPoint1, controlPoint2, endPoint)); - - public void Dispose() - { - } - - internal abstract class Command - { - internal sealed class BeginFigure : Command - { - internal BeginFigure(Vector2 startPoint) => StartPoint = startPoint; - - public Vector2 StartPoint { get; } - } - - internal sealed class EndFigure : Command - { - internal EndFigure(CanvasFigureLoop figureLoop) => FigureLoop = figureLoop; - - public CanvasFigureLoop FigureLoop { get; } - } - - internal sealed class AddLine : Command - { - internal AddLine(Vector2 endPoint) => EndPoint = endPoint; - - public Vector2 EndPoint { get; } - } - - internal sealed class AddCubicBezier : Command - { - internal AddCubicBezier(Vector2 controlPoint1, Vector2 controlPoint2, Vector2 endPoint) - => (ControlPoint1, ControlPoint2, EndPoint) = (controlPoint1, controlPoint2, endPoint); - - public Vector2 ControlPoint1 { get; } - - public Vector2 ControlPoint2 { get; } - - public Vector2 EndPoint { get; } - } - } - } -} - -namespace Windows.UI.Xaml.Media -{ - // The two loaded image surfaces are separate types here, unlike in the real API - // where one type is returned by both factories, so that the dump can tell them - // apart in the same way that the WinCompData dump does. - abstract class LoadedImageSurface : ICompositionSurface - { - public static LoadedImageSurface StartLoadFromUri(Uri uri) => new LoadedImageSurfaceFromUri(uri); - - public static LoadedImageSurface StartLoadFromStream(IRandomAccessStream stream) - => new LoadedImageSurfaceFromStream(stream.Bytes); - } - - sealed class LoadedImageSurfaceFromUri : LoadedImageSurface - { - internal LoadedImageSurfaceFromUri(Uri uri) => Uri = uri; - - public Uri Uri { get; } - } - - sealed class LoadedImageSurfaceFromStream : LoadedImageSurface - { - internal LoadedImageSurfaceFromStream(byte[] bytes) => Bytes = bytes; - - public byte[] Bytes { get; } - } -} - -namespace Windows.UI.Composition -{ - // Values match CommunityToolkit.WinUI.Lottie.WinCompData.CompositionBorderMode. - enum CompositionBorderMode - { - Inherit = 0, - Soft = 1, - Hard = 2, - } - - // Values match CommunityToolkit.WinUI.Lottie.WinCompData.CompositionStrokeCap. - enum CompositionStrokeCap - { - Flat, - Square, - Round, - Triangle, - } - - // Values match CommunityToolkit.WinUI.Lottie.WinCompData.CompositionStrokeLineJoin. - enum CompositionStrokeLineJoin - { - Miter, - Bevel, - Round, - MiterOrBevel, - } - - // Values match CommunityToolkit.WinUI.Lottie.WinCompData.CompositionGradientExtendMode. - enum CompositionGradientExtendMode - { - Clamp = 0, - Wrap = 1, - Mirror = 2, - } - - // Values match CommunityToolkit.WinUI.Lottie.WinCompData.CompositionColorSpace. - enum CompositionColorSpace - { - Auto = 0, - Hsl = 1, - Rgb = 2, - HslLinear = 3, - RgbLinear = 4, - } - - // Values match CommunityToolkit.WinUI.Lottie.WinCompData.CompositionMappingMode. - enum CompositionMappingMode - { - Absolute = 0, - Relative = 1, - } - - // Values match CommunityToolkit.WinUI.Lottie.WinCompData.CompositionDropShadowSourcePolicy. - enum CompositionDropShadowSourcePolicy - { - Default = 0, - InheritFromVisualContent = 1, - } - - interface ICompositionSurface - { - } - - abstract class CompositionObject - { - readonly List _animators = new List(); - - private protected CompositionObject() - => Properties = this as CompositionPropertySet ?? new CompositionPropertySet(this); - - public string? Comment { get; set; } - - public CompositionPropertySet Properties { get; } - - internal IReadOnlyList Animators => _animators; - - public void StartAnimation(string propertyName, CompositionAnimation animation) - => StartAnimation(propertyName, animation, null); - - public void StartAnimation( - string propertyName, CompositionAnimation animation, AnimationController? animationController) - { - // A controller comes into existence with the animation, unless the - // animation is an expression, which is driven by its inputs rather than by - // a clock and so has nothing to control. - var controller = animationController ?? - (animation is ExpressionAnimation ? null : new AnimationController(this, propertyName)); - - _animators.Add(new Animator(propertyName, animation, controller)); - } - - public AnimationController? TryGetAnimationController(string propertyName) - { - foreach (var animator in _animators) - { - if (animator.AnimatedProperty == propertyName) - { - return animator.Controller; - } - } - - return null; - } - - internal sealed class Animator - { - internal Animator(string animatedProperty, CompositionAnimation animation, AnimationController? controller) - => (AnimatedProperty, Animation, Controller) = (animatedProperty, animation, controller); - - public string AnimatedProperty { get; } - - public CompositionAnimation Animation { get; } - - public AnimationController? Controller { get; } - } - } - - sealed class AnimationController : CompositionObject - { - internal AnimationController(CompositionObject targetObject, string targetProperty) - => (TargetObject, TargetProperty) = (targetObject, targetProperty); - - internal AnimationController() - { - } - - public bool IsCustom => TargetObject is null; - - public CompositionObject? TargetObject { get; } - - public string? TargetProperty { get; } - - public bool IsPaused { get; private set; } - - public void Pause() => IsPaused = true; - } - - enum PropertySetValueType - { - Color, - Scalar, - Vector2, - Vector3, - Vector4, - } - - sealed class CompositionPropertySet : CompositionObject - { - // A SortedDictionary with the default comparer, exactly as WinCompData uses, - // so that the dump of a property set is in the same order on both sides. - readonly SortedDictionary _names = - new SortedDictionary(); - - readonly Dictionary _values = new Dictionary(); - - internal CompositionPropertySet(CompositionObject? owner) => Owner = owner; - - public CompositionObject? Owner { get; } - - internal IReadOnlyDictionary Names => _names; - - public void InsertColor(string propertyName, UI.Color value) - => Insert(propertyName, PropertySetValueType.Color, value); - - public void InsertScalar(string propertyName, float value) - => Insert(propertyName, PropertySetValueType.Scalar, value); - - public void InsertVector2(string propertyName, Vector2 value) - => Insert(propertyName, PropertySetValueType.Vector2, value); - - public void InsertVector3(string propertyName, Vector3 value) - => Insert(propertyName, PropertySetValueType.Vector3, value); - - public void InsertVector4(string propertyName, Vector4 value) - => Insert(propertyName, PropertySetValueType.Vector4, value); - - internal object GetValue(string propertyName) => _values[propertyName]; - - void Insert(string propertyName, PropertySetValueType type, object value) - { - _names[propertyName] = type; - _values[propertyName] = value; - } - } - - sealed class CompositionPath - { - public CompositionPath(Mgcg.CanvasGeometry source) => Source = source; - - public Mgcg.CanvasGeometry Source { get; } - } - - sealed class CompositionEffectSourceParameter : IGraphicsEffectSource - { - public CompositionEffectSourceParameter(string name) => Name = name; - - public string Name { get; } - } - - abstract class Visual : CompositionObject - { - public CompositionBorderMode? BorderMode { get; set; } - - public Vector3? CenterPoint { get; set; } - - public CompositionClip? Clip { get; set; } - - public bool? IsVisible { get; set; } - - public Vector3? Offset { get; set; } - - public float? Opacity { get; set; } - - public float? RotationAngleInDegrees { get; set; } - - public Vector3? RotationAxis { get; set; } - - public Vector3? Scale { get; set; } - - public Vector2? Size { get; set; } - - public Matrix4x4? TransformMatrix { get; set; } - } - - sealed class VisualCollection - { - readonly List _visuals = new List(); - - internal IReadOnlyList Visuals => _visuals; - - public void InsertAtTop(Visual visual) => _visuals.Add(visual); - } - - class ContainerVisual : Visual - { - public VisualCollection Children { get; } = new VisualCollection(); - } - - sealed class ShapeVisual : ContainerVisual - { - public IList Shapes { get; } = new List(); - - public CompositionViewBox? ViewBox { get; set; } - } - - sealed class SpriteVisual : ContainerVisual - { - public CompositionBrush? Brush { get; set; } - - public CompositionShadow? Shadow { get; set; } - } - - sealed class LayerVisual : ContainerVisual - { - public CompositionShadow? Shadow { get; set; } - } - - abstract class CompositionShape : CompositionObject - { - public Vector2? CenterPoint { get; set; } - - public Vector2? Offset { get; set; } - - public float? RotationAngleInDegrees { get; set; } - - public Vector2? Scale { get; set; } - - public Matrix3x2? TransformMatrix { get; set; } - } - - sealed class CompositionContainerShape : CompositionShape - { - public IList Shapes { get; } = new List(); - } - - sealed class CompositionSpriteShape : CompositionShape - { - public CompositionBrush? FillBrush { get; set; } - - public CompositionBrush? StrokeBrush { get; set; } - - public CompositionGeometry? Geometry { get; set; } - - public bool? IsStrokeNonScaling { get; set; } - - public float? StrokeDashOffset { get; set; } - - public IList StrokeDashArray { get; } = new List(); - - public CompositionStrokeCap? StrokeDashCap { get; set; } - - public CompositionStrokeCap? StrokeStartCap { get; set; } - - public CompositionStrokeCap? StrokeEndCap { get; set; } - - public CompositionStrokeLineJoin? StrokeLineJoin { get; set; } - - public float? StrokeMiterLimit { get; set; } - - public float? StrokeThickness { get; set; } - } - - abstract class CompositionGeometry : CompositionObject - { - public float? TrimStart { get; set; } - - public float? TrimEnd { get; set; } - - public float? TrimOffset { get; set; } - } - - sealed class CompositionPathGeometry : CompositionGeometry - { - public CompositionPath? Path { get; set; } - } - - sealed class CompositionRectangleGeometry : CompositionGeometry - { - public Vector2? Offset { get; set; } - - public Vector2? Size { get; set; } - } - - sealed class CompositionRoundedRectangleGeometry : CompositionGeometry - { - public Vector2? Offset { get; set; } - - public Vector2? Size { get; set; } - - public Vector2? CornerRadius { get; set; } - } - - sealed class CompositionEllipseGeometry : CompositionGeometry - { - public Vector2? Center { get; set; } - - public Vector2? Radius { get; set; } - } - - abstract class CompositionBrush : CompositionObject - { - } - - sealed class CompositionColorBrush : CompositionBrush - { - public UI.Color? Color { get; set; } - } - - abstract class CompositionGradientBrush : CompositionBrush - { - public Vector2? AnchorPoint { get; set; } - - public Vector2? CenterPoint { get; set; } - - public IList ColorStops { get; } = new List(); - - public CompositionGradientExtendMode? ExtendMode { get; set; } - - public CompositionColorSpace? InterpolationSpace { get; set; } - - public CompositionMappingMode? MappingMode { get; set; } - - public Vector2? Offset { get; set; } - - public float? RotationAngleInDegrees { get; set; } - - public Vector2? Scale { get; set; } - - public Matrix3x2? TransformMatrix { get; set; } - } - - sealed class CompositionLinearGradientBrush : CompositionGradientBrush - { - public Vector2? StartPoint { get; set; } - - public Vector2? EndPoint { get; set; } - } - - sealed class CompositionRadialGradientBrush : CompositionGradientBrush - { - public Vector2? EllipseCenter { get; set; } - - public Vector2? EllipseRadius { get; set; } - - public Vector2? GradientOriginOffset { get; set; } - } - - sealed class CompositionMaskBrush : CompositionBrush - { - public CompositionBrush? Source { get; set; } - - public CompositionBrush? Mask { get; set; } - } - - sealed class CompositionSurfaceBrush : CompositionBrush - { - internal CompositionSurfaceBrush(ICompositionSurface? surface) => Surface = surface; - - public ICompositionSurface? Surface { get; set; } - } - - sealed class CompositionEffectBrush : CompositionBrush - { - readonly Dictionary _sources = new Dictionary(); - readonly CompositionEffectFactory _factory; - - internal CompositionEffectBrush(CompositionEffectFactory factory) => _factory = factory; - - public void SetSourceParameter(string name, CompositionBrush source) => _sources[name] = source; - - internal CompositionEffectFactory GetEffectFactory() => _factory; - - internal CompositionBrush? GetSourceParameter(string name) - => _sources.TryGetValue(name, out var result) ? result : null; - } - - sealed class CompositionEffectFactory : CompositionObject - { - internal CompositionEffectFactory(IGraphicsEffect effect) => Effect = effect; - - internal IGraphicsEffect Effect { get; } - - public CompositionEffectBrush CreateBrush() => new CompositionEffectBrush(this); - } - - sealed class CompositionColorGradientStop : CompositionObject - { - public UI.Color? Color { get; set; } - - public float? Offset { get; set; } - } - - sealed class CompositionViewBox : CompositionObject - { - public Vector2? Size { get; set; } - } - - abstract class CompositionClip : CompositionObject - { - public Vector2? CenterPoint { get; set; } - - public Vector2? Scale { get; set; } - } - - sealed class InsetClip : CompositionClip - { - public float? LeftInset { get; set; } - - public float? RightInset { get; set; } - - public float? TopInset { get; set; } - - public float? BottomInset { get; set; } - } - - sealed class CompositionGeometricClip : CompositionClip - { - public CompositionGeometry? Geometry { get; set; } - } - - abstract class CompositionShadow : CompositionObject - { - } - - sealed class DropShadow : CompositionShadow - { - public float? BlurRadius { get; set; } - - public UI.Color? Color { get; set; } - - public CompositionBrush? Mask { get; set; } - - public Vector3? Offset { get; set; } - - public float? Opacity { get; set; } - - public CompositionDropShadowSourcePolicy? SourcePolicy { get; set; } - } - - sealed class CompositionVisualSurface : CompositionObject, ICompositionSurface - { - public Visual? SourceVisual { get; set; } - - public Vector2? SourceSize { get; set; } - - public Vector2? SourceOffset { get; set; } - } - - abstract class CompositionEasingFunction : CompositionObject - { - } - - sealed class LinearEasingFunction : CompositionEasingFunction - { - } - - sealed class CubicBezierEasingFunction : CompositionEasingFunction - { - internal CubicBezierEasingFunction(Vector2 controlPoint1, Vector2 controlPoint2) - => (ControlPoint1, ControlPoint2) = (controlPoint1, controlPoint2); - - public Vector2 ControlPoint1 { get; } - - public Vector2 ControlPoint2 { get; } - } - - sealed class StepEasingFunction : CompositionEasingFunction - { - public int? StepCount { get; set; } - - public int? InitialStep { get; set; } - - public int? FinalStep { get; set; } - - public bool? IsInitialStepSingleFrame { get; set; } - - public bool? IsFinalStepSingleFrame { get; set; } - } - - abstract class CompositionAnimation : CompositionObject - { - readonly SortedDictionary _referenceParameters = - new SortedDictionary(StringComparer.Ordinal); - - public string? Target { get; set; } - - internal IReadOnlyDictionary ReferenceParameters => _referenceParameters; - - public void SetReferenceParameter(string key, CompositionObject compositionObject) - => _referenceParameters[key] = compositionObject; - } - - sealed class ExpressionAnimation : CompositionAnimation - { - internal ExpressionAnimation(string expression) => Expression = expression; - - public string Expression { get; } - } - - abstract class KeyFrameAnimation : CompositionAnimation - { - // Key frames are held in progress order and one deep per progress, which is - // what Windows.UI.Composition does and what makes the dump canonical. - readonly SortedList _keyFrames = new SortedList(); - - public TimeSpan Duration { get; set; } - - internal IEnumerable KeyFrames => _keyFrames.Values; - - public void InsertExpressionKeyFrame(float progress, string expression, CompositionEasingFunction? easing) - => Insert(new ExpressionKeyFrame(progress, easing, expression)); - - private protected void Insert(KeyFrame keyFrame) => _keyFrames[keyFrame.Progress] = keyFrame; - - internal abstract class KeyFrame - { - private protected KeyFrame(float progress, CompositionEasingFunction? easing) - => (Progress, Easing) = (progress, easing); - - public float Progress { get; } - - public CompositionEasingFunction? Easing { get; } - } - - internal sealed class ExpressionKeyFrame : KeyFrame - { - internal ExpressionKeyFrame(float progress, CompositionEasingFunction? easing, string expression) - : base(progress, easing) - => Expression = expression; - - public string Expression { get; } - } - - internal sealed class ValueKeyFrame : KeyFrame - { - internal ValueKeyFrame(float progress, CompositionEasingFunction? easing, object value) - : base(progress, easing) - => Value = value; - - public object Value { get; } - } - } - - sealed class ScalarKeyFrameAnimation : KeyFrameAnimation - { - public void InsertKeyFrame(float progress, float value, CompositionEasingFunction? easing) - => Insert(new ValueKeyFrame(progress, easing, value)); - } - - sealed class Vector2KeyFrameAnimation : KeyFrameAnimation - { - public void InsertKeyFrame(float progress, Vector2 value, CompositionEasingFunction? easing) - => Insert(new ValueKeyFrame(progress, easing, value)); - } - - sealed class Vector3KeyFrameAnimation : KeyFrameAnimation - { - public void InsertKeyFrame(float progress, Vector3 value, CompositionEasingFunction? easing) - => Insert(new ValueKeyFrame(progress, easing, value)); - } - - sealed class Vector4KeyFrameAnimation : KeyFrameAnimation - { - public void InsertKeyFrame(float progress, Vector4 value, CompositionEasingFunction? easing) - => Insert(new ValueKeyFrame(progress, easing, value)); - } - - sealed class ColorKeyFrameAnimation : KeyFrameAnimation - { - public CompositionColorSpace? InterpolationColorSpace { get; set; } - - public void InsertKeyFrame(float progress, UI.Color value, CompositionEasingFunction? easing) - => Insert(new ValueKeyFrame(progress, easing, value)); - } - - sealed class BooleanKeyFrameAnimation : KeyFrameAnimation - { - public void InsertKeyFrame(float progress, bool value) - => Insert(new ValueKeyFrame(progress, null, value)); - } - - sealed class PathKeyFrameAnimation : KeyFrameAnimation - { - public void InsertKeyFrame(float progress, CompositionPath value, CompositionEasingFunction? easing) - => Insert(new ValueKeyFrame(progress, easing, value)); - } - - sealed class Compositor - { - public ContainerVisual CreateContainerVisual() => new ContainerVisual(); - - public ShapeVisual CreateShapeVisual() => new ShapeVisual(); - - public SpriteVisual CreateSpriteVisual() => new SpriteVisual(); - - public LayerVisual CreateLayerVisual() => new LayerVisual(); - - public CompositionContainerShape CreateContainerShape() => new CompositionContainerShape(); - - public CompositionSpriteShape CreateSpriteShape() => new CompositionSpriteShape(); - - public CompositionPathGeometry CreatePathGeometry() => new CompositionPathGeometry(); - - public CompositionRectangleGeometry CreateRectangleGeometry() => new CompositionRectangleGeometry(); - - public CompositionRoundedRectangleGeometry CreateRoundedRectangleGeometry() - => new CompositionRoundedRectangleGeometry(); - - public CompositionEllipseGeometry CreateEllipseGeometry() => new CompositionEllipseGeometry(); - - public CompositionColorBrush CreateColorBrush() => new CompositionColorBrush(); - - public CompositionLinearGradientBrush CreateLinearGradientBrush() => new CompositionLinearGradientBrush(); - - public CompositionRadialGradientBrush CreateRadialGradientBrush() => new CompositionRadialGradientBrush(); - - public CompositionMaskBrush CreateMaskBrush() => new CompositionMaskBrush(); - - public CompositionSurfaceBrush CreateSurfaceBrush(ICompositionSurface? surface) - => new CompositionSurfaceBrush(surface); - - public CompositionEffectFactory CreateEffectFactory(IGraphicsEffect effect) - => new CompositionEffectFactory(effect); - - public CompositionColorGradientStop CreateColorGradientStop() => new CompositionColorGradientStop(); - - public CompositionViewBox CreateViewBox() => new CompositionViewBox(); - - public InsetClip CreateInsetClip() => new InsetClip(); - - public CompositionGeometricClip CreateGeometricClip() => new CompositionGeometricClip(); - - public DropShadow CreateDropShadow() => new DropShadow(); - - public CompositionVisualSurface CreateVisualSurface() => new CompositionVisualSurface(); - - public CompositionPropertySet CreatePropertySet() => new CompositionPropertySet(null); - - public AnimationController CreateAnimationController() => new AnimationController(); - - public LinearEasingFunction CreateLinearEasingFunction() => new LinearEasingFunction(); - - public CubicBezierEasingFunction CreateCubicBezierEasingFunction(Vector2 controlPoint1, Vector2 controlPoint2) - => new CubicBezierEasingFunction(controlPoint1, controlPoint2); - - public StepEasingFunction CreateStepEasingFunction() => new StepEasingFunction(); - - public ExpressionAnimation CreateExpressionAnimation(string expression) => new ExpressionAnimation(expression); - - public ScalarKeyFrameAnimation CreateScalarKeyFrameAnimation() => new ScalarKeyFrameAnimation(); - - public Vector2KeyFrameAnimation CreateVector2KeyFrameAnimation() => new Vector2KeyFrameAnimation(); - - public Vector3KeyFrameAnimation CreateVector3KeyFrameAnimation() => new Vector3KeyFrameAnimation(); - - public Vector4KeyFrameAnimation CreateVector4KeyFrameAnimation() => new Vector4KeyFrameAnimation(); - - public ColorKeyFrameAnimation CreateColorKeyFrameAnimation() => new ColorKeyFrameAnimation(); - - public BooleanKeyFrameAnimation CreateBooleanKeyFrameAnimation() => new BooleanKeyFrameAnimation(); - - public PathKeyFrameAnimation CreatePathKeyFrameAnimation() => new PathKeyFrameAnimation(); - } -} diff --git a/tests/CompDataFlatbuffer.Tests/CoverageTests.cs b/tests/CompDataFlatbuffer.Tests/CoverageTests.cs index 547b6d54..b06281cf 100644 --- a/tests/CompDataFlatbuffer.Tests/CoverageTests.cs +++ b/tests/CompDataFlatbuffer.Tests/CoverageTests.cs @@ -61,23 +61,6 @@ public void EveryNodeTypeSurvivesARoundTrip() Assert.Equal(expected, actual); } - [Fact] - public void EveryNodeTypeIsInterpreted() - { - var root = BuildGraph(); - - // The interpreter has a branch per node type just as the deserializer does, - // so it is held to the same standard: every node type that a graph can - // contain must come out of the buffer unchanged. - var expected = CompositionTreeDumper.Dump(root); - var actual = InterpretedTreeDumper.Dump( - LottieRuntime.CompositionInterpreter.LoadComposition( - new Windows.UI.Composition.Compositor(), - CompositionSerializer.Serialize(root, 14, null, null, 100, 100))); - - Assert.Equal(expected, actual); - } - // Builds a graph containing one of everything. It is not a sensible animation; // its only job is to reach every branch of the serializer and the deserializer. static Visual BuildGraph() diff --git a/tests/CompDataFlatbuffer.Tests/InterpretedTreeDumper.cs b/tests/CompDataFlatbuffer.Tests/InterpretedTreeDumper.cs deleted file mode 100644 index 82035b07..00000000 --- a/tests/CompDataFlatbuffer.Tests/InterpretedTreeDumper.cs +++ /dev/null @@ -1,805 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable enable - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Numerics; -using System.Text; -using Microsoft.Graphics.Canvas.Geometry; -using Windows.Graphics.Effects; -using Windows.UI.Composition; -using Windows.UI.Xaml.Media; -using Mgce = Microsoft.Graphics.Canvas.Effects; -using Wui = Windows.UI; - -namespace CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Tests -{ - /// - /// Renders a tree of composition objects as canonical text. - /// - /// - /// This is for the tree that - /// CompositionInterpreter builds. It writes the same text for the same - /// animation, which is what lets the interpreter be compared against the - /// WinCompData graph that the buffer was written from: the graph and the - /// interpreted tree are different object models, but if the two dumps are equal - /// then the interpreter created the same objects, gave them the same values, and - /// shared them in the same way. - /// - /// The two dumpers are deliberately not factored into one. Each is a plain - /// description of one object model, and keeping them separate means neither can - /// paper over a difference by describing both models in the same terms. - /// - static class InterpretedTreeDumper - { - /// - /// Renders an interpreted tree as canonical text. - /// - /// The root of the tree. - /// The canonical text of the tree. - public static string Dump(Visual root) - { - var writer = new Writer(); - writer.WriteVisual(root); - return writer.ToString(); - } - - sealed class Writer - { - readonly StringBuilder _builder = new StringBuilder(); - - readonly Dictionary _ids = - new Dictionary(ReferenceEqualityComparer.Instance); - - int _indent; - - public override string ToString() => _builder.ToString(); - - public void WriteVisual(Visual? value) - { - if (WriteNodeHeader(value, "Visual")) - { - return; - } - - WriteCompositionObject(value!); - - WriteOptional("BorderMode", value!.BorderMode); - WriteOptional("CenterPoint", value.CenterPoint); - WriteChild("Clip", value.Clip, WriteClip); - WriteOptional("IsVisible", value.IsVisible); - WriteOptional("Offset", value.Offset); - WriteOptional("Opacity", value.Opacity); - WriteOptional("RotationAngleInDegrees", value.RotationAngleInDegrees); - WriteOptional("RotationAxis", value.RotationAxis); - WriteOptional("Scale", value.Scale); - WriteOptional("Size", value.Size); - WriteOptional("TransformMatrix", value.TransformMatrix); - - if (value is ContainerVisual container) - { - WriteList("Children", container.Children.Visuals, WriteVisual); - } - - switch (value) - { - case ShapeVisual shapeVisual: - WriteList("Shapes", shapeVisual.Shapes, WriteShape); - WriteChild("ViewBox", shapeVisual.ViewBox, WriteViewBox); - break; - case SpriteVisual spriteVisual: - WriteChild("Brush", spriteVisual.Brush, WriteBrush); - WriteChild("Shadow", spriteVisual.Shadow, WriteShadow); - break; - case LayerVisual layerVisual: - WriteChild("Shadow", layerVisual.Shadow, WriteShadow); - break; - case ContainerVisual _: - break; - default: - throw new InvalidOperationException($"Unexpected visual {value.GetType().Name}."); - } - - _indent--; - } - - public void WriteShape(CompositionShape? value) - { - if (WriteNodeHeader(value, "Shape")) - { - return; - } - - WriteCompositionObject(value!); - - WriteOptional("CenterPoint", value!.CenterPoint); - WriteOptional("Offset", value.Offset); - WriteOptional("RotationAngleInDegrees", value.RotationAngleInDegrees); - WriteOptional("Scale", value.Scale); - WriteOptional("TransformMatrix", value.TransformMatrix); - - switch (value) - { - case CompositionContainerShape containerShape: - WriteList("Shapes", containerShape.Shapes, WriteShape); - break; - case CompositionSpriteShape spriteShape: - WriteChild("FillBrush", spriteShape.FillBrush, WriteBrush); - WriteChild("StrokeBrush", spriteShape.StrokeBrush, WriteBrush); - WriteChild("Geometry", spriteShape.Geometry, WriteGeometry); - WriteOptional("IsStrokeNonScaling", spriteShape.IsStrokeNonScaling); - WriteOptional("StrokeDashOffset", spriteShape.StrokeDashOffset); - WriteFloats("StrokeDashArray", spriteShape.StrokeDashArray); - WriteOptional("StrokeDashCap", spriteShape.StrokeDashCap); - WriteOptional("StrokeStartCap", spriteShape.StrokeStartCap); - WriteOptional("StrokeEndCap", spriteShape.StrokeEndCap); - WriteOptional("StrokeLineJoin", spriteShape.StrokeLineJoin); - WriteOptional("StrokeMiterLimit", spriteShape.StrokeMiterLimit); - WriteOptional("StrokeThickness", spriteShape.StrokeThickness); - break; - default: - throw new InvalidOperationException($"Unexpected shape {value.GetType().Name}."); - } - - _indent--; - } - - public void WriteGeometry(CompositionGeometry? value) - { - if (WriteNodeHeader(value, "Geometry")) - { - return; - } - - WriteCompositionObject(value!); - - WriteOptional("TrimStart", value!.TrimStart); - WriteOptional("TrimEnd", value.TrimEnd); - WriteOptional("TrimOffset", value.TrimOffset); - - switch (value) - { - case CompositionPathGeometry pathGeometry: - WriteChild("Path", pathGeometry.Path?.Source, WriteCanvasGeometry); - break; - case CompositionRoundedRectangleGeometry roundedRectangle: - WriteOptional("Offset", roundedRectangle.Offset); - WriteOptional("Size", roundedRectangle.Size); - Write("CornerRadius", roundedRectangle.CornerRadius ?? default); - break; - case CompositionRectangleGeometry rectangle: - WriteOptional("Offset", rectangle.Offset); - WriteOptional("Size", rectangle.Size); - break; - case CompositionEllipseGeometry ellipse: - Write("Center", ellipse.Center ?? default); - Write("Radius", ellipse.Radius ?? default); - break; - default: - throw new InvalidOperationException($"Unexpected geometry {value.GetType().Name}."); - } - - _indent--; - } - - public void WriteBrush(CompositionBrush? value) - { - if (WriteNodeHeader(value, "Brush")) - { - return; - } - - WriteCompositionObject(value!); - - switch (value) - { - case CompositionColorBrush colorBrush: - WriteOptional("Color", colorBrush.Color); - break; - case CompositionMaskBrush maskBrush: - WriteChild("Source", maskBrush.Source, WriteBrush); - WriteChild("Mask", maskBrush.Mask, WriteBrush); - break; - case CompositionSurfaceBrush surfaceBrush: - WriteChild("Surface", surfaceBrush.Surface, WriteSurface); - break; - case CompositionEffectBrush effectBrush: - var effect = effectBrush.GetEffectFactory().Effect; - WriteChild("Effect", effect, WriteEffect); - - foreach (var source in SourcesOf(effect)) - { - WriteChild( - $"Source({source.Name})", - effectBrush.GetSourceParameter(source.Name), - WriteBrush); - } - - break; - case CompositionLinearGradientBrush _: - case CompositionRadialGradientBrush _: - break; - default: - throw new InvalidOperationException($"Unexpected brush {value!.GetType().Name}."); - } - - if (value is CompositionGradientBrush gradientBrush) - { - WriteOptional("AnchorPoint", gradientBrush.AnchorPoint); - WriteOptional("CenterPoint", gradientBrush.CenterPoint); - WriteList("ColorStops", gradientBrush.ColorStops, WriteGradientStop); - WriteOptional("ExtendMode", gradientBrush.ExtendMode); - WriteOptional("InterpolationSpace", gradientBrush.InterpolationSpace); - WriteOptional("MappingMode", gradientBrush.MappingMode); - WriteOptional("Offset", gradientBrush.Offset); - WriteOptional("RotationAngleInDegrees", gradientBrush.RotationAngleInDegrees); - WriteOptional("Scale", gradientBrush.Scale); - WriteOptional("TransformMatrix", gradientBrush.TransformMatrix); - - switch (gradientBrush) - { - case CompositionLinearGradientBrush linear: - WriteOptional("StartPoint", linear.StartPoint); - WriteOptional("EndPoint", linear.EndPoint); - break; - case CompositionRadialGradientBrush radial: - WriteOptional("EllipseCenter", radial.EllipseCenter); - WriteOptional("EllipseRadius", radial.EllipseRadius); - WriteOptional("GradientOriginOffset", radial.GradientOriginOffset); - break; - } - } - - _indent--; - } - - public void WriteGradientStop(CompositionColorGradientStop? value) - { - if (WriteNodeHeader(value, "GradientStop")) - { - return; - } - - WriteCompositionObject(value!); - Write("Color", value!.Color ?? default); - Write("Offset", value.Offset ?? default); - _indent--; - } - - public void WriteViewBox(CompositionViewBox? value) - { - if (WriteNodeHeader(value, "ViewBox")) - { - return; - } - - WriteCompositionObject(value!); - Write("Size", value!.Size ?? default); - _indent--; - } - - public void WriteClip(CompositionClip? value) - { - if (WriteNodeHeader(value, "Clip")) - { - return; - } - - WriteCompositionObject(value!); - WriteOptional("CenterPoint", value!.CenterPoint); - WriteOptional("Scale", value.Scale); - - switch (value) - { - case InsetClip inset: - WriteOptional("LeftInset", inset.LeftInset); - WriteOptional("RightInset", inset.RightInset); - WriteOptional("TopInset", inset.TopInset); - WriteOptional("BottomInset", inset.BottomInset); - break; - case CompositionGeometricClip geometric: - WriteChild("Geometry", geometric.Geometry, WriteGeometry); - break; - default: - throw new InvalidOperationException($"Unexpected clip {value.GetType().Name}."); - } - - _indent--; - } - - public void WriteShadow(CompositionShadow? value) - { - if (WriteNodeHeader(value, "Shadow")) - { - return; - } - - WriteCompositionObject(value!); - - switch (value) - { - case DropShadow dropShadow: - WriteOptional("BlurRadius", dropShadow.BlurRadius); - WriteOptional("Color", dropShadow.Color); - WriteChild("Mask", dropShadow.Mask, WriteBrush); - WriteOptional("Offset", dropShadow.Offset); - WriteOptional("Opacity", dropShadow.Opacity); - WriteOptional("SourcePolicy", dropShadow.SourcePolicy); - break; - default: - throw new InvalidOperationException($"Unexpected shadow {value!.GetType().Name}."); - } - - _indent--; - } - - public void WriteSurface(ICompositionSurface? value) - { - if (WriteNodeHeader(value, "Surface")) - { - return; - } - - switch (value) - { - case CompositionVisualSurface visualSurface: - WriteCompositionObject(visualSurface); - WriteChild("SourceVisual", visualSurface.SourceVisual, WriteVisual); - WriteOptional("SourceSize", visualSurface.SourceSize); - WriteOptional("SourceOffset", visualSurface.SourceOffset); - break; - case LoadedImageSurfaceFromUri fromUri: - WriteLine($"Kind: FromUri"); - WriteLine($"Uri: {fromUri.Uri}"); - break; - case LoadedImageSurfaceFromStream fromStream: - WriteLine($"Kind: FromStream"); - WriteLine($"Bytes: {Convert.ToBase64String(fromStream.Bytes)}"); - break; - default: - throw new InvalidOperationException($"Unexpected surface {value!.GetType().Name}."); - } - - _indent--; - } - - public void WriteEffect(IGraphicsEffect? value) - { - if (WriteNodeHeader(value, "Effect")) - { - return; - } - - WriteLine($"Sources: {string.Join(", ", SourcesOf(value!).Select(s => s.Name))}"); - - switch (value) - { - case Mgce.CompositeEffect composite: - Write("Mode", composite.Mode); - break; - case Mgce.GaussianBlurEffect blur: - Write("BlurAmount", blur.BlurAmount); - break; - default: - throw new InvalidOperationException($"Unexpected effect {value!.GetType().Name}."); - } - - _indent--; - } - - public void WriteEasing(CompositionEasingFunction? value) - { - if (WriteNodeHeader(value, "Easing")) - { - return; - } - - WriteCompositionObject(value!); - - switch (value) - { - case LinearEasingFunction _: - break; - case CubicBezierEasingFunction cubicBezier: - Write("ControlPoint1", cubicBezier.ControlPoint1); - Write("ControlPoint2", cubicBezier.ControlPoint2); - break; - case StepEasingFunction step: - WriteOptional("StepCount", step.StepCount); - WriteOptional("InitialStep", step.InitialStep); - WriteOptional("FinalStep", step.FinalStep); - WriteOptional("IsInitialStepSingleFrame", step.IsInitialStepSingleFrame); - WriteOptional("IsFinalStepSingleFrame", step.IsFinalStepSingleFrame); - break; - default: - throw new InvalidOperationException($"Unexpected easing {value!.GetType().Name}."); - } - - _indent--; - } - - public void WriteAnimation(CompositionAnimation? value) - { - if (WriteNodeHeader(value, "Animation")) - { - return; - } - - WriteCompositionObject(value!); - WriteLine($"Target: {Text(value!.Target)}"); - - foreach (var parameter in value.ReferenceParameters.OrderBy(p => p.Key, StringComparer.Ordinal)) - { - WriteChild($"Reference({parameter.Key})", parameter.Value, WriteCompositionObjectReference); - } - - switch (value) - { - case ExpressionAnimation expression: - WriteLine($"Expression: {expression.Expression}"); - break; - case KeyFrameAnimation keyFrameAnimation: - WriteLine($"Duration: {keyFrameAnimation.Duration.Ticks}"); - - if (value is ColorKeyFrameAnimation color) - { - Write("InterpolationColorSpace", color.InterpolationColorSpace ?? CompositionColorSpace.Auto); - } - - foreach (var keyFrame in keyFrameAnimation.KeyFrames) - { - WriteKeyFrame(keyFrame); - } - - break; - default: - throw new InvalidOperationException($"Unexpected animation {value.GetType().Name}."); - } - - _indent--; - } - - void WriteKeyFrame(KeyFrameAnimation.KeyFrame value) - { - WriteLine($"KeyFrame @{Text(value.Progress)}"); - _indent++; - WriteChild("Easing", value.Easing, WriteEasing); - - switch (value) - { - case KeyFrameAnimation.ExpressionKeyFrame expression: - WriteLine($"Expression: {expression.Expression}"); - break; - case KeyFrameAnimation.ValueKeyFrame valueKeyFrame: - if (valueKeyFrame.Value is CompositionPath path) - { - WriteChild("Value", path.Source, WriteCanvasGeometry); - } - else - { - WriteLine($"Value: {Text(valueKeyFrame.Value)}"); - } - - break; - default: - throw new InvalidOperationException($"Unexpected key frame {value.GetType().Name}."); - } - - _indent--; - } - - public void WriteCanvasGeometry(CanvasGeometry? value) - { - if (WriteNodeHeader(value, "CanvasGeometry")) - { - return; - } - - switch (value) - { - case CanvasGeometry.Combination combination: - WriteChild("A", combination.A, WriteCanvasGeometry); - WriteChild("B", combination.B, WriteCanvasGeometry); - Write("CombineMode", combination.CombineMode); - Write("Matrix", combination.Matrix); - break; - case CanvasGeometry.TransformedGeometry transformed: - WriteChild("Source", transformed.SourceGeometry, WriteCanvasGeometry); - Write("TransformMatrix", transformed.TransformMatrix); - break; - case CanvasGeometry.Group group: - Write("FilledRegionDetermination", group.FilledRegionDetermination); - WriteList("Geometries", group.Geometries, WriteCanvasGeometry); - break; - case CanvasGeometry.Path path: - Write("FilledRegionDetermination", path.FilledRegionDetermination); - - foreach (var command in path.Commands) - { - WriteLine(Text(command)); - } - - break; - case CanvasGeometry.Ellipse ellipse: - WriteLine( - $"Ellipse: {Text(ellipse.X)} {Text(ellipse.Y)} " + - $"{Text(ellipse.RadiusX)} {Text(ellipse.RadiusY)}"); - break; - case CanvasGeometry.RoundedRectangle rectangle: - WriteLine( - $"RoundedRectangle: {Text(rectangle.X)} {Text(rectangle.Y)} " + - $"{Text(rectangle.W)} {Text(rectangle.H)} " + - $"{Text(rectangle.RadiusX)} {Text(rectangle.RadiusY)}"); - break; - default: - throw new InvalidOperationException($"Unexpected canvas geometry {value!.GetType().Name}."); - } - - _indent--; - } - - void WriteCompositionObjectReference(CompositionObject? value) - { - switch (value) - { - case null: - WriteLine("null"); - break; - case Visual visual: - WriteVisual(visual); - break; - case CompositionShape shape: - WriteShape(shape); - break; - case CompositionGeometry geometry: - WriteGeometry(geometry); - break; - case CompositionBrush brush: - WriteBrush(brush); - break; - case CompositionAnimation animation: - WriteAnimation(animation); - break; - case CompositionEasingFunction easing: - WriteEasing(easing); - break; - case CompositionPropertySet propertySet: - WritePropertySet(propertySet); - break; - case CompositionVisualSurface surface: - WriteSurface(surface); - break; - case CompositionClip clip: - WriteClip(clip); - break; - case AnimationController controller: - WriteController(controller); - break; - case CompositionShadow shadow: - WriteShadow(shadow); - break; - case CompositionColorGradientStop stop: - WriteGradientStop(stop); - break; - case CompositionViewBox viewBox: - WriteViewBox(viewBox); - break; - default: - throw new InvalidOperationException($"Unexpected reference to {value.GetType().Name}."); - } - } - - void WritePropertySet(CompositionPropertySet? value) - { - if (WriteNodeHeader(value, "PropertySet")) - { - return; - } - - WritePropertySetContents(value!); - _indent--; - } - - void WriteController(AnimationController? value) - { - if (WriteNodeHeader(value, "Controller")) - { - return; - } - - WriteLine($"IsCustom: {value!.IsCustom}"); - WriteLine($"IsPaused: {value.IsPaused}"); - WriteLine($"TargetProperty: {Text(value.TargetProperty)}"); - WriteCompositionObject(value); - _indent--; - } - - void WriteCompositionObject(CompositionObject value) - { - if (value.Comment is not null) - { - WriteLine($"Comment: {value.Comment}"); - } - - var properties = value.Properties; - if (value is not CompositionPropertySet && - (properties.Names.Count > 0 || properties.Animators.Count > 0)) - { - WriteLine("Properties"); - _indent++; - WritePropertySetContents(properties); - _indent--; - } - - foreach (var animator in value.Animators) - { - WriteLine($"Animator: {animator.AnimatedProperty}"); - _indent++; - WriteChild("Animation", animator.Animation, WriteAnimation); - - if (animator.Controller is not null) - { - WriteChild("Controller", animator.Controller, WriteController); - } - - _indent--; - } - } - - void WritePropertySetContents(CompositionPropertySet value) - { - foreach (var name in value.Names.Keys) - { - WriteLine($"{name}: {Text(value.GetValue(name))}"); - } - - foreach (var animator in value.Animators) - { - WriteLine($"Animator: {animator.AnimatedProperty}"); - _indent++; - WriteChild("Animation", animator.Animation, WriteAnimation); - _indent--; - } - } - - // The sources of an effect, in the order that they were given to it. - static IEnumerable SourcesOf(IGraphicsEffect effect) - => effect switch - { - Mgce.CompositeEffect composite => composite.Sources.Cast(), - Mgce.GaussianBlurEffect blur => new[] { (CompositionEffectSourceParameter)blur.Source! }, - _ => throw new InvalidOperationException($"Unexpected effect {effect.GetType().Name}."), - }; - - bool WriteNodeHeader(object? value, string category) - { - if (value is null) - { - WriteLine($"{category}: null"); - return true; - } - - if (_ids.TryGetValue(value, out var existing)) - { - WriteLine($"{category} #{existing} (shared)"); - return true; - } - - var id = _ids.Count; - _ids.Add(value, id); - WriteLine($"{category} #{id} {TypeNameOf(value)}"); - _indent++; - return false; - } - - // The names of the stand-in types are the names of the real composition - // types, which are also the names of the WinCompData types, so the two - // dumps name the same object identically. - static string TypeNameOf(object value) - => value switch - { - LoadedImageSurfaceFromUri _ => "FromUri", - LoadedImageSurfaceFromStream _ => "FromStream", - _ => value.GetType().Name, - }; - - void WriteChild(string name, T? value, Action write) - where T : class - { - WriteLine($"{name}:"); - _indent++; - write(value); - _indent--; - } - - void WriteList(string name, IEnumerable values, Action write) - where T : class - { - var array = values.ToArray(); - if (array.Length == 0) - { - return; - } - - WriteLine($"{name}: [{array.Length}]"); - _indent++; - foreach (var value in array) - { - write(value); - } - - _indent--; - } - - void WriteOptional(string name, T? value) - where T : struct - { - if (value.HasValue) - { - WriteLine($"{name}: {Text(value.Value)}"); - } - } - - void Write(string name, T value) - where T : struct - => WriteLine($"{name}: {Text(value)}"); - - void WriteFloats(string name, IEnumerable values) - { - var array = values.ToArray(); - if (array.Length > 0) - { - WriteLine($"{name}: {string.Join(", ", array.Select(v => Text(v)))}"); - } - } - - void WriteLine(string text) - => _builder.Append(' ', _indent * 2).Append(text).Append('\n'); - - static string Text(object? value) - => value switch - { - null => "null", - float f => f.ToString("R", CultureInfo.InvariantCulture), - Vector2 v => $"<{Text(v.X)}, {Text(v.Y)}>", - Vector3 v => $"<{Text(v.X)}, {Text(v.Y)}, {Text(v.Z)}>", - Vector4 v => $"<{Text(v.X)}, {Text(v.Y)}, {Text(v.Z)}, {Text(v.W)}>", - Matrix3x2 m => - $"[{Text(m.M11)}, {Text(m.M12)}, {Text(m.M21)}, " + - $"{Text(m.M22)}, {Text(m.M31)}, {Text(m.M32)}]", - Matrix4x4 m => - $"[{Text(m.M11)}, {Text(m.M12)}, {Text(m.M13)}, {Text(m.M14)}, " + - $"{Text(m.M21)}, {Text(m.M22)}, {Text(m.M23)}, {Text(m.M24)}, " + - $"{Text(m.M31)}, {Text(m.M32)}, {Text(m.M33)}, {Text(m.M34)}, " + - $"{Text(m.M41)}, {Text(m.M42)}, {Text(m.M43)}, {Text(m.M44)}]", - Wui.Color c => $"#{c.A:X2}{c.R:X2}{c.G:X2}{c.B:X2}", - CanvasPathBuilder.Command.BeginFigure begin => $"BeginFigure {Text(begin.StartPoint)}", - CanvasPathBuilder.Command.EndFigure end => $"EndFigure {end.FigureLoop}", - CanvasPathBuilder.Command.AddLine line => $"AddLine {Text(line.EndPoint)}", - CanvasPathBuilder.Command.AddCubicBezier bezier => - $"AddCubicBezier {Text(bezier.ControlPoint1)} " + - $"{Text(bezier.ControlPoint2)} {Text(bezier.EndPoint)}", - _ => value.ToString() ?? string.Empty, - }; - } - - sealed class ReferenceEqualityComparer : IEqualityComparer - { - internal static readonly ReferenceEqualityComparer Instance = new ReferenceEqualityComparer(); - - ReferenceEqualityComparer() - { - } - - bool IEqualityComparer.Equals(object? x, object? y) => ReferenceEquals(x, y); - - int IEqualityComparer.GetHashCode(object obj) - => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj); - } - } -} diff --git a/tests/CompDataFlatbuffer.Tests/InterpreterTests.cs b/tests/CompDataFlatbuffer.Tests/InterpreterTests.cs deleted file mode 100644 index 224cbf6a..00000000 --- a/tests/CompDataFlatbuffer.Tests/InterpreterTests.cs +++ /dev/null @@ -1,214 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable enable - -using System; -using System.Linq; -using CommunityToolkit.WinUI.Lottie.LottieRuntime; -using Google.FlatBuffers; -using Windows.UI.Composition; -using Xunit; -using Fb = CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Schema; - -namespace CommunityToolkit.WinUI.Lottie.CompDataFlatbuffer.Tests -{ - /// - /// Verifies that the managed interpreter builds the composition that the buffer - /// describes. - /// - /// - /// The interpreter goes straight from bytes to composition objects, so unlike the - /// deserializer it cannot be checked by comparing WinCompData graphs. Instead the - /// tree it builds is dumped in the same canonical text as the graph that the - /// buffer was written from, and the two are compared. Equal text means the - /// interpreter created the same objects, gave them the same values, and shared - /// them in the same way, which is the whole of what an interpreter has to get - /// right. - /// - /// The composition APIs that the interpreter calls only exist on Windows, so this - /// runs against the stand-ins in CompositionApiStandIns.cs. That means these tests - /// verify the calls that the interpreter makes rather than the pixels that Windows - /// draws; the pixels are the same as the deserializer's precisely because the - /// calls are. - /// - public class InterpreterTests - { - [Theory] - [MemberData(nameof(Corpus.FileNames), MemberType = typeof(Corpus))] - public void InterpretedTreeMatchesTheGraph(string fileName) - { - var translated = Corpus.Translate(fileName); - - var expected = CompositionTreeDumper.Dump(translated.RootVisual); - var actual = InterpretedTreeDumper.Dump(Interpret(translated.Serialize())); - - Assert.Equal(expected, actual); - } - - [Theory] - [MemberData(nameof(Corpus.FileNames), MemberType = typeof(Corpus))] - public void InterpretationIsRepeatable(string fileName) - { - var buffer = Corpus.Translate(fileName).Serialize(); - - // Interpreting the same buffer twice must produce the same tree. A buffer - // is read in place, so this also shows that reading it does not modify it. - Assert.Equal( - InterpretedTreeDumper.Dump(Interpret(buffer)), - InterpretedTreeDumper.Dump(Interpret(buffer))); - } - - [Theory] - [MemberData(nameof(Corpus.FileNames), MemberType = typeof(Corpus))] - public void ProgressPropertySetHoldsTheProgressProperty(string fileName) - { - var root = Interpret(Corpus.Translate(fileName).Serialize()); - - // Every translated animation is driven by a single Progress property on the - // root visual's property set. A host that cannot find it cannot play the - // animation, so this is checked separately from the dump. - Assert.Contains( - "Progress", - CompositionInterpreter.ProgressPropertySet(root).Names.Keys, - StringComparer.Ordinal); - } - - [Fact] - public void EmptyBufferIsRejected() - => Assert.Throws(() => Interpret(Array.Empty())); - - [Fact] - public void GarbageIsRejected() - { - var garbage = new byte[512]; - new Random(0).NextBytes(garbage); - - Assert.Throws(() => Interpret(garbage)); - } - - [Fact] - public void BufferWithTheWrongFileIdentifierIsRejected() - { - var buffer = Corpus.Translate("LightBulb.json").Serialize(); - - // The file identifier sits at offset 4, immediately after the root offset. - buffer[4] = (byte)'X'; - - Assert.Throws(() => Interpret(buffer)); - } - - [Fact] - public void BufferWithNoRootVisualIsRejected() - => Assert.Throws( - () => Interpret(CreateComposition(Format.Version, 0, withRootVisual: false))); - - [Fact] - public void NewerSchemaVersionIsRejected() - { - // A buffer written by a newer serializer may use fields that this build - // does not know about, so loading it would silently lose part of the - // animation. It is refused instead. - var exception = Assert.Throws( - () => Interpret(CreateComposition((ushort)(Format.Version + 1), 0))); - - Assert.Contains($"{Format.Version + 1}", exception.Message, StringComparison.Ordinal); - } - - [Fact] - public void CurrentSchemaVersionIsAccepted() - => Assert.NotNull(Interpret(CreateComposition(Format.Version, 0))); - - [Fact] - public void UnavailableApiContractIsRejected() - { - // An animation that needs composition APIs that this version of Windows - // does not have is refused rather than built into a tree that cannot run. - var exception = Assert.Throws( - () => Interpret(CreateComposition(Format.Version, ushort.MaxValue))); - - Assert.Contains("UniversalApiContract", exception.Message, StringComparison.Ordinal); - } - - [Theory] - [MemberData(nameof(Corpus.FileNames), MemberType = typeof(Corpus))] - public void TruncatedBuffersAreRejected(string fileName) - { - var buffer = Corpus.Translate(fileName).Serialize(); - - // Truncation is checked at a spread of lengths rather than at every length, - // which would make the test run for minutes without finding anything more. - for (var length = 0; length < buffer.Length; length += Math.Max(1, buffer.Length / 64)) - { - var truncated = new byte[length]; - Array.Copy(buffer, truncated, length); - - Assert.Throws(() => Interpret(truncated)); - } - } - - [Theory] - [MemberData(nameof(Corpus.FileNames), MemberType = typeof(Corpus))] - public void CorruptedBuffersDoNotCrash(string fileName) - { - var original = Corpus.Translate(fileName).Serialize(); - var random = new Random(fileName.GetHashCode(StringComparison.Ordinal)); - - // Unlike truncation, a flipped bit may leave a buffer that is still valid, - // just describing a different animation. So the requirement here is weaker: - // the interpreter either succeeds or reports a format error, but never - // throws something else and never reads outside the buffer. - for (var i = 0; i < 200; i++) - { - var corrupted = (byte[])original.Clone(); - corrupted[random.Next(corrupted.Length)] ^= (byte)(1 << random.Next(8)); - - try - { - Interpret(corrupted); - } - catch (FlatBufferFormatException) - { - } - catch (NotSupportedException) - { - // A flipped bit can also land in the schema version or the required - // API contract version. - } - } - } - - static Visual Interpret(byte[] buffer) - => CompositionInterpreter.LoadComposition(new Compositor(), buffer); - - // Builds the smallest possible composition: one empty container visual. Used by - // the tests of the header, which have nothing to do with the contents. - static byte[] CreateComposition(ushort schemaVersion, ushort requiredUapVersion, bool withRootVisual = true) - { - var builder = new FlatBufferBuilder(256); - - Fb.Visual.StartVisual(builder); - Fb.Visual.AddKind(builder, Fb.VisualKind.Container); - var visual = Fb.Visual.EndVisual(builder); - - var visuals = Fb.LottieComposition.CreateVisualsVector(builder, new[] { visual }); - - Fb.LottieComposition.StartLottieComposition(builder); - Fb.LottieComposition.AddSchemaVersion(builder, schemaVersion); - Fb.LottieComposition.AddRequiredUapVersion(builder, requiredUapVersion); - - if (withRootVisual) - { - Fb.LottieComposition.AddRootVisual(builder, 0); - } - - Fb.LottieComposition.AddVisuals(builder, visuals); - var composition = Fb.LottieComposition.EndLottieComposition(builder); - - Fb.LottieComposition.FinishLottieCompositionBuffer(builder, composition); - - return builder.SizedByteArray(); - } - } -} From a380992c3edc5a08346711e924d5ded9cc36e042 Mon Sep 17 00:00:00 2001 From: Jon Wiswall Date: Mon, 12 Oct 2026 17:10:57 -0700 Subject: [PATCH 32/39] Vendor FlatBuffers C++ headers Replace the FlatBuffers submodule with the pinned public headers and license, and document a validated regeneration workflow for matching flatc binaries and headers. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: adcabe6f-aa5c-4e06-948d-b49e5adae80c --- .gitmodules | 3 - build/RegenerateFlatbuffers.ps1 | 61 +- external/flatbuffers | 1 - external/flatbuffers/LICENSE | 202 ++ external/flatbuffers/UPSTREAM.md | 11 + .../include/flatbuffers/allocator.h | 68 + .../flatbuffers/include/flatbuffers/array.h | 258 ++ .../flatbuffers/include/flatbuffers/base.h | 503 ++++ .../flatbuffers/include/flatbuffers/buffer.h | 225 ++ .../include/flatbuffers/buffer_ref.h | 54 + .../include/flatbuffers/code_generator.h | 99 + .../include/flatbuffers/code_generators.h | 238 ++ .../include/flatbuffers/default_allocator.h | 64 + .../include/flatbuffers/detached_buffer.h | 121 + .../include/flatbuffers/file_manager.h | 70 + .../include/flatbuffers/flatbuffer_builder.h | 1518 ++++++++++++ .../include/flatbuffers/flatbuffers.h | 284 +++ .../flatbuffers/include/flatbuffers/flatc.h | 131 ++ .../include/flatbuffers/flex_flat_util.h | 37 + .../include/flatbuffers/flexbuffers.h | 2086 +++++++++++++++++ .../flatbuffers/include/flatbuffers/grpc.h | 303 +++ .../flatbuffers/include/flatbuffers/hash.h | 135 ++ .../flatbuffers/include/flatbuffers/idl.h | 1329 +++++++++++ .../include/flatbuffers/minireflect.h | 441 ++++ .../include/flatbuffers/pch/flatc_pch.h | 39 + .../flatbuffers/include/flatbuffers/pch/pch.h | 38 + .../include/flatbuffers/reflection.h | 530 +++++ .../flatbuffers/reflection_generated.h | 1541 ++++++++++++ .../include/flatbuffers/registry.h | 130 + .../include/flatbuffers/stl_emulation.h | 516 ++++ .../flatbuffers/include/flatbuffers/string.h | 69 + .../flatbuffers/include/flatbuffers/struct.h | 55 + .../flatbuffers/include/flatbuffers/table.h | 263 +++ .../flatbuffers/include/flatbuffers/util.h | 781 ++++++ .../flatbuffers/include/flatbuffers/vector.h | 455 ++++ .../include/flatbuffers/vector_downward.h | 298 +++ .../include/flatbuffers/verifier.h | 370 +++ source/LottieFlatbuffer/README.md | 12 +- 38 files changed, 13330 insertions(+), 9 deletions(-) delete mode 100644 .gitmodules delete mode 160000 external/flatbuffers create mode 100644 external/flatbuffers/LICENSE create mode 100644 external/flatbuffers/UPSTREAM.md create mode 100644 external/flatbuffers/include/flatbuffers/allocator.h create mode 100644 external/flatbuffers/include/flatbuffers/array.h create mode 100644 external/flatbuffers/include/flatbuffers/base.h create mode 100644 external/flatbuffers/include/flatbuffers/buffer.h create mode 100644 external/flatbuffers/include/flatbuffers/buffer_ref.h create mode 100644 external/flatbuffers/include/flatbuffers/code_generator.h create mode 100644 external/flatbuffers/include/flatbuffers/code_generators.h create mode 100644 external/flatbuffers/include/flatbuffers/default_allocator.h create mode 100644 external/flatbuffers/include/flatbuffers/detached_buffer.h create mode 100644 external/flatbuffers/include/flatbuffers/file_manager.h create mode 100644 external/flatbuffers/include/flatbuffers/flatbuffer_builder.h create mode 100644 external/flatbuffers/include/flatbuffers/flatbuffers.h create mode 100644 external/flatbuffers/include/flatbuffers/flatc.h create mode 100644 external/flatbuffers/include/flatbuffers/flex_flat_util.h create mode 100644 external/flatbuffers/include/flatbuffers/flexbuffers.h create mode 100644 external/flatbuffers/include/flatbuffers/grpc.h create mode 100644 external/flatbuffers/include/flatbuffers/hash.h create mode 100644 external/flatbuffers/include/flatbuffers/idl.h create mode 100644 external/flatbuffers/include/flatbuffers/minireflect.h create mode 100644 external/flatbuffers/include/flatbuffers/pch/flatc_pch.h create mode 100644 external/flatbuffers/include/flatbuffers/pch/pch.h create mode 100644 external/flatbuffers/include/flatbuffers/reflection.h create mode 100644 external/flatbuffers/include/flatbuffers/reflection_generated.h create mode 100644 external/flatbuffers/include/flatbuffers/registry.h create mode 100644 external/flatbuffers/include/flatbuffers/stl_emulation.h create mode 100644 external/flatbuffers/include/flatbuffers/string.h create mode 100644 external/flatbuffers/include/flatbuffers/struct.h create mode 100644 external/flatbuffers/include/flatbuffers/table.h create mode 100644 external/flatbuffers/include/flatbuffers/util.h create mode 100644 external/flatbuffers/include/flatbuffers/vector.h create mode 100644 external/flatbuffers/include/flatbuffers/vector_downward.h create mode 100644 external/flatbuffers/include/flatbuffers/verifier.h diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index d67d60a6..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "external/flatbuffers"] - path = external/flatbuffers - url = https://github.com/google/flatbuffers.git diff --git a/build/RegenerateFlatbuffers.ps1 b/build/RegenerateFlatbuffers.ps1 index 0de938e3..134f1e10 100644 --- a/build/RegenerateFlatbuffers.ps1 +++ b/build/RegenerateFlatbuffers.ps1 @@ -6,6 +6,15 @@ # # C# output must match the Google.FlatBuffers package available through the # configured Microsoft feed. C++ output must match external/flatbuffers. +# +# To obtain the matching C++ compiler and headers: +# 1. Download the Windows flatc binary for v25.12.19 from +# https://github.com/google/flatbuffers/releases/tag/v25.12.19. +# 2. Download and extract the source archive for commit +# 5761d6e67af841d15ee21bc1ce9a78ffa9cf939e. +# 3. Pass the extracted source directory as -CppSource. The script validates +# its version, copies include/flatbuffers and LICENSE into external/flatbuffers, +# and regenerates the checked-in C++ binding with the matching flatc.exe. [CmdletBinding()] param( @@ -14,18 +23,42 @@ param( # C++ code must match the headers in external/flatbuffers. [Parameter(Mandatory)] - [string]$CppFlatc + [string]$CppFlatc, + + # Optional extracted FlatBuffers source tree. Supply this when updating the + # vendored headers and license. + [string]$CppSource ) $ErrorActionPreference = "Stop" $expectedCSharpVersion = "25.2.10" $expectedCppVersion = "25.12.19" +$expectedCppCommit = "5761d6e67af841d15ee21bc1ce9a78ffa9cf939e" $repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..") $schema = Join-Path $repoRoot "source/LottieFlatbuffer/lottie_comp.fbs" $csharpOut = Join-Path $repoRoot "source/CompDataFlatbuffer/Generated" $cppOut = Join-Path $repoRoot "source/LottieRuntime/Generated" +$vendoredFlatBuffers = Join-Path $repoRoot "external/flatbuffers" + +function Get-HeaderVersion([string]$sourceRoot) { + $baseHeader = Join-Path $sourceRoot "include/flatbuffers/base.h" + if (!(Test-Path $baseHeader)) { + throw "FlatBuffers header not found: $baseHeader" + } + + $contents = Get-Content $baseHeader -Raw + $major = [regex]::Match($contents, '#define FLATBUFFERS_VERSION_MAJOR\s+(\d+)').Groups[1].Value + $minor = [regex]::Match($contents, '#define FLATBUFFERS_VERSION_MINOR\s+(\d+)').Groups[1].Value + $revision = [regex]::Match($contents, '#define FLATBUFFERS_VERSION_REVISION\s+(\d+)').Groups[1].Value + + if (!$major -or !$minor -or !$revision) { + throw "Could not read the FlatBuffers version from $baseHeader." + } + + return "$major.$minor.$revision" +} $actualCSharpVersion = (& $CSharpFlatc --version) -replace '^flatc version ', '' if ($actualCSharpVersion.Trim() -ne $expectedCSharpVersion) { @@ -37,6 +70,32 @@ if ($actualCppVersion.Trim() -ne $expectedCppVersion) { throw "C++ flatc version $expectedCppVersion is required, but '$CppFlatc' is version $($actualCppVersion.Trim())." } +if ($CppSource) { + $resolvedCppSource = Resolve-Path $CppSource + $sourceVersion = Get-HeaderVersion $resolvedCppSource + if ($sourceVersion -ne $expectedCppVersion) { + throw "FlatBuffers headers version $expectedCppVersion is required, but '$resolvedCppSource' contains version $sourceVersion." + } + + $sourceLicense = Join-Path $resolvedCppSource "LICENSE" + if (!(Test-Path $sourceLicense)) { + throw "FlatBuffers license not found: $sourceLicense" + } + + $vendoredHeaders = Join-Path $vendoredFlatBuffers "include/flatbuffers" + Remove-Item -LiteralPath $vendoredHeaders -Recurse -Force -ErrorAction SilentlyContinue + New-Item -ItemType Directory -Force -Path (Split-Path $vendoredHeaders) | Out-Null + Copy-Item -LiteralPath (Join-Path $resolvedCppSource "include/flatbuffers") -Destination $vendoredHeaders -Recurse + Copy-Item -LiteralPath $sourceLicense -Destination (Join-Path $vendoredFlatBuffers "LICENSE") -Force + + Write-Host "Updated vendored FlatBuffers headers from commit $expectedCppCommit" +} + +$vendoredVersion = Get-HeaderVersion $vendoredFlatBuffers +if ($vendoredVersion -ne $expectedCppVersion) { + throw "Vendored FlatBuffers headers must be version $expectedCppVersion, but they are version $vendoredVersion." +} + New-Item -ItemType Directory -Force -Path $csharpOut, $cppOut | Out-Null # --gen-onefile keeps the checked in C# to a single file rather than one file diff --git a/external/flatbuffers b/external/flatbuffers deleted file mode 160000 index 5761d6e6..00000000 --- a/external/flatbuffers +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5761d6e67af841d15ee21bc1ce9a78ffa9cf939e diff --git a/external/flatbuffers/LICENSE b/external/flatbuffers/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/external/flatbuffers/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/external/flatbuffers/UPSTREAM.md b/external/flatbuffers/UPSTREAM.md new file mode 100644 index 00000000..751489c9 --- /dev/null +++ b/external/flatbuffers/UPSTREAM.md @@ -0,0 +1,11 @@ +# FlatBuffers headers + +The files in this directory are vendored from: + +- Repository: +- Commit: `5761d6e67af841d15ee21bc1ce9a78ffa9cf939e` +- Header and `flatc` version: `25.12.19` + +Only the C++ headers under `include/flatbuffers` and the upstream `LICENSE` are +included. Use `build/RegenerateFlatbuffers.ps1` to validate and update this copy +alongside the generated C++ binding. diff --git a/external/flatbuffers/include/flatbuffers/allocator.h b/external/flatbuffers/include/flatbuffers/allocator.h new file mode 100644 index 00000000..d4518185 --- /dev/null +++ b/external/flatbuffers/include/flatbuffers/allocator.h @@ -0,0 +1,68 @@ +/* + * Copyright 2021 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLATBUFFERS_ALLOCATOR_H_ +#define FLATBUFFERS_ALLOCATOR_H_ + +#include "flatbuffers/base.h" + +namespace flatbuffers { + +// Allocator interface. This is flatbuffers-specific and meant only for +// `vector_downward` usage. +class Allocator { + public: + virtual ~Allocator() {} + + // Allocate `size` bytes of memory. + virtual uint8_t* allocate(size_t size) = 0; + + // Deallocate `size` bytes of memory at `p` allocated by this allocator. + virtual void deallocate(uint8_t* p, size_t size) = 0; + + // Reallocate `new_size` bytes of memory, replacing the old region of size + // `old_size` at `p`. In contrast to a normal realloc, this grows downwards, + // and is intended specifcally for `vector_downward` use. + // `in_use_back` and `in_use_front` indicate how much of `old_size` is + // actually in use at each end, and needs to be copied. + virtual uint8_t* reallocate_downward(uint8_t* old_p, size_t old_size, + size_t new_size, size_t in_use_back, + size_t in_use_front) { + FLATBUFFERS_ASSERT(new_size > old_size); // vector_downward only grows + uint8_t* new_p = allocate(new_size); + memcpy_downward(old_p, old_size, new_p, new_size, in_use_back, + in_use_front); + deallocate(old_p, old_size); + return new_p; + } + + protected: + // Called by `reallocate_downward` to copy memory from `old_p` of `old_size` + // to `new_p` of `new_size`. Only memory of size `in_use_front` and + // `in_use_back` will be copied from the front and back of the old memory + // allocation. + void memcpy_downward(uint8_t* old_p, size_t old_size, uint8_t* new_p, + size_t new_size, size_t in_use_back, + size_t in_use_front) { + memcpy(new_p + new_size - in_use_back, old_p + old_size - in_use_back, + in_use_back); + memcpy(new_p, old_p, in_use_front); + } +}; + +} // namespace flatbuffers + +#endif // FLATBUFFERS_ALLOCATOR_H_ diff --git a/external/flatbuffers/include/flatbuffers/array.h b/external/flatbuffers/include/flatbuffers/array.h new file mode 100644 index 00000000..163d8c36 --- /dev/null +++ b/external/flatbuffers/include/flatbuffers/array.h @@ -0,0 +1,258 @@ +/* + * Copyright 2021 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLATBUFFERS_ARRAY_H_ +#define FLATBUFFERS_ARRAY_H_ + +#include +#include + +#include "flatbuffers/base.h" +#include "flatbuffers/stl_emulation.h" +#include "flatbuffers/vector.h" + +namespace flatbuffers { + +// This is used as a helper type for accessing arrays. +template +class Array { + // Array can carry only POD data types (scalars or structs). + typedef typename flatbuffers::bool_constant::value> + scalar_tag; + + public: + typedef uint16_t size_type; + typedef typename IndirectHelper::return_type return_type; + typedef VectorConstIterator const_iterator; + typedef VectorReverseIterator const_reverse_iterator; + + // If T is a non-pointer and a LE-scalar or a struct (!scalar_tag::value). + static FLATBUFFERS_CONSTEXPR bool is_span_observable = + !std::is_pointer::value && + ((scalar_tag::value && (FLATBUFFERS_LITTLEENDIAN || sizeof(T) == 1)) || + !scalar_tag::value); + + FLATBUFFERS_CONSTEXPR uint16_t size() const { return length; } + + return_type Get(uoffset_t i) const { + FLATBUFFERS_ASSERT(i < size()); + return IndirectHelper::Read(Data(), i); + } + + return_type operator[](uoffset_t i) const { return Get(i); } + + // If this is a Vector of enums, T will be its storage type, not the enum + // type. This function makes it convenient to retrieve value with enum + // type E. + template + E GetEnum(uoffset_t i) const { + return static_cast(Get(i)); + } + + const_iterator begin() const { return const_iterator(Data(), 0); } + const_iterator end() const { return const_iterator(Data(), size()); } + + const_reverse_iterator rbegin() const { + return const_reverse_iterator(end()); + } + const_reverse_iterator rend() const { + return const_reverse_iterator(begin()); + } + + const_iterator cbegin() const { return begin(); } + const_iterator cend() const { return end(); } + + const_reverse_iterator crbegin() const { return rbegin(); } + const_reverse_iterator crend() const { return rend(); } + + // Get a mutable pointer to elements inside this array. + // This method used to mutate arrays of structs followed by a @p Mutate + // operation. For primitive types use @p Mutate directly. + // @warning Assignments and reads to/from the dereferenced pointer are not + // automatically converted to the correct endianness. + typename flatbuffers::conditional::type + GetMutablePointer(uoffset_t i) const { + FLATBUFFERS_ASSERT(i < size()); + return const_cast(&data()[i]); + } + + // Change elements if you have a non-const pointer to this object. + void Mutate(uoffset_t i, const T& val) { MutateImpl(scalar_tag(), i, val); } + + // The raw data in little endian format. Use with care. + const uint8_t* Data() const { return data_; } + + uint8_t* Data() { return data_; } + + // Similarly, but typed, much like std::vector::data + const T* data() const { return reinterpret_cast(Data()); } + T* data() { return reinterpret_cast(Data()); } + + // Copy data from a span with endian conversion. + // If this Array and the span overlap, the behavior is undefined. + void CopyFromSpan(flatbuffers::span src) { + const auto p1 = reinterpret_cast(src.data()); + const auto p2 = Data(); + FLATBUFFERS_ASSERT(!(p1 >= p2 && p1 < (p2 + length)) && + !(p2 >= p1 && p2 < (p1 + length))); + (void)p1; + (void)p2; + CopyFromSpanImpl(flatbuffers::bool_constant(), src); + } + + protected: + void MutateImpl(flatbuffers::true_type, uoffset_t i, const T& val) { + FLATBUFFERS_ASSERT(i < size()); + WriteScalar(data() + i, val); + } + + void MutateImpl(flatbuffers::false_type, uoffset_t i, const T& val) { + *(GetMutablePointer(i)) = val; + } + + void CopyFromSpanImpl(flatbuffers::true_type, + flatbuffers::span src) { + // Use std::memcpy() instead of std::copy() to avoid performance degradation + // due to aliasing if T is char or unsigned char. + // The size is known at compile time, so memcpy would be inlined. + std::memcpy(data(), src.data(), length * sizeof(T)); + } + + // Copy data from flatbuffers::span with endian conversion. + void CopyFromSpanImpl(flatbuffers::false_type, + flatbuffers::span src) { + for (size_type k = 0; k < length; k++) { + Mutate(k, src[k]); + } + } + + // This class is only used to access pre-existing data. Don't ever + // try to construct these manually. + // 'constexpr' allows us to use 'size()' at compile time. + // @note Must not use 'FLATBUFFERS_CONSTEXPR' here, as const is not allowed on + // a constructor. +#if defined(__cpp_constexpr) + constexpr Array(); +#else + Array(); +#endif + + uint8_t data_[length * sizeof(T)]; + + private: + // This class is a pointer. Copying will therefore create an invalid object. + // Private and unimplemented copy constructor. + Array(const Array&); + Array& operator=(const Array&); +}; + +// Specialization for Array[struct] with access using Offset pointer. +// This specialization used by idl_gen_text.cpp. +template class OffsetT> +class Array, length> { + static_assert(flatbuffers::is_same::value, "unexpected type T"); + + public: + typedef const void* return_type; + typedef uint16_t size_type; + + const uint8_t* Data() const { return data_; } + + // Make idl_gen_text.cpp::PrintContainer happy. + return_type operator[](uoffset_t) const { + FLATBUFFERS_ASSERT(false); + return nullptr; + } + + private: + // This class is only used to access pre-existing data. + Array(); + Array(const Array&); + Array& operator=(const Array&); + + uint8_t data_[1]; +}; + +template +FLATBUFFERS_CONSTEXPR_CPP11 flatbuffers::span make_span(Array& arr) + FLATBUFFERS_NOEXCEPT { + static_assert( + Array::is_span_observable, + "wrong type U, only plain struct, LE-scalar, or byte types are allowed"); + return span(arr.data(), N); +} + +template +FLATBUFFERS_CONSTEXPR_CPP11 flatbuffers::span make_span( + const Array& arr) FLATBUFFERS_NOEXCEPT { + static_assert( + Array::is_span_observable, + "wrong type U, only plain struct, LE-scalar, or byte types are allowed"); + return span(arr.data(), N); +} + +template +FLATBUFFERS_CONSTEXPR_CPP11 flatbuffers::span +make_bytes_span(Array& arr) FLATBUFFERS_NOEXCEPT { + static_assert(Array::is_span_observable, + "internal error, Array might hold only scalars or structs"); + return span(arr.Data(), sizeof(U) * N); +} + +template +FLATBUFFERS_CONSTEXPR_CPP11 flatbuffers::span +make_bytes_span(const Array& arr) FLATBUFFERS_NOEXCEPT { + static_assert(Array::is_span_observable, + "internal error, Array might hold only scalars or structs"); + return span(arr.Data(), sizeof(U) * N); +} + +// Cast a raw T[length] to a raw flatbuffers::Array +// without endian conversion. Use with care. +// TODO: move these Cast-methods to `internal` namespace. +template +Array& CastToArray(T (&arr)[length]) { + return *reinterpret_cast*>(arr); +} + +template +const Array& CastToArray(const T (&arr)[length]) { + return *reinterpret_cast*>(arr); +} + +template +Array& CastToArrayOfEnum(T (&arr)[length]) { + static_assert(sizeof(E) == sizeof(T), "invalid enum type E"); + return *reinterpret_cast*>(arr); +} + +template +const Array& CastToArrayOfEnum(const T (&arr)[length]) { + static_assert(sizeof(E) == sizeof(T), "invalid enum type E"); + return *reinterpret_cast*>(arr); +} + +template +bool operator==(const Array& lhs, + const Array& rhs) noexcept { + return std::addressof(lhs) == std::addressof(rhs) || + (lhs.size() == rhs.size() && + std::memcmp(lhs.Data(), rhs.Data(), rhs.size() * sizeof(T)) == 0); +} + +} // namespace flatbuffers + +#endif // FLATBUFFERS_ARRAY_H_ diff --git a/external/flatbuffers/include/flatbuffers/base.h b/external/flatbuffers/include/flatbuffers/base.h new file mode 100644 index 00000000..1d2ab585 --- /dev/null +++ b/external/flatbuffers/include/flatbuffers/base.h @@ -0,0 +1,503 @@ +#ifndef FLATBUFFERS_BASE_H_ +#define FLATBUFFERS_BASE_H_ + +// clang-format off + +// If activate should be declared and included first. +#if defined(FLATBUFFERS_MEMORY_LEAK_TRACKING) && \ + defined(_MSC_VER) && defined(_DEBUG) + // The _CRTDBG_MAP_ALLOC inside will replace + // calloc/free (etc) to its debug version using #define directives. + #define _CRTDBG_MAP_ALLOC + #include + #include + // Replace operator new by trace-enabled version. + #define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__) + #define new DEBUG_NEW +#endif + +#if !defined(FLATBUFFERS_ASSERT) +#include +#define FLATBUFFERS_ASSERT assert +#elif defined(FLATBUFFERS_ASSERT_INCLUDE) +// Include file with forward declaration +#include FLATBUFFERS_ASSERT_INCLUDE +#endif + +#ifndef ARDUINO +#include +#endif + +#include +#include +#include + +#if defined(ARDUINO) && !defined(ARDUINOSTL_M_H) && defined(__AVR__) + #include +#else + #include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__unix__) && !defined(FLATBUFFERS_LOCALE_INDEPENDENT) + #include +#endif + +#ifdef __ANDROID__ + #include +#endif + +#if defined(__ICCARM__) +#include +#endif + +// Note the __clang__ check is needed, because clang presents itself +// as an older GNUC compiler (4.2). +// Clang 3.3 and later implement all of the ISO C++ 2011 standard. +// Clang 3.4 and later implement all of the ISO C++ 2014 standard. +// http://clang.llvm.org/cxx_status.html + +// Note the MSVC value '__cplusplus' may be incorrect: +// The '__cplusplus' predefined macro in the MSVC stuck at the value 199711L, +// indicating (erroneously!) that the compiler conformed to the C++98 Standard. +// This value should be correct starting from MSVC2017-15.7-Preview-3. +// The '__cplusplus' will be valid only if MSVC2017-15.7-P3 and the `/Zc:__cplusplus` switch is set. +// Workaround (for details see MSDN): +// Use the _MSC_VER and _MSVC_LANG definition instead of the __cplusplus for compatibility. +// The _MSVC_LANG macro reports the Standard version regardless of the '/Zc:__cplusplus' switch. + +#if defined(__GNUC__) && !defined(__clang__) + #define FLATBUFFERS_GCC (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +#else + #define FLATBUFFERS_GCC 0 +#endif + +#if defined(__clang__) + #define FLATBUFFERS_CLANG (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) +#else + #define FLATBUFFERS_CLANG 0 +#endif + +/// @cond FLATBUFFERS_INTERNAL +#if __cplusplus <= 199711L && \ + (!defined(_MSC_VER) || _MSC_VER < 1600) && \ + (!defined(__GNUC__) || \ + (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__ < 40400)) + #error A C++11 compatible compiler with support for the auto typing is \ + required for FlatBuffers. + #error __cplusplus _MSC_VER __GNUC__ __GNUC_MINOR__ __GNUC_PATCHLEVEL__ +#endif + +#if !defined(__clang__) && \ + defined(__GNUC__) && \ + (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__ < 40600) + // Backwards compatibility for g++ 4.4, and 4.5 which don't have the nullptr + // and constexpr keywords. Note the __clang__ check is needed, because clang + // presents itself as an older GNUC compiler. + #ifndef nullptr_t + const class nullptr_t { + public: + template inline operator T*() const { return 0; } + private: + void operator&() const; + } nullptr = {}; + #endif + #ifndef constexpr + #define constexpr const + #endif +#endif + +// The wire format uses a little endian encoding (since that's efficient for +// the common platforms). +#if defined(__s390x__) + #define FLATBUFFERS_LITTLEENDIAN 0 +#endif // __s390x__ +#if !defined(FLATBUFFERS_LITTLEENDIAN) + #if defined(__GNUC__) || defined(__clang__) || defined(__ICCARM__) + #if (defined(__BIG_ENDIAN__) || \ + (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) + #define FLATBUFFERS_LITTLEENDIAN 0 + #else + #define FLATBUFFERS_LITTLEENDIAN 1 + #endif // __BIG_ENDIAN__ + #elif defined(_MSC_VER) + #if defined(_M_PPC) + #define FLATBUFFERS_LITTLEENDIAN 0 + #else + #define FLATBUFFERS_LITTLEENDIAN 1 + #endif + #else + #error Unable to determine endianness, define FLATBUFFERS_LITTLEENDIAN. + #endif +#endif // !defined(FLATBUFFERS_LITTLEENDIAN) + +#define FLATBUFFERS_VERSION_MAJOR 25 +#define FLATBUFFERS_VERSION_MINOR 12 +#define FLATBUFFERS_VERSION_REVISION 19 +#define FLATBUFFERS_STRING_EXPAND(X) #X +#define FLATBUFFERS_STRING(X) FLATBUFFERS_STRING_EXPAND(X) +namespace flatbuffers { + // Returns version as string "MAJOR.MINOR.REVISION". + const char* FLATBUFFERS_VERSION(); +} + +#if (!defined(_MSC_VER) || _MSC_VER > 1600) && \ + (!defined(__GNUC__) || (__GNUC__ * 100 + __GNUC_MINOR__ >= 407)) || \ + defined(__clang__) + #define FLATBUFFERS_FINAL_CLASS final + #define FLATBUFFERS_OVERRIDE override + #define FLATBUFFERS_EXPLICIT_CPP11 explicit + #define FLATBUFFERS_VTABLE_UNDERLYING_TYPE : ::flatbuffers::voffset_t +#else + #define FLATBUFFERS_FINAL_CLASS + #define FLATBUFFERS_OVERRIDE + #define FLATBUFFERS_EXPLICIT_CPP11 + #define FLATBUFFERS_VTABLE_UNDERLYING_TYPE +#endif + +#if (!defined(_MSC_VER) || _MSC_VER >= 1900) && \ + (!defined(__GNUC__) || (__GNUC__ * 100 + __GNUC_MINOR__ >= 406)) || \ + (defined(__cpp_constexpr) && __cpp_constexpr >= 200704) + #define FLATBUFFERS_CONSTEXPR constexpr + #define FLATBUFFERS_CONSTEXPR_CPP11 constexpr + #define FLATBUFFERS_CONSTEXPR_DEFINED +#else + #define FLATBUFFERS_CONSTEXPR const + #define FLATBUFFERS_CONSTEXPR_CPP11 +#endif + +#if (defined(__cplusplus) && __cplusplus >= 201402L) || \ + (defined(__cpp_constexpr) && __cpp_constexpr >= 201304) + #define FLATBUFFERS_CONSTEXPR_CPP14 FLATBUFFERS_CONSTEXPR_CPP11 +#else + #define FLATBUFFERS_CONSTEXPR_CPP14 +#endif + +#if (defined(__GXX_EXPERIMENTAL_CXX0X__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 406)) || \ + (defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 190023026)) || \ + defined(__clang__) + #define FLATBUFFERS_NOEXCEPT noexcept +#else + #define FLATBUFFERS_NOEXCEPT +#endif + +// NOTE: the FLATBUFFERS_DELETE_FUNC macro may change the access mode to +// private, so be sure to put it at the end or reset access mode explicitly. +#if (!defined(_MSC_VER) || _MSC_FULL_VER >= 180020827) && \ + (!defined(__GNUC__) || (__GNUC__ * 100 + __GNUC_MINOR__ >= 404)) || \ + defined(__clang__) + #define FLATBUFFERS_DELETE_FUNC(func) func = delete +#else + #define FLATBUFFERS_DELETE_FUNC(func) private: func +#endif + +#if (!defined(_MSC_VER) || _MSC_VER >= 1900) && \ + (!defined(__GNUC__) || (__GNUC__ * 100 + __GNUC_MINOR__ >= 409)) || \ + defined(__clang__) + #define FLATBUFFERS_DEFAULT_DECLARATION +#endif + +// Check if we can use template aliases +// Not possible if Microsoft Compiler before 2012 +// Possible is the language feature __cpp_alias_templates is defined well +// Or possible if the C++ std is C+11 or newer +#if (defined(_MSC_VER) && _MSC_VER > 1700 /* MSVC2012 */) \ + || (defined(__cpp_alias_templates) && __cpp_alias_templates >= 200704) \ + || (defined(__cplusplus) && __cplusplus >= 201103L) + #define FLATBUFFERS_TEMPLATES_ALIASES +#endif + +#ifndef FLATBUFFERS_HAS_STRING_VIEW + // Only provide flatbuffers::string_view if __has_include can be used + // to detect a header that provides an implementation + #if defined(__has_include) + // Check for std::string_view (in c++17) + #if __has_include() && (__cplusplus >= 201606 || (defined(_HAS_CXX17) && _HAS_CXX17)) + #include + namespace flatbuffers { + typedef std::string_view string_view; + } + #define FLATBUFFERS_HAS_STRING_VIEW 1 + // Check for std::experimental::string_view (in c++14, compiler-dependent) + #elif __has_include() && (__cplusplus >= 201411) + #include + namespace flatbuffers { + typedef std::experimental::string_view string_view; + } + #define FLATBUFFERS_HAS_STRING_VIEW 1 + // Check for absl::string_view + #elif __has_include("absl/strings/string_view.h") && \ + __has_include("absl/base/config.h") && \ + (__cplusplus >= 201411) + #include "absl/base/config.h" + #if !defined(ABSL_USES_STD_STRING_VIEW) + #include "absl/strings/string_view.h" + namespace flatbuffers { + typedef absl::string_view string_view; + } + #define FLATBUFFERS_HAS_STRING_VIEW 1 + #endif + #endif + #endif // __has_include +#endif // !FLATBUFFERS_HAS_STRING_VIEW + +#ifndef FLATBUFFERS_GENERAL_HEAP_ALLOC_OK + // Allow heap allocations to be used + #define FLATBUFFERS_GENERAL_HEAP_ALLOC_OK 1 +#endif // !FLATBUFFERS_GENERAL_HEAP_ALLOC_OK + +#ifndef FLATBUFFERS_HAS_NEW_STRTOD + // Modern (C++11) strtod and strtof functions are available for use. + // 1) nan/inf strings as argument of strtod; + // 2) hex-float as argument of strtod/strtof. + #if (defined(_MSC_VER) && _MSC_VER >= 1900) || \ + (defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 409)) || \ + (defined(__clang__)) + #define FLATBUFFERS_HAS_NEW_STRTOD 1 + #endif +#endif // !FLATBUFFERS_HAS_NEW_STRTOD + +#ifndef FLATBUFFERS_LOCALE_INDEPENDENT + // Enable locale independent functions {strtof_l, strtod_l,strtoll_l, + // strtoull_l} on platforms that support them. + #if (defined(_MSC_VER) && _MSC_VER >= 1800) || \ + (defined(__ANDROID__) && defined(__ANDROID_API__) && __ANDROID_API__>= 26) || \ + (defined(_XOPEN_VERSION) && (_XOPEN_VERSION >= 700)) && \ + !defined(__Fuchsia__) + #define FLATBUFFERS_LOCALE_INDEPENDENT 1 + #else + #define FLATBUFFERS_LOCALE_INDEPENDENT 0 + #endif +#endif // !FLATBUFFERS_LOCALE_INDEPENDENT + +// Suppress Undefined Behavior Sanitizer (recoverable only). Usage: +// - FLATBUFFERS_SUPPRESS_UBSAN("undefined") +// - FLATBUFFERS_SUPPRESS_UBSAN("signed-integer-overflow") +#if defined(__clang__) && (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >=7)) + #define FLATBUFFERS_SUPPRESS_UBSAN(type) __attribute__((no_sanitize(type))) +#elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 409) + #define FLATBUFFERS_SUPPRESS_UBSAN(type) __attribute__((no_sanitize_undefined)) +#else + #define FLATBUFFERS_SUPPRESS_UBSAN(type) +#endif + +namespace flatbuffers { + // This is constexpr function used for checking compile-time constants. + // Avoid `#pragma warning(disable: 4127) // C4127: expression is constant`. + template FLATBUFFERS_CONSTEXPR inline bool IsConstTrue(T t) { + return !!t; + } +} + +// Enable C++ attribute [[]] if std:c++17 or higher. +#if ((__cplusplus >= 201703L) \ + || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L))) + // All attributes unknown to an implementation are ignored without causing an error. + #define FLATBUFFERS_ATTRIBUTE(attr) attr + + #define FLATBUFFERS_FALLTHROUGH() [[fallthrough]] +#else + #define FLATBUFFERS_ATTRIBUTE(attr) + + #if FLATBUFFERS_CLANG >= 30800 + #define FLATBUFFERS_FALLTHROUGH() [[clang::fallthrough]] + #elif FLATBUFFERS_GCC >= 70300 + #define FLATBUFFERS_FALLTHROUGH() [[gnu::fallthrough]] + #else + #define FLATBUFFERS_FALLTHROUGH() + #endif +#endif + +/// @endcond + +/// @file +namespace flatbuffers { + +/// @cond FLATBUFFERS_INTERNAL +// Our default offset / size type, 32bit on purpose on 64bit systems. +// Also, using a consistent offset type maintains compatibility of serialized +// offset values between 32bit and 64bit systems. +typedef uint32_t uoffset_t; +typedef uint64_t uoffset64_t; + +// Signed offsets for references that can go in both directions. +typedef int32_t soffset_t; +typedef int64_t soffset64_t; + +// Offset/index used in v-tables, can be changed to uint8_t in +// format forks to save a bit of space if desired. +typedef uint16_t voffset_t; + +typedef uintmax_t largest_scalar_t; + +// In 32bits, this evaluates to 2GB - 1 +#define FLATBUFFERS_MAX_BUFFER_SIZE (std::numeric_limits<::flatbuffers::soffset_t>::max)() +#define FLATBUFFERS_MAX_64_BUFFER_SIZE (std::numeric_limits<::flatbuffers::soffset64_t>::max)() + +// The minimum size buffer that can be a valid flatbuffer. +// Includes the offset to the root table (uoffset_t), the offset to the vtable +// of the root table (soffset_t), the size of the vtable (uint16_t), and the +// size of the referring table (uint16_t). +#define FLATBUFFERS_MIN_BUFFER_SIZE sizeof(::flatbuffers::uoffset_t) + \ + sizeof(::flatbuffers::soffset_t) + sizeof(uint16_t) + sizeof(uint16_t) + +// We support aligning the contents of buffers up to this size. +#ifndef FLATBUFFERS_MAX_ALIGNMENT + #define FLATBUFFERS_MAX_ALIGNMENT 32 +#endif + +/// @brief The length of a FlatBuffer file header. +static const size_t kFileIdentifierLength = 4; + +inline bool VerifyAlignmentRequirements(size_t align, size_t min_align = 1) { + return (min_align <= align) && (align <= (FLATBUFFERS_MAX_ALIGNMENT)) && + (align & (align - 1)) == 0; // must be power of 2 +} + +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable: 4127) // C4127: conditional expression is constant +#endif + +template T EndianSwap(T t) { + #if defined(_MSC_VER) + #define FLATBUFFERS_BYTESWAP16 _byteswap_ushort + #define FLATBUFFERS_BYTESWAP32 _byteswap_ulong + #define FLATBUFFERS_BYTESWAP64 _byteswap_uint64 + #elif defined(__ICCARM__) + #define FLATBUFFERS_BYTESWAP16 __REV16 + #define FLATBUFFERS_BYTESWAP32 __REV + #define FLATBUFFERS_BYTESWAP64(x) \ + ((__REV(static_cast(x >> 32U))) | (static_cast(__REV(static_cast(x)))) << 32U) + #else + #if defined(__GNUC__) && __GNUC__ * 100 + __GNUC_MINOR__ < 408 && !defined(__clang__) + // __builtin_bswap16 was missing prior to GCC 4.8. + #define FLATBUFFERS_BYTESWAP16(x) \ + static_cast(__builtin_bswap32(static_cast(x) << 16)) + #else + #define FLATBUFFERS_BYTESWAP16 __builtin_bswap16 + #endif + #define FLATBUFFERS_BYTESWAP32 __builtin_bswap32 + #define FLATBUFFERS_BYTESWAP64 __builtin_bswap64 + #endif + if (sizeof(T) == 1) { // Compile-time if-then's. + return t; + } else if (sizeof(T) == 2) { + union { T t; uint16_t i; } u = { t }; + u.i = FLATBUFFERS_BYTESWAP16(u.i); + return u.t; + } else if (sizeof(T) == 4) { + union { T t; uint32_t i; } u = { t }; + u.i = FLATBUFFERS_BYTESWAP32(u.i); + return u.t; + } else if (sizeof(T) == 8) { + union { T t; uint64_t i; } u = { t }; + u.i = FLATBUFFERS_BYTESWAP64(u.i); + return u.t; + } else { + FLATBUFFERS_ASSERT(0); + return t; + } +} + +#if defined(_MSC_VER) + #pragma warning(pop) +#endif + + +template T EndianScalar(T t) { + #if FLATBUFFERS_LITTLEENDIAN + return t; + #else + return EndianSwap(t); + #endif +} + +template +// UBSAN: C++ aliasing type rules, see std::bit_cast<> for details. +FLATBUFFERS_SUPPRESS_UBSAN("alignment") +T ReadScalar(const void *p) { + return EndianScalar(*reinterpret_cast(p)); +} + +// See https://github.com/google/flatbuffers/issues/5950 + +#if (FLATBUFFERS_GCC >= 100000) && (FLATBUFFERS_GCC < 110000) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wstringop-overflow" +#endif + +template +// UBSAN: C++ aliasing type rules, see std::bit_cast<> for details. +FLATBUFFERS_SUPPRESS_UBSAN("alignment") +void WriteScalar(void *p, T t) { + *reinterpret_cast(p) = EndianScalar(t); +} + +template struct Offset; +template FLATBUFFERS_SUPPRESS_UBSAN("alignment") void WriteScalar(void *p, Offset t) { + *reinterpret_cast(p) = EndianScalar(t.o); +} + +#if (FLATBUFFERS_GCC >= 100000) && (FLATBUFFERS_GCC < 110000) + #pragma GCC diagnostic pop +#endif + +// Computes how many bytes you'd have to pad to be able to write an +// "scalar_size" scalar if the buffer had grown to "buf_size" (downwards in +// memory). +FLATBUFFERS_SUPPRESS_UBSAN("unsigned-integer-overflow") +inline size_t PaddingBytes(size_t buf_size, size_t scalar_size) { + return ((~buf_size) + 1) & (scalar_size - 1); +} + +#if !defined(_MSC_VER) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wfloat-equal" +#endif +// Generic 'operator==' with conditional specialisations. +// T e - new value of a scalar field. +// T def - default of scalar (is known at compile-time). +template inline bool IsTheSameAs(T e, T def) { return e == def; } +#if !defined(_MSC_VER) + #pragma GCC diagnostic pop +#endif + +#if defined(FLATBUFFERS_NAN_DEFAULTS) && \ + defined(FLATBUFFERS_HAS_NEW_STRTOD) && (FLATBUFFERS_HAS_NEW_STRTOD > 0) +// Like `operator==(e, def)` with weak NaN if T=(float|double). +template inline bool IsFloatTheSameAs(T e, T def) { + return (e == def) || ((def != def) && (e != e)); +} +template<> inline bool IsTheSameAs(float e, float def) { + return IsFloatTheSameAs(e, def); +} +template<> inline bool IsTheSameAs(double e, double def) { + return IsFloatTheSameAs(e, def); +} +#endif + +// Check 'v' is out of closed range [low; high]. +// Workaround for GCC warning [-Werror=type-limits]: +// comparison is always true due to limited range of data type. +template +inline bool IsOutRange(const T &v, const T &low, const T &high) { + return (v < low) || (high < v); +} + +// Check 'v' is in closed range [low; high]. +template +inline bool IsInRange(const T &v, const T &low, const T &high) { + return !IsOutRange(v, low, high); +} + +} // namespace flatbuffers +#endif // FLATBUFFERS_BASE_H_ diff --git a/external/flatbuffers/include/flatbuffers/buffer.h b/external/flatbuffers/include/flatbuffers/buffer.h new file mode 100644 index 00000000..154d187a --- /dev/null +++ b/external/flatbuffers/include/flatbuffers/buffer.h @@ -0,0 +1,225 @@ +/* + * Copyright 2021 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLATBUFFERS_BUFFER_H_ +#define FLATBUFFERS_BUFFER_H_ + +#include + +#include "flatbuffers/base.h" +#include "flatbuffers/stl_emulation.h" + +namespace flatbuffers { + +// Wrapper for uoffset_t to allow safe template specialization. +// Value is allowed to be 0 to indicate a null object (see e.g. AddOffset). +template +struct Offset { + // The type of offset to use. + typedef uoffset_t offset_type; + + offset_type o; + Offset() : o(0) {} + Offset(const offset_type _o) : o(_o) {} + Offset<> Union() const { return o; } + bool IsNull() const { return !o; } +}; + +template +struct is_specialisation_of_Offset : false_type {}; +template +struct is_specialisation_of_Offset> : true_type {}; + +// Wrapper for uoffset64_t Offsets. +template +struct Offset64 { + // The type of offset to use. + typedef uoffset64_t offset_type; + + offset_type o; + Offset64() : o(0) {} + Offset64(const offset_type offset) : o(offset) {} + Offset64<> Union() const { return o; } + bool IsNull() const { return !o; } +}; + +template +struct is_specialisation_of_Offset64 : false_type {}; +template +struct is_specialisation_of_Offset64> : true_type {}; + +// Litmus check for ensuring the Offsets are the expected size. +static_assert(sizeof(Offset<>) == 4, "Offset has wrong size"); +static_assert(sizeof(Offset64<>) == 8, "Offset64 has wrong size"); + +inline void EndianCheck() { + int endiantest = 1; + // If this fails, see FLATBUFFERS_LITTLEENDIAN above. + FLATBUFFERS_ASSERT(*reinterpret_cast(&endiantest) == + FLATBUFFERS_LITTLEENDIAN); + (void)endiantest; +} + +template +FLATBUFFERS_CONSTEXPR size_t AlignOf() { + // clang-format off + #ifdef _MSC_VER + return __alignof(T); + #else + #ifndef alignof + return __alignof__(T); + #else + return alignof(T); + #endif + #endif + // clang-format on +} + +// Lexicographically compare two strings (possibly containing nulls), and +// return true if the first is less than the second. +static inline bool StringLessThan(const char* a_data, uoffset_t a_size, + const char* b_data, uoffset_t b_size) { + const auto cmp = memcmp(a_data, b_data, (std::min)(a_size, b_size)); + return cmp == 0 ? a_size < b_size : cmp < 0; +} + +// When we read serialized data from memory, in the case of most scalars, +// we want to just read T, but in the case of Offset, we want to actually +// perform the indirection and return a pointer. +// The template specialization below does just that. +// It is wrapped in a struct since function templates can't overload on the +// return type like this. +// The typedef is for the convenience of callers of this function +// (avoiding the need for a trailing return decltype) +template +struct IndirectHelper { + typedef T return_type; + typedef T mutable_return_type; + static const size_t element_stride = sizeof(T); + + static return_type Read(const uint8_t* p, const size_t i) { + return EndianScalar((reinterpret_cast(p))[i]); + } + static mutable_return_type Read(uint8_t* p, const size_t i) { + return reinterpret_cast( + Read(const_cast(p), i)); + } +}; + +// For vector of Offsets. +template class OffsetT> +struct IndirectHelper> { + typedef const T* return_type; + typedef T* mutable_return_type; + typedef typename OffsetT::offset_type offset_type; + static const offset_type element_stride = sizeof(offset_type); + + static return_type Read(const uint8_t* const p, const offset_type i) { + // Offsets are relative to themselves, so first update the pointer to + // point to the offset location. + const uint8_t* const offset_location = p + i * element_stride; + + // Then read the scalar value of the offset (which may be 32 or 64-bits) and + // then determine the relative location from the offset location. + return reinterpret_cast( + offset_location + ReadScalar(offset_location)); + } + static mutable_return_type Read(uint8_t* const p, const offset_type i) { + // Offsets are relative to themselves, so first update the pointer to + // point to the offset location. + uint8_t* const offset_location = p + i * element_stride; + + // Then read the scalar value of the offset (which may be 32 or 64-bits) and + // then determine the relative location from the offset location. + return reinterpret_cast( + offset_location + ReadScalar(offset_location)); + } +}; + +// For vector of structs. +template +struct IndirectHelper< + T, typename std::enable_if< + !std::is_scalar::type>::value && + !is_specialisation_of_Offset::value && + !is_specialisation_of_Offset64::value>::type> { + private: + typedef typename std::remove_pointer::type>::type + pointee_type; + + public: + typedef const pointee_type* return_type; + typedef pointee_type* mutable_return_type; + static const size_t element_stride = sizeof(pointee_type); + + static return_type Read(const uint8_t* const p, const size_t i) { + // Structs are stored inline, relative to the first struct pointer. + return reinterpret_cast(p + i * element_stride); + } + static mutable_return_type Read(uint8_t* const p, const size_t i) { + // Structs are stored inline, relative to the first struct pointer. + return reinterpret_cast(p + i * element_stride); + } +}; + +/// @brief Get a pointer to the file_identifier section of the buffer. +/// @return Returns a const char pointer to the start of the file_identifier +/// characters in the buffer. The returned char * has length +/// 'flatbuffers::FlatBufferBuilder::kFileIdentifierLength'. +/// This function is UNDEFINED for FlatBuffers whose schema does not include +/// a file_identifier (likely points at padding or the start of a the root +/// vtable). +inline const char* GetBufferIdentifier(const void* buf, + bool size_prefixed = false) { + return reinterpret_cast(buf) + + ((size_prefixed) ? 2 * sizeof(uoffset_t) : sizeof(uoffset_t)); +} + +// Helper to see if the identifier in a buffer has the expected value. +inline bool BufferHasIdentifier(const void* buf, const char* identifier, + bool size_prefixed = false) { + return strncmp(GetBufferIdentifier(buf, size_prefixed), identifier, + flatbuffers::kFileIdentifierLength) == 0; +} + +/// @cond FLATBUFFERS_INTERNAL +// Helpers to get a typed pointer to the root object contained in the buffer. +template +T* GetMutableRoot(void* buf) { + if (!buf) return nullptr; + EndianCheck(); + return reinterpret_cast(reinterpret_cast(buf) + + EndianScalar(*reinterpret_cast(buf))); +} + +template +T* GetMutableSizePrefixedRoot(void* buf) { + return GetMutableRoot(reinterpret_cast(buf) + sizeof(SizeT)); +} + +template +const T* GetRoot(const void* buf) { + return GetMutableRoot(const_cast(buf)); +} + +template +const T* GetSizePrefixedRoot(const void* buf) { + return GetRoot(reinterpret_cast(buf) + sizeof(SizeT)); +} + +} // namespace flatbuffers + +#endif // FLATBUFFERS_BUFFER_H_ diff --git a/external/flatbuffers/include/flatbuffers/buffer_ref.h b/external/flatbuffers/include/flatbuffers/buffer_ref.h new file mode 100644 index 00000000..746903eb --- /dev/null +++ b/external/flatbuffers/include/flatbuffers/buffer_ref.h @@ -0,0 +1,54 @@ +/* + * Copyright 2021 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLATBUFFERS_BUFFER_REF_H_ +#define FLATBUFFERS_BUFFER_REF_H_ + +#include "flatbuffers/base.h" +#include "flatbuffers/verifier.h" + +namespace flatbuffers { + +// Convenient way to bundle a buffer and its length, to pass it around +// typed by its root. +// A BufferRef does not own its buffer. +struct BufferRefBase {}; // for std::is_base_of + +template +struct BufferRef : BufferRefBase { + BufferRef() : buf(nullptr), len(0), must_free(false) {} + BufferRef(uint8_t* _buf, uoffset_t _len) + : buf(_buf), len(_len), must_free(false) {} + + ~BufferRef() { + if (must_free) free(buf); + } + + const T* GetRoot() const { return flatbuffers::GetRoot(buf); } + + bool Verify() { + Verifier verifier(buf, len); + return verifier.VerifyBuffer(nullptr); + } + + uint8_t* buf; + uoffset_t len; + bool must_free; +}; + +} // namespace flatbuffers + +#endif // FLATBUFFERS_BUFFER_REF_H_ diff --git a/external/flatbuffers/include/flatbuffers/code_generator.h b/external/flatbuffers/include/flatbuffers/code_generator.h new file mode 100644 index 00000000..bfd771da --- /dev/null +++ b/external/flatbuffers/include/flatbuffers/code_generator.h @@ -0,0 +1,99 @@ +/* + * Copyright 2023 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLATBUFFERS_CODE_GENERATOR_H_ +#define FLATBUFFERS_CODE_GENERATOR_H_ + +#include + +#include "flatbuffers/file_manager.h" +#include "flatbuffers/idl.h" + +namespace flatbuffers { + +struct CodeGenOptions { + std::string output_path; + FileSaver* file_saver{nullptr}; +}; + +// A code generator interface for producing converting flatbuffer schema into +// code. +class CodeGenerator { + public: + virtual ~CodeGenerator() = default; + + enum Status { + OK = 0, + ERROR = 1, + FAILED_VERIFICATION = 2, + NOT_IMPLEMENTED = 3 + }; + + std::string status_detail; + + // Generate code from the provided `parser`. + // + // DEPRECATED: prefer using the other overload of GenerateCode for bfbs. + virtual Status GenerateCode(const Parser& parser, const std::string& path, + const std::string& filename) = 0; + + // Generate code from the provided `parser` and place it in the output. + virtual Status GenerateCodeString(const Parser& parser, + const std::string& filename, + std::string& output) { + (void)parser; + (void)filename; + (void)output; + return Status::NOT_IMPLEMENTED; + } + + // Generate code from the provided `buffer` of given `length`. The buffer is a + // serialized reflection.fbs. + virtual Status GenerateCode(const uint8_t* buffer, int64_t length, + const CodeGenOptions& options) = 0; + + virtual Status GenerateMakeRule(const Parser& parser, const std::string& path, + const std::string& filename, + std::string& output) = 0; + + virtual Status GenerateGrpcCode(const Parser& parser, const std::string& path, + const std::string& filename) = 0; + + virtual Status GenerateRootFile(const Parser& parser, + const std::string& path) = 0; + + virtual bool IsSchemaOnly() const = 0; + + virtual bool SupportsBfbsGeneration() const = 0; + + virtual bool SupportsRootFileGeneration() const = 0; + + virtual IDLOptions::Language Language() const = 0; + + virtual std::string LanguageName() const = 0; + + protected: + CodeGenerator() = default; + + private: + // Copying is not supported. + CodeGenerator(const CodeGenerator&) = delete; + CodeGenerator& operator=(const CodeGenerator&) = delete; +}; + +} // namespace flatbuffers + +#endif // FLATBUFFERS_CODE_GENERATOR_H_ diff --git a/external/flatbuffers/include/flatbuffers/code_generators.h b/external/flatbuffers/include/flatbuffers/code_generators.h new file mode 100644 index 00000000..d284ac5a --- /dev/null +++ b/external/flatbuffers/include/flatbuffers/code_generators.h @@ -0,0 +1,238 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLATBUFFERS_CODE_GENERATORS_H_ +#define FLATBUFFERS_CODE_GENERATORS_H_ + +#include +#include + +#include "flatbuffers/idl.h" + +namespace flatbuffers { + +// Utility class to assist in generating code through use of text templates. +// +// Example code: +// CodeWriter code("\t"); +// code.SetValue("NAME", "Foo"); +// code += "void {{NAME}}() { printf("%s", "{{NAME}}"); }"; +// code.SetValue("NAME", "Bar"); +// code += "void {{NAME}}() { printf("%s", "{{NAME}}"); }"; +// std::cout << code.ToString() << std::endl; +// +// Output: +// void Foo() { printf("%s", "Foo"); } +// void Bar() { printf("%s", "Bar"); } +class CodeWriter { + public: + CodeWriter(std::string pad = std::string()) + : pad_(pad), cur_ident_lvl_(0), ignore_ident_(false) {} + + // Clears the current "written" code. + void Clear() { + stream_.str(""); + stream_.clear(); + } + + // Associates a key with a value. All subsequent calls to operator+=, where + // the specified key is contained in {{ and }} delimiters will be replaced by + // the given value. + void SetValue(const std::string& key, const std::string& value) { + value_map_[key] = value; + } + + std::string GetValue(const std::string& key) const { + const auto it = value_map_.find(key); + return it == value_map_.end() ? "" : it->second; + } + + // Appends the given text to the generated code as well as a newline + // character. Any text within {{ and }} delimiters is replaced by values + // previously stored in the CodeWriter by calling SetValue above. The newline + // will be suppressed if the text ends with the \\ character. + void operator+=(std::string text); + + // Returns the current contents of the CodeWriter as a std::string. + std::string ToString() const { return stream_.str(); } + + // Increase ident level for writing code + void IncrementIdentLevel() { cur_ident_lvl_++; } + // Decrease ident level for writing code + void DecrementIdentLevel() { + if (cur_ident_lvl_) cur_ident_lvl_--; + } + + void SetPadding(const std::string& padding) { pad_ = padding; } + + private: + std::map value_map_; + std::stringstream stream_; + std::string pad_; + int cur_ident_lvl_; + bool ignore_ident_; + + // Add ident padding (tab or space) based on ident level + void AppendIdent(std::stringstream& stream); +}; + +class BaseGenerator { + public: + virtual bool generate() = 0; + + static std::string NamespaceDir(const Parser& parser, const std::string& path, + const Namespace& ns, + const bool dasherize = false); + + std::string GeneratedFileName(const std::string& path, + const std::string& file_name, + const IDLOptions& options) const; + + protected: + BaseGenerator(const Parser& parser, const std::string& path, + const std::string& file_name, std::string qualifying_start, + std::string qualifying_separator, std::string default_extension) + : parser_(parser), + path_(path), + file_name_(file_name), + qualifying_start_(qualifying_start), + qualifying_separator_(qualifying_separator), + default_extension_(default_extension) {} + virtual ~BaseGenerator() {} + + // No copy/assign. + BaseGenerator& operator=(const BaseGenerator&); + BaseGenerator(const BaseGenerator&); + + std::string NamespaceDir(const Namespace& ns, + const bool dasherize = false) const; + + static const char* FlatBuffersGeneratedWarning(); + + static std::string FullNamespace(const char* separator, const Namespace& ns); + + static std::string LastNamespacePart(const Namespace& ns); + + // tracks the current namespace for early exit in WrapInNameSpace + // c++, java and csharp returns a different namespace from + // the following default (no early exit, always fully qualify), + // which works for js and php + virtual const Namespace* CurrentNameSpace() const { return nullptr; } + + // Ensure that a type is prefixed with its namespace even within + // its own namespace to avoid conflict between generated method + // names and similarly named classes or structs + std::string WrapInNameSpace(const Namespace* ns, + const std::string& name) const; + + std::string WrapInNameSpace(const Definition& def, + const std::string& suffix = "") const; + + std::string GetNameSpace(const Definition& def) const; + + const Parser& parser_; + const std::string& path_; + const std::string& file_name_; + const std::string qualifying_start_; + const std::string qualifying_separator_; + const std::string default_extension_; +}; + +struct CommentConfig { + const char* first_line; + const char* content_line_prefix; + const char* last_line; +}; + +extern void GenComment(const std::vector& dc, + std::string* code_ptr, const CommentConfig* config, + const char* prefix = ""); + +class FloatConstantGenerator { + public: + virtual ~FloatConstantGenerator() {} + std::string GenFloatConstant(const FieldDef& field) const; + + private: + virtual std::string Value(double v, const std::string& src) const = 0; + virtual std::string Inf(double v) const = 0; + virtual std::string NaN(double v) const = 0; + + virtual std::string Value(float v, const std::string& src) const = 0; + virtual std::string Inf(float v) const = 0; + virtual std::string NaN(float v) const = 0; + + template + std::string GenFloatConstantImpl(const FieldDef& field) const; +}; + +class SimpleFloatConstantGenerator : public FloatConstantGenerator { + public: + SimpleFloatConstantGenerator(const char* nan_number, + const char* pos_inf_number, + const char* neg_inf_number); + + private: + std::string Value(double v, + const std::string& src) const FLATBUFFERS_OVERRIDE; + std::string Inf(double v) const FLATBUFFERS_OVERRIDE; + std::string NaN(double v) const FLATBUFFERS_OVERRIDE; + + std::string Value(float v, const std::string& src) const FLATBUFFERS_OVERRIDE; + std::string Inf(float v) const FLATBUFFERS_OVERRIDE; + std::string NaN(float v) const FLATBUFFERS_OVERRIDE; + + const std::string nan_number_; + const std::string pos_inf_number_; + const std::string neg_inf_number_; +}; + +// C++, C#, Java like generator. +class TypedFloatConstantGenerator : public FloatConstantGenerator { + public: + TypedFloatConstantGenerator(const char* double_prefix, + const char* single_prefix, const char* nan_number, + const char* pos_inf_number, + const char* neg_inf_number = ""); + + private: + std::string Value(double v, + const std::string& src) const FLATBUFFERS_OVERRIDE; + std::string Inf(double v) const FLATBUFFERS_OVERRIDE; + + std::string NaN(double v) const FLATBUFFERS_OVERRIDE; + + std::string Value(float v, const std::string& src) const FLATBUFFERS_OVERRIDE; + std::string Inf(float v) const FLATBUFFERS_OVERRIDE; + std::string NaN(float v) const FLATBUFFERS_OVERRIDE; + + std::string MakeNaN(const std::string& prefix) const; + std::string MakeInf(bool neg, const std::string& prefix) const; + + const std::string double_prefix_; + const std::string single_prefix_; + const std::string nan_number_; + const std::string pos_inf_number_; + const std::string neg_inf_number_; +}; + +std::string JavaCSharpMakeRule(const bool java, const Parser& parser, + const std::string& path, + const std::string& file_name); + +} // namespace flatbuffers + +#endif // FLATBUFFERS_CODE_GENERATORS_H_ diff --git a/external/flatbuffers/include/flatbuffers/default_allocator.h b/external/flatbuffers/include/flatbuffers/default_allocator.h new file mode 100644 index 00000000..d1cab08d --- /dev/null +++ b/external/flatbuffers/include/flatbuffers/default_allocator.h @@ -0,0 +1,64 @@ +/* + * Copyright 2021 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLATBUFFERS_DEFAULT_ALLOCATOR_H_ +#define FLATBUFFERS_DEFAULT_ALLOCATOR_H_ + +#include "flatbuffers/allocator.h" +#include "flatbuffers/base.h" + +namespace flatbuffers { + +// DefaultAllocator uses new/delete to allocate memory regions +class DefaultAllocator : public Allocator { + public: + uint8_t* allocate(size_t size) FLATBUFFERS_OVERRIDE { + return new uint8_t[size]; + } + + void deallocate(uint8_t* p, size_t) FLATBUFFERS_OVERRIDE { delete[] p; } + + static void dealloc(void* p, size_t) { delete[] static_cast(p); } +}; + +// These functions allow for a null allocator to mean use the default allocator, +// as used by DetachedBuffer and vector_downward below. +// This is to avoid having a statically or dynamically allocated default +// allocator, or having to move it between the classes that may own it. +inline uint8_t* Allocate(Allocator* allocator, size_t size) { + return allocator ? allocator->allocate(size) + : DefaultAllocator().allocate(size); +} + +inline void Deallocate(Allocator* allocator, uint8_t* p, size_t size) { + if (allocator) + allocator->deallocate(p, size); + else + DefaultAllocator().deallocate(p, size); +} + +inline uint8_t* ReallocateDownward(Allocator* allocator, uint8_t* old_p, + size_t old_size, size_t new_size, + size_t in_use_back, size_t in_use_front) { + return allocator ? allocator->reallocate_downward(old_p, old_size, new_size, + in_use_back, in_use_front) + : DefaultAllocator().reallocate_downward( + old_p, old_size, new_size, in_use_back, in_use_front); +} + +} // namespace flatbuffers + +#endif // FLATBUFFERS_DEFAULT_ALLOCATOR_H_ diff --git a/external/flatbuffers/include/flatbuffers/detached_buffer.h b/external/flatbuffers/include/flatbuffers/detached_buffer.h new file mode 100644 index 00000000..0577a42d --- /dev/null +++ b/external/flatbuffers/include/flatbuffers/detached_buffer.h @@ -0,0 +1,121 @@ +/* + * Copyright 2021 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLATBUFFERS_DETACHED_BUFFER_H_ +#define FLATBUFFERS_DETACHED_BUFFER_H_ + +#include "flatbuffers/allocator.h" +#include "flatbuffers/base.h" +#include "flatbuffers/default_allocator.h" + +namespace flatbuffers { + +// DetachedBuffer is a finished flatbuffer memory region, detached from its +// builder. The original memory region and allocator are also stored so that +// the DetachedBuffer can manage the memory lifetime. +class DetachedBuffer { + public: + DetachedBuffer() + : allocator_(nullptr), + own_allocator_(false), + buf_(nullptr), + reserved_(0), + cur_(nullptr), + size_(0) {} + + DetachedBuffer(Allocator* allocator, bool own_allocator, uint8_t* buf, + size_t reserved, uint8_t* cur, size_t sz) + : allocator_(allocator), + own_allocator_(own_allocator), + buf_(buf), + reserved_(reserved), + cur_(cur), + size_(sz) {} + + DetachedBuffer(DetachedBuffer&& other) noexcept + : allocator_(other.allocator_), + own_allocator_(other.own_allocator_), + buf_(other.buf_), + reserved_(other.reserved_), + cur_(other.cur_), + size_(other.size_) { + other.reset(); + } + + DetachedBuffer& operator=(DetachedBuffer&& other) noexcept { + if (this == &other) return *this; + + destroy(); + + allocator_ = other.allocator_; + own_allocator_ = other.own_allocator_; + buf_ = other.buf_; + reserved_ = other.reserved_; + cur_ = other.cur_; + size_ = other.size_; + + other.reset(); + + return *this; + } + + ~DetachedBuffer() { destroy(); } + + const uint8_t* data() const { return cur_; } + + uint8_t* data() { return cur_; } + + size_t size() const { return size_; } + + uint8_t* begin() { return data(); } + const uint8_t* begin() const { return data(); } + uint8_t* end() { return data() + size(); } + const uint8_t* end() const { return data() + size(); } + + // These may change access mode, leave these at end of public section + FLATBUFFERS_DELETE_FUNC(DetachedBuffer(const DetachedBuffer& other)); + FLATBUFFERS_DELETE_FUNC( + DetachedBuffer& operator=(const DetachedBuffer& other)); + + protected: + Allocator* allocator_; + bool own_allocator_; + uint8_t* buf_; + size_t reserved_; + uint8_t* cur_; + size_t size_; + + inline void destroy() { + if (buf_) Deallocate(allocator_, buf_, reserved_); + if (own_allocator_ && allocator_) { + delete allocator_; + } + reset(); + } + + inline void reset() { + allocator_ = nullptr; + own_allocator_ = false; + buf_ = nullptr; + reserved_ = 0; + cur_ = nullptr; + size_ = 0; + } +}; + +} // namespace flatbuffers + +#endif // FLATBUFFERS_DETACHED_BUFFER_H_ diff --git a/external/flatbuffers/include/flatbuffers/file_manager.h b/external/flatbuffers/include/flatbuffers/file_manager.h new file mode 100644 index 00000000..bafe6f29 --- /dev/null +++ b/external/flatbuffers/include/flatbuffers/file_manager.h @@ -0,0 +1,70 @@ +/* + * Copyright 2023 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLATBUFFERS_FILE_MANAGER_H_ +#define FLATBUFFERS_FILE_MANAGER_H_ + +#include +#include +#include + +namespace flatbuffers { + +// A File interface to write data to file by default or +// save only file names +class FileSaver { + public: + FileSaver() = default; + virtual ~FileSaver() = default; + + virtual bool SaveFile(const char* name, const char* buf, size_t len, + bool binary) = 0; + + bool SaveFile(const char* name, const std::string& buf, bool binary) { + return SaveFile(name, buf.c_str(), buf.size(), binary); + } + + virtual void Finish() {} + + private: + // Copying is not supported. + FileSaver(const FileSaver&) = delete; + FileSaver& operator=(const FileSaver&) = delete; + // Rule of 5 + FileSaver(FileSaver&&) = default; + FileSaver& operator=(FileSaver&&) = default; +}; + +class RealFileSaver final : public FileSaver { + public: + bool SaveFile(const char* name, const char* buf, size_t len, + bool binary) final; +}; + +class FileNameSaver final : public FileSaver { + public: + bool SaveFile(const char* name, const char* buf, size_t len, + bool binary) final; + + void Finish() final; + + private: + std::set file_names_{}; +}; + +} // namespace flatbuffers + +#endif // FLATBUFFERS_FILE_MANAGER_H_ diff --git a/external/flatbuffers/include/flatbuffers/flatbuffer_builder.h b/external/flatbuffers/include/flatbuffers/flatbuffer_builder.h new file mode 100644 index 00000000..636d3776 --- /dev/null +++ b/external/flatbuffers/include/flatbuffers/flatbuffer_builder.h @@ -0,0 +1,1518 @@ +/* + * Copyright 2021 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLATBUFFERS_FLATBUFFER_BUILDER_H_ +#define FLATBUFFERS_FLATBUFFER_BUILDER_H_ + +#include +#include +#include +#include +#include + +#include "flatbuffers/allocator.h" +#include "flatbuffers/array.h" +#include "flatbuffers/base.h" +#include "flatbuffers/buffer.h" +#include "flatbuffers/buffer_ref.h" +#include "flatbuffers/default_allocator.h" +#include "flatbuffers/detached_buffer.h" +#include "flatbuffers/stl_emulation.h" +#include "flatbuffers/string.h" +#include "flatbuffers/struct.h" +#include "flatbuffers/table.h" +#include "flatbuffers/vector.h" +#include "flatbuffers/vector_downward.h" +#include "flatbuffers/verifier.h" + +namespace flatbuffers { + +// Converts a Field ID to a virtual table offset. +inline voffset_t FieldIndexToOffset(voffset_t field_id) { + // Should correspond to what EndTable() below builds up. + const voffset_t fixed_fields = + 2 * sizeof(voffset_t); // Vtable size and Object Size. + size_t offset = fixed_fields + field_id * sizeof(voffset_t); + FLATBUFFERS_ASSERT(offset < std::numeric_limits::max()); + return static_cast(offset); +} + +template > +const T* data(const std::vector& v) { + // Eventually the returned pointer gets passed down to memcpy, so + // we need it to be non-null to avoid undefined behavior. + static uint8_t t; + return v.empty() ? reinterpret_cast(&t) : &v.front(); +} +template > +T* data(std::vector& v) { + // Eventually the returned pointer gets passed down to memcpy, so + // we need it to be non-null to avoid undefined behavior. + static uint8_t t; + return v.empty() ? reinterpret_cast(&t) : &v.front(); +} + +/// @addtogroup flatbuffers_cpp_api +/// @{ +/// @class FlatBufferBuilder +/// @brief Helper class to hold data needed in creation of a FlatBuffer. +/// To serialize data, you typically call one of the `Create*()` functions in +/// the generated code, which in turn call a sequence of `StartTable`/ +/// `PushElement`/`AddElement`/`EndTable`, or the builtin `CreateString`/ +/// `CreateVector` functions. Do this is depth-first order to build up a tree to +/// the root. `Finish()` wraps up the buffer ready for transport. +template +class FlatBufferBuilderImpl { + public: + // This switches the size type of the builder, based on if its 64-bit aware + // (uoffset64_t) or not (uoffset_t). + typedef + typename std::conditional::type SizeT; + + /// @brief Default constructor for FlatBufferBuilder. + /// @param[in] initial_size The initial size of the buffer, in bytes. Defaults + /// to `1024`. + /// @param[in] allocator An `Allocator` to use. If null will use + /// `DefaultAllocator`. + /// @param[in] own_allocator Whether the builder/vector should own the + /// allocator. Defaults to / `false`. + /// @param[in] buffer_minalign Force the buffer to be aligned to the given + /// minimum alignment upon reallocation. Only needed if you intend to store + /// types with custom alignment AND you wish to read the buffer in-place + /// directly after creation. + explicit FlatBufferBuilderImpl( + size_t initial_size = 1024, Allocator* allocator = nullptr, + bool own_allocator = false, + size_t buffer_minalign = AlignOf()) + : buf_(initial_size, allocator, own_allocator, buffer_minalign, + static_cast(Is64Aware ? FLATBUFFERS_MAX_64_BUFFER_SIZE + : FLATBUFFERS_MAX_BUFFER_SIZE)), + num_field_loc(0), + max_voffset_(0), + length_of_64_bit_region_(0), + nested(false), + finished(false), + minalign_(1), + force_defaults_(false), + dedup_vtables_(true), + string_pool(nullptr) { + EndianCheck(); + } + + /// @brief Move constructor for FlatBufferBuilder. + FlatBufferBuilderImpl(FlatBufferBuilderImpl&& other) noexcept + : buf_(1024, nullptr, false, AlignOf(), + static_cast(Is64Aware ? FLATBUFFERS_MAX_64_BUFFER_SIZE + : FLATBUFFERS_MAX_BUFFER_SIZE)), + num_field_loc(0), + max_voffset_(0), + length_of_64_bit_region_(0), + nested(false), + finished(false), + minalign_(1), + force_defaults_(false), + dedup_vtables_(true), + string_pool(nullptr) { + EndianCheck(); + // Default construct and swap idiom. + // Lack of delegating constructors in vs2010 makes it more verbose than + // needed. + Swap(other); + } + + /// @brief Move assignment operator for FlatBufferBuilder. + FlatBufferBuilderImpl& operator=(FlatBufferBuilderImpl&& other) noexcept { + // Move construct a temporary and swap idiom + FlatBufferBuilderImpl temp(std::move(other)); + Swap(temp); + return *this; + } + + void Swap(FlatBufferBuilderImpl& other) { + using std::swap; + buf_.swap(other.buf_); + swap(num_field_loc, other.num_field_loc); + swap(max_voffset_, other.max_voffset_); + swap(length_of_64_bit_region_, other.length_of_64_bit_region_); + swap(nested, other.nested); + swap(finished, other.finished); + swap(minalign_, other.minalign_); + swap(force_defaults_, other.force_defaults_); + swap(dedup_vtables_, other.dedup_vtables_); + swap(string_pool, other.string_pool); + } + + ~FlatBufferBuilderImpl() { + if (string_pool) delete string_pool; + } + + void Reset() { + Clear(); // clear builder state + buf_.reset(); // deallocate buffer + } + + /// @brief Reset all the state in this FlatBufferBuilder so it can be reused + /// to construct another buffer. + void Clear() { + ClearOffsets(); + buf_.clear(); + nested = false; + finished = false; + minalign_ = 1; + length_of_64_bit_region_ = 0; + if (string_pool) string_pool->clear(); + } + + /// @brief The current size of the serialized buffer, counting from the end. + /// @return Returns an `SizeT` with the current size of the buffer. + SizeT GetSize() const { return buf_.size(); } + + /// @brief The current size of the serialized buffer relative to the end of + /// the 32-bit region. + /// @return Returns an `uoffset_t` with the current size of the buffer. + template + // Only enable this method for the 64-bit builder, as only that builder is + // concerned with the 32/64-bit boundary, and should be the one to bare any + // run time costs. + typename std::enable_if::type GetSizeRelative32BitRegion() + const { + //[32-bit region][64-bit region] + // [XXXXXXXXXXXXXXXXXXX] GetSize() + // [YYYYYYYYYYYYY] length_of_64_bit_region_ + // [ZZZZ] return size + return static_cast(GetSize() - length_of_64_bit_region_); + } + + template + // Only enable this method for the 32-bit builder. + typename std::enable_if::type GetSizeRelative32BitRegion() + const { + return static_cast(GetSize()); + } + + /// @brief Get the serialized buffer (after you call `Finish()`). + /// @return Returns an `uint8_t` pointer to the FlatBuffer data inside the + /// buffer. + uint8_t* GetBufferPointer() const { + Finished(); + return buf_.data(); + } + + /// @brief Get the serialized buffer (after you call `Finish()`) as a span. + /// @return Returns a constructed flatbuffers::span that is a view over the + /// FlatBuffer data inside the buffer. + flatbuffers::span GetBufferSpan() const { + Finished(); + return flatbuffers::span(buf_.data(), buf_.size()); + } + + /// @brief Get a pointer to an unfinished buffer. + /// @return Returns a `uint8_t` pointer to the unfinished buffer. + uint8_t* GetCurrentBufferPointer() const { return buf_.data(); } + + /// @brief Get the released DetachedBuffer. + /// @return A `DetachedBuffer` that owns the buffer and its allocator. + DetachedBuffer Release() { + Finished(); + DetachedBuffer buffer = buf_.release(); + Clear(); + return buffer; + } + + /// @brief Get the released pointer to the serialized buffer. + /// @param size The size of the memory block containing + /// the serialized `FlatBuffer`. + /// @param offset The offset from the released pointer where the finished + /// `FlatBuffer` starts. + /// @return A raw pointer to the start of the memory block containing + /// the serialized `FlatBuffer`. + /// @remark If the allocator is owned, it gets deleted when the destructor is + /// called. + uint8_t* ReleaseRaw(size_t& size, size_t& offset) { + Finished(); + uint8_t* raw = buf_.release_raw(size, offset); + Clear(); + return raw; + } + + /// @brief get the minimum alignment this buffer needs to be accessed + /// properly. This is only known once all elements have been written (after + /// you call Finish()). You can use this information if you need to embed + /// a FlatBuffer in some other buffer, such that you can later read it + /// without first having to copy it into its own buffer. + size_t GetBufferMinAlignment() const { + Finished(); + return minalign_; + } + + /// @cond FLATBUFFERS_INTERNAL + void Finished() const { + // If you get this assert, you're attempting to get access a buffer + // which hasn't been finished yet. Be sure to call + // FlatBufferBuilder::Finish with your root table. + // If you really need to access an unfinished buffer, call + // GetCurrentBufferPointer instead. + FLATBUFFERS_ASSERT(finished); + } + /// @endcond + + /// @brief In order to save space, fields that are set to their default value + /// don't get serialized into the buffer. + /// @param[in] fd When set to `true`, always serializes default values that + /// are set. Optional fields which are not set explicitly, will still not be + /// serialized. + void ForceDefaults(bool fd) { force_defaults_ = fd; } + + /// @brief By default vtables are deduped in order to save space. + /// @param[in] dedup When set to `true`, dedup vtables. + void DedupVtables(bool dedup) { dedup_vtables_ = dedup; } + + /// @cond FLATBUFFERS_INTERNAL + void Pad(size_t num_bytes) { buf_.fill(num_bytes); } + + void TrackMinAlign(size_t elem_size) { + if (elem_size > minalign_) minalign_ = elem_size; + } + + void Align(size_t elem_size) { + TrackMinAlign(elem_size); + buf_.fill(PaddingBytes(buf_.size(), elem_size)); + } + + void PushFlatBuffer(const uint8_t* bytes, size_t size) { + PushBytes(bytes, size); + finished = true; + } + + void PushBytes(const uint8_t* bytes, size_t size) { buf_.push(bytes, size); } + + void PopBytes(size_t amount) { buf_.pop(amount); } + + template + void AssertScalarT() { + // The code assumes power of 2 sizes and endian-swap-ability. + static_assert(flatbuffers::is_scalar::value, "T must be a scalar type"); + } + + // Write a single aligned scalar to the buffer + template + ReturnT PushElement(T element) { + AssertScalarT(); + Align(sizeof(T)); + buf_.push_small(EndianScalar(element)); + return CalculateOffset(); + } + + template class OffsetT = Offset> + uoffset_t PushElement(OffsetT off) { + // Special case for offsets: see ReferTo below. + return PushElement(ReferTo(off.o)); + } + + // When writing fields, we track where they are, so we can create correct + // vtables later. + void TrackField(voffset_t field, uoffset_t off) { + FieldLoc fl = {off, field}; + buf_.scratch_push_small(fl); + num_field_loc++; + if (field > max_voffset_) { + max_voffset_ = field; + } + } + + // Like PushElement, but additionally tracks the field this represents. + template + void AddElement(voffset_t field, T e, T def) { + // We don't serialize values equal to the default. + if (IsTheSameAs(e, def) && !force_defaults_) return; + TrackField(field, PushElement(e)); + } + + template + void AddElement(voffset_t field, T e) { + TrackField(field, PushElement(e)); + } + + template + void AddOffset(voffset_t field, Offset off) { + if (off.IsNull()) return; // Don't store. + AddElement(field, ReferTo(off.o), static_cast(0)); + } + + template + void AddOffset(voffset_t field, Offset64 off) { + if (off.IsNull()) return; // Don't store. + AddElement(field, ReferTo(off.o), static_cast(0)); + } + + template + void AddStruct(voffset_t field, const T* structptr) { + if (!structptr) return; // Default, don't store. + Align(AlignOf()); + buf_.push_small(*structptr); + TrackField(field, CalculateOffset()); + } + + void AddStructOffset(voffset_t field, uoffset_t off) { + TrackField(field, off); + } + + // Offsets initially are relative to the end of the buffer (downwards). + // This function converts them to be relative to the current location + // in the buffer (when stored here), pointing upwards. + uoffset_t ReferTo(uoffset_t off) { + // Align to ensure GetSizeRelative32BitRegion() below is correct. + Align(sizeof(uoffset_t)); + // 32-bit offsets are relative to the tail of the 32-bit region of the + // buffer. For most cases (without 64-bit entities) this is equivalent to + // size of the whole buffer (e.g. GetSize()) + return ReferTo(off, GetSizeRelative32BitRegion()); + } + + uoffset64_t ReferTo(uoffset64_t off) { + // Align to ensure GetSize() below is correct. + Align(sizeof(uoffset64_t)); + // 64-bit offsets are relative to tail of the whole buffer + return ReferTo(off, GetSize()); + } + + template + T ReferTo(const T off, const T2 size) { + FLATBUFFERS_ASSERT(off && off <= size); + return size - off + static_cast(sizeof(T)); + } + + template + T ReferTo(const T off, const T size) { + FLATBUFFERS_ASSERT(off && off <= size); + return size - off + static_cast(sizeof(T)); + } + + void NotNested() { + // If you hit this, you're trying to construct a Table/Vector/String + // during the construction of its parent table (between the MyTableBuilder + // and table.Finish(). + // Move the creation of these sub-objects to above the MyTableBuilder to + // not get this assert. + // Ignoring this assert may appear to work in simple cases, but the reason + // it is here is that storing objects in-line may cause vtable offsets + // to not fit anymore. It also leads to vtable duplication. + FLATBUFFERS_ASSERT(!nested); + // If you hit this, fields were added outside the scope of a table. + FLATBUFFERS_ASSERT(!num_field_loc); + } + + // From generated code (or from the parser), we call StartTable/EndTable + // with a sequence of AddElement calls in between. + uoffset_t StartTable() { + NotNested(); + nested = true; + return GetSizeRelative32BitRegion(); + } + + // This finishes one serialized object by generating the vtable if it's a + // table, comparing it against existing vtables, and writing the + // resulting vtable offset. + uoffset_t EndTable(uoffset_t start) { + // If you get this assert, a corresponding StartTable wasn't called. + FLATBUFFERS_ASSERT(nested); + // Write the vtable offset, which is the start of any Table. + // We fill its value later. + // This is relative to the end of the 32-bit region. + const uoffset_t vtable_offset_loc = + static_cast(PushElement(0)); + // Write a vtable, which consists entirely of voffset_t elements. + // It starts with the number of offsets, followed by a type id, followed + // by the offsets themselves. In reverse: + // Include space for the last offset and ensure empty tables have a + // minimum size. + max_voffset_ = + (std::max)(static_cast(max_voffset_ + sizeof(voffset_t)), + FieldIndexToOffset(0)); + buf_.fill_big(max_voffset_); + const uoffset_t table_object_size = vtable_offset_loc - start; + // Vtable use 16bit offsets. + FLATBUFFERS_ASSERT(table_object_size < 0x10000); + WriteScalar(buf_.data() + sizeof(voffset_t), + static_cast(table_object_size)); + WriteScalar(buf_.data(), max_voffset_); + // Write the offsets into the table + for (auto it = buf_.scratch_end() - num_field_loc * sizeof(FieldLoc); + it < buf_.scratch_end(); it += sizeof(FieldLoc)) { + auto field_location = reinterpret_cast(it); + const voffset_t pos = + static_cast(vtable_offset_loc - field_location->off); + // If this asserts, it means you've set a field twice. + FLATBUFFERS_ASSERT( + !ReadScalar(buf_.data() + field_location->id)); + WriteScalar(buf_.data() + field_location->id, pos); + } + ClearOffsets(); + auto vt1 = reinterpret_cast(buf_.data()); + auto vt1_size = ReadScalar(vt1); + auto vt_use = GetSizeRelative32BitRegion(); + // See if we already have generated a vtable with this exact same + // layout before. If so, make it point to the old one, remove this one. + if (dedup_vtables_) { + for (auto it = buf_.scratch_data(); it < buf_.scratch_end(); + it += sizeof(uoffset_t)) { + auto vt_offset_ptr = reinterpret_cast(it); + auto vt2 = reinterpret_cast( + buf_.data_at(*vt_offset_ptr + length_of_64_bit_region_)); + auto vt2_size = ReadScalar(vt2); + if (vt1_size != vt2_size || 0 != memcmp(vt2, vt1, vt1_size)) continue; + vt_use = *vt_offset_ptr; + buf_.pop(GetSizeRelative32BitRegion() - vtable_offset_loc); + break; + } + } + // If this is a new vtable, remember it. + if (vt_use == GetSizeRelative32BitRegion()) { + buf_.scratch_push_small(vt_use); + } + // Fill the vtable offset we created above. + // The offset points from the beginning of the object to where the vtable is + // stored. + // Offsets default direction is downward in memory for future format + // flexibility (storing all vtables at the start of the file). + WriteScalar(buf_.data_at(vtable_offset_loc + length_of_64_bit_region_), + static_cast(vt_use) - + static_cast(vtable_offset_loc)); + nested = false; + return vtable_offset_loc; + } + + FLATBUFFERS_ATTRIBUTE([[deprecated("call the version above instead")]]) + uoffset_t EndTable(uoffset_t start, voffset_t /*numfields*/) { + return EndTable(start); + } + + // This checks a required field has been set in a given table that has + // just been constructed. + template + void Required(Offset table, voffset_t field) { + auto table_ptr = reinterpret_cast(buf_.data_at(table.o)); + bool ok = table_ptr->GetOptionalFieldOffset(field) != 0; + // If this fails, the caller will show what field needs to be set. + FLATBUFFERS_ASSERT(ok); + (void)ok; + } + + uoffset_t StartStruct(size_t alignment) { + Align(alignment); + return GetSizeRelative32BitRegion(); + } + + uoffset_t EndStruct() { return GetSizeRelative32BitRegion(); } + + void ClearOffsets() { + buf_.scratch_pop(num_field_loc * sizeof(FieldLoc)); + num_field_loc = 0; + max_voffset_ = 0; + } + + // Aligns such that when "len" bytes are written, an object can be written + // after it (forward in the buffer) with "alignment" without padding. + void PreAlign(size_t len, size_t alignment) { + if (len == 0) return; + TrackMinAlign(alignment); + buf_.fill(PaddingBytes(GetSize() + len, alignment)); + } + + // Aligns such than when "len" bytes are written, an object of type `AlignT` + // can be written after it (forward in the buffer) without padding. + template + void PreAlign(size_t len) { + AssertScalarT(); + PreAlign(len, AlignOf()); + } + /// @endcond + + /// @brief Store a string in the buffer, which can contain any binary data. + /// @param[in] str A const char pointer to the data to be stored as a string. + /// @param[in] len The number of bytes that should be stored from `str`. + /// @return Returns the offset in the buffer where the string starts. + template