Support .lottie files in LottieGen. (#376)

* Support .lottie files in LottieGen.

See https://dotlottie.io for file details.
LottieGen will now read .lottie files as well as .json files.
This commit is contained in:
Simeon 2020-10-26 13:46:38 -07:00 коммит произвёл GitHub
Родитель 67c626d0d3
Коммит 0589774252
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
8 изменённых файлов: 377 добавлений и 137 удалений

261
LottieGen/FileProcessor.cs Normal file
Просмотреть файл

@ -0,0 +1,261 @@
// 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.
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using Microsoft.Toolkit.Uwp.UI.Lottie.DotLottie;
/// <summary>
/// Processes a file containing Lottie animations and produces various generated outputs.
/// Both JSON and .lottie files are supported.
/// </summary>
sealed class FileProcessor
{
readonly CommandLineOptions _options;
readonly Reporter _reporter;
readonly string _filePath;
readonly string _outputFolder;
readonly DateTime _timestamp;
FileProcessor(
CommandLineOptions options,
Reporter reporter,
string filePath,
string outputFolder,
DateTime timestamp)
{
_options = options;
_filePath = filePath;
_reporter = reporter;
_outputFolder = outputFolder;
_timestamp = timestamp;
}
internal static bool ProcessFile(
CommandLineOptions options,
Reporter reporter,
string filePath,
string outputFolder,
DateTime timestamp)
{
try
{
return new FileProcessor(options, reporter, filePath, outputFolder, timestamp).Run();
}
catch
{
reporter.WriteError($"Unhandled exception processing: {filePath}");
throw;
}
}
bool Run()
{
switch (Path.GetExtension(_filePath).ToLowerInvariant())
{
case ".lottie":
return ProcessDotLottieFile(
options: _options,
reporter: _reporter,
filePath: _filePath,
outputFolder: _outputFolder,
timestamp: _timestamp);
case ".json":
default:
return ProcessJsonLottieFile(
options: _options,
reporter: _reporter,
jsonFilePath: _filePath,
outputFolder: _outputFolder,
timestamp: _timestamp);
}
}
static bool ProcessJsonLottieFile(
CommandLineOptions options,
Reporter reporter,
string jsonFilePath,
string outputFolder,
DateTime timestamp)
{
using var jsonStream = TryOpenFile(jsonFilePath, reporter);
if (jsonStream is null)
{
reporter.WriteError($"Failed to read {jsonFilePath}.");
return false;
}
return LottieJsonFileProcessor.ProcessLottieJsonFile(
options: options,
reporter: reporter,
sourceFilePath: jsonFilePath,
jsonFilePath: jsonFilePath,
className: GetClassName(jsonFilePath),
jsonStream: jsonStream,
outputFolder: outputFolder,
timestamp: timestamp);
}
static bool ProcessDotLottieFile(
CommandLineOptions options,
Reporter reporter,
string filePath,
string outputFolder,
DateTime timestamp)
{
using var fileStream = TryOpenFile(filePath, reporter);
if (fileStream is null)
{
return false;
}
ZipArchive zipArchive;
try
{
zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Read);
}
catch (InvalidDataException e)
{
using (reporter.ErrorStream.Lock())
{
reporter.WriteError($"File {filePath} is not a valid .lottie file.");
reporter.WriteError(e.Message);
}
return false;
}
var dotLottieFile = DotLottieFile.FromZipArchive(zipArchive);
if (dotLottieFile is null)
{
reporter.WriteError($"File {filePath} is not a valid .lottie file.");
return false;
}
if (dotLottieFile.Animations.Count == 0)
{
reporter.WriteError($"File {filePath} contains no animations.");
return false;
}
var succeeded = true;
foreach (var animation in dotLottieFile.Animations)
{
var jsonPath = $"{filePath}{animation.Path}";
using var jsonStream = animation.Open();
if (jsonStream == null)
{
reporter.WriteError($"Failed to read from {jsonPath}.");
succeeded = false;
continue;
}
// The name of the class should reflect the name of the .lottie file,
// but if there are multiple .json files in the .lottie file then we
// need to differentiate them by adding the file name.
var className = dotLottieFile.Animations.Count == 1
? GetClassName(filePath)
: GetClassName($"{filePath}_{animation.Id}");
if (!LottieJsonFileProcessor.ProcessLottieJsonFile(
options: options,
reporter: reporter,
sourceFilePath: filePath,
jsonFilePath: jsonPath,
className: className,
jsonStream: jsonStream,
outputFolder: outputFolder,
timestamp))
{
succeeded = false;
}
}
return succeeded;
}
static Stream? TryOpenFile(string filePath, Reporter reporter)
{
using (reporter.InfoStream.Lock())
{
reporter.WriteInfo($"Reading file:");
reporter.WriteInfo(InfoType.FilePath, $" {filePath}");
}
try
{
return File.OpenRead(filePath);
}
catch (Exception e)
{
using (reporter.ErrorStream.Lock())
{
reporter.WriteError($"Failed to read from {filePath}");
reporter.WriteError(e.Message);
}
return null;
}
}
// Get an appropriate name for a generated class.
static string GetClassName(string path) =>
TrySynthesizeClassName(Path.GetFileNameWithoutExtension(path)) ??
"Lottie"; // If all else fails, just call it Lottie.
/// <summary>
/// Takes a name and modifies it as necessary to be suited for use as a class name in languages such
/// as C# and C++.
/// Returns null on failure.
/// </summary>
/// <returns>A name, or null.</returns>
static string? TrySynthesizeClassName(string? name)
{
if (string.IsNullOrWhiteSpace(name))
{
return null;
}
// Remove any leading punctuation.
var prefixSize = name.TakeWhile(c => !char.IsLetterOrDigit(c)).Count();
return SanitizeTypeName(name.Substring(prefixSize));
}
// Makes the given name suitable for use as a class name in languages such as C# and C++.
static string? SanitizeTypeName(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return null;
}
// If the first character is not a letter, prepend an underscore.
if (!char.IsLetter(name, 0))
{
name = "_" + name;
}
// Replace any disallowed character with underscores.
name =
new string((from ch in name
select char.IsLetterOrDigit(ch) ? ch : '_').ToArray());
// Remove any duplicated underscores.
name = name.Replace("__", "_");
// Capitalize the first letter.
name = name.ToUpperInvariant().Substring(0, 1) + name.Substring(1);
return name;
}
}

Просмотреть файл

@ -16,8 +16,6 @@
<AssemblyName>LottieGen</AssemblyName>
<LangVersion>latest</LangVersion>
<PlatformTarget>AnyCPU</PlatformTarget>
<!-- Disable warnings about XML comments temporarily until all the comments have been authored. -->
<NoWarn>1572;1573;1574;1591</NoWarn>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<IsPackable>True</IsPackable>
<StartupObject>Program</StartupObject>
@ -28,6 +26,9 @@
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
</ItemGroup>
<Import Project="..\source\CompMetadata\CompMetadata.projitems" Label="Shared" />
<Import Project="..\source\DotLottie\DotLottie.projitems" Label="Shared" />
<Import Project="..\source\GenericData\GenericData.projitems" Label="Shared" />
<Import Project="..\source\LottieData\LottieData.projitems" Label="Shared" />
<Import Project="..\source\LottieMetadata\LottieMetadata.projitems" Label="Shared" />
<Import Project="..\source\LottieReader\LottieReader.projitems" Label="Shared" />
@ -37,8 +38,6 @@
<Import Project="..\source\WinStorageStreamsData\WinStorageStreamsData.projitems" Label="Shared" />
<Import Project="..\source\WinUIXamlMediaData\WinUIXamlMediaData.projitems" Label="Shared" />
<Import Project="..\source\YamlData\YamlData.projitems" Label="Shared" />
<Import Project="..\source\GenericData\GenericData.projitems" Label="Shared" />
<Import Project="..\source\UIDataCodeGen\UIDataCodeGen.projitems" Label="Shared" />
<Import Project="..\source\CompMetadata\CompMetadata.projitems" Label="Shared" />
</Project>

Просмотреть файл

@ -94,6 +94,8 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "LottieMetadata", "..\source
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "CompMetadata", "..\source\CompMetadata\CompMetadata.shproj", "{B0197C19-BDF5-473E-A022-E21F6122EEE5}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "DotLottie", "..\source\DotLottie\DotLottie.shproj", "{7012420D-624C-4BD4-A1D2-1C6C1655ED3A}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
..\source\LottieToWinComp\LottieToWinComp.projitems*{0340244a-683c-405e-838b-f93872779532}*SharedItemsImports = 13
@ -102,11 +104,13 @@ Global
..\source\WinUIXamlMediaData\WinUIXamlMediaData.projitems*{30059ca7-0745-4eec-8d11-b14850a70c98}*SharedItemsImports = 13
..\source\YamlData\YamlData.projitems*{39c6b7f3-5e75-4019-82ab-00fd8a0a06e2}*SharedItemsImports = 13
..\source\LottieReader\LottieReader.projitems*{4e7d8957-3f5f-46e1-99a8-2012b806c9b0}*SharedItemsImports = 13
..\source\DotLottie\DotLottie.projitems*{7012420d-624c-4bd4-a1d2-1c6c1655ed3a}*SharedItemsImports = 13
..\source\UIData\UIData.projitems*{74601e6c-2dfe-4842-b170-047941abff2c}*SharedItemsImports = 13
..\source\GenericData\GenericData.projitems*{77bcd724-8555-463b-985f-f8e8110164c4}*SharedItemsImports = 13
..\source\CompMetadata\CompMetadata.projitems*{b0197c19-bdf5-473e-a022-e21f6122eee5}*SharedItemsImports = 13
..\source\LottieData\LottieData.projitems*{b3db16ee-a821-4474-a188-e64926529bbd}*SharedItemsImports = 13
..\source\CompMetadata\CompMetadata.projitems*{cb12d5ba-a6fe-41e2-b555-83c903cce92a}*SharedItemsImports = 5
..\source\DotLottie\DotLottie.projitems*{cb12d5ba-a6fe-41e2-b555-83c903cce92a}*SharedItemsImports = 5
..\source\GenericData\GenericData.projitems*{cb12d5ba-a6fe-41e2-b555-83c903cce92a}*SharedItemsImports = 5
..\source\LottieData\LottieData.projitems*{cb12d5ba-a6fe-41e2-b555-83c903cce92a}*SharedItemsImports = 5
..\source\LottieMetadata\LottieMetadata.projitems*{cb12d5ba-a6fe-41e2-b555-83c903cce92a}*SharedItemsImports = 5
@ -165,6 +169,7 @@ Global
{D02BE6C8-14DB-4B4F-8600-F3C9B69C104D} = {AB232F35-AAF7-4AE2-B1D2-45DD9BC2F7D7}
{04B43A1A-DDFB-4A61-BF36-39F5E666C702} = {AB232F35-AAF7-4AE2-B1D2-45DD9BC2F7D7}
{B0197C19-BDF5-473E-A022-E21F6122EEE5} = {AB232F35-AAF7-4AE2-B1D2-45DD9BC2F7D7}
{7012420D-624C-4BD4-A1D2-1C6C1655ED3A} = {AB232F35-AAF7-4AE2-B1D2-45DD9BC2F7D7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {51B9BB4C-5196-41CF-950C-12B04AD8A61C}

Просмотреть файл

@ -18,14 +18,15 @@ using Microsoft.Toolkit.Uwp.UI.Lottie.UIData.Tools;
using Microsoft.Toolkit.Uwp.UI.Lottie.WinCompData;
/// <summary>
/// Processes a single Lottie file to produce various generated outputs.
/// Processes a single Lottie .json file to produce various generated outputs.
/// </summary>
sealed class LottieFileProcessor
sealed class LottieJsonFileProcessor
{
readonly CommandLineOptions _options;
readonly Profiler _profiler = new Profiler();
readonly Reporter _reporter;
readonly string _lottieFilePath;
readonly string _sourceFilePath;
readonly string _jsonFilePath;
readonly string _outputFolder;
readonly DateTime _timestamp;
readonly uint _minimumUapVersion;
@ -39,16 +40,20 @@ sealed class LottieFileProcessor
IReadOnlyList<TranslationResult>? _translationResults;
IReadOnlyList<(TranslationIssue issue, UapVersionRange versionRange)>? _translationIssues;
LottieFileProcessor(
LottieJsonFileProcessor(
CommandLineOptions options,
Reporter reporter,
string lottieFilePath,
string sourceFilePath,
string jsonFilePath,
string className,
string outputFolder,
DateTime timestamp)
{
_options = options;
_reporter = reporter;
_lottieFilePath = lottieFilePath;
_sourceFilePath = sourceFilePath;
_jsonFilePath = jsonFilePath;
_className = className;
_outputFolder = outputFolder;
_timestamp = timestamp;
@ -56,32 +61,39 @@ sealed class LottieFileProcessor
const uint defaultUapVersion = 7;
_minimumUapVersion = _options.MinimumUapVersion ?? defaultUapVersion;
// Get an appropriate name for a generated class.
_className =
InstantiatorGeneratorBase.TrySynthesizeClassName(System.IO.Path.GetFileNameWithoutExtension(_lottieFilePath)) ??
"Lottie"; // If all else fails, just call it Lottie.
}
internal static bool ProcessLottieFile(
/// <summary>
/// Processes a single .json file containing a Lottie animation.
/// </summary>
/// <param name="options">The kinds of processing to perform.</param>
/// <param name="reporter">Receives progress and status information.</param>
/// <param name="sourceFilePath">The file in which the Lottie animation is contained.</param>
/// <param name="jsonFilePath">A path describing where the .json file is.</param>
/// <param name="className">The name to give to the generated C# and/or C++ class.</param>
/// <param name="jsonStream">The stream of bytes containined in the .json file.</param>
/// <param name="outputFolder">Where the output should be written.</param>
/// <param name="timestamp">A timestamp used to indicate when the processing took place.</param>
/// <returns><c>true</c> if the processing succeeded.</returns>
internal static bool ProcessLottieJsonFile(
CommandLineOptions options,
Reporter reporter,
string lottieFilePath,
string sourceFilePath,
string jsonFilePath,
string className,
Stream jsonStream,
string outputFolder,
DateTime timestamp)
{
try
{
return new LottieFileProcessor(options, reporter, lottieFilePath, outputFolder, timestamp).Run();
}
catch
{
reporter.WriteError($"Unhandled exception processing: {lottieFilePath}");
throw;
}
}
DateTime timestamp) =>
new LottieJsonFileProcessor(
options: options,
reporter: reporter,
sourceFilePath: sourceFilePath,
jsonFilePath: jsonFilePath,
className: className,
outputFolder: outputFolder,
timestamp: timestamp).Run(jsonStream);
bool Run()
bool Run(Stream jsonStream)
{
// Make sure we can write to the output directory.
if (!TryEnsureDirectoryExists(_outputFolder))
@ -90,14 +102,6 @@ sealed class LottieFileProcessor
return false;
}
// Read the Lottie .json text.
var jsonStream = TryReadTextFile(_lottieFilePath);
if (jsonStream is null)
{
return false;
}
// Parse the Lottie.
var lottieComposition =
LottieCompositionReader.ReadLottieCompositionFromJsonStream(
@ -109,12 +113,12 @@ sealed class LottieFileProcessor
foreach (var issue in _readerIssues)
{
_reporter.WriteInfo(InfoType.Issue, IssueToString(_lottieFilePath, issue));
_reporter.WriteInfo(InfoType.Issue, IssueToString(_jsonFilePath, issue));
}
if (lottieComposition is null)
{
_reporter.WriteError($"Failed to parse Lottie file: {_lottieFilePath}");
_reporter.WriteError($"Failed to parse Lottie file: {_jsonFilePath}");
return false;
}
@ -130,9 +134,7 @@ sealed class LottieFileProcessor
bool TryGenerateCode(LottieComposition lottieComposition)
{
var lottieFileNameWithoutExtension = System.IO.Path.GetFileNameWithoutExtension(_lottieFilePath);
var outputFileBase = System.IO.Path.Combine(_outputFolder, lottieFileNameWithoutExtension);
var outputFileBase = System.IO.Path.Combine(_outputFolder, _className);
var codeGenSucceeded = true;
@ -211,7 +213,7 @@ sealed class LottieFileProcessor
"Timing",
new[]
{
("Path", _lottieFilePath),
("Path", _jsonFilePath),
("TotalSeconds", (_profiler.ParseTime +
_profiler.TranslateTime +
_profiler.OptimizationTime +
@ -234,7 +236,7 @@ sealed class LottieFileProcessor
"Lottie",
new[]
{
("Path", _lottieFilePath),
("Path", _jsonFilePath),
("Name", _lottieStats.Name),
("BodyMovinVersion", _lottieStats.Version.ToString()),
("DurationSeconds", _lottieStats.Duration.TotalSeconds.ToString()),
@ -266,7 +268,7 @@ sealed class LottieFileProcessor
"WinComp",
new[]
{
("Path", _lottieFilePath),
("Path", _jsonFilePath),
("Animator", translationStats.AnimatorCount.ToString()),
("BooleanKeyFrameAnimation", translationStats.BooleanKeyFrameAnimationCount.ToString()),
("CanvasGeometry", translationStats.CanvasGeometryCount.ToString()),
@ -316,7 +318,7 @@ sealed class LottieFileProcessor
"WinCompOptimization",
new[]
{
("Path", _lottieFilePath),
("Path", _jsonFilePath),
("Animator", (_afterOptimizationStats.AnimatorCount - _beforeOptimizationStats.AnimatorCount).ToString()),
("BooleanKeyFrameAnimation", (_afterOptimizationStats.BooleanKeyFrameAnimationCount - _beforeOptimizationStats.BooleanKeyFrameAnimationCount).ToString()),
("CanvasGeometry", (_afterOptimizationStats.CanvasGeometryCount - _beforeOptimizationStats.CanvasGeometryCount).ToString()),
@ -364,7 +366,7 @@ sealed class LottieFileProcessor
"Errors",
new[]
{
("Path", _lottieFilePath),
("Path", _jsonFilePath),
("ErrorCode", code),
("Description", description),
});
@ -380,7 +382,7 @@ sealed class LottieFileProcessor
writer => LottieCompositionYamlSerializer.WriteYaml(
lottieComposition,
writer,
System.IO.Path.GetFileName(_lottieFilePath)));
comment: System.IO.Path.GetFileName(_jsonFilePath)));
if (result)
{
@ -624,30 +626,6 @@ sealed class LottieFileProcessor
return true;
}
Stream? TryReadTextFile(string filePath)
{
using (_reporter.InfoStream.Lock())
{
_reporter.WriteInfo($"Reading file:");
_reporter.WriteInfo(InfoType.FilePath, $" {_lottieFilePath}");
}
try
{
return File.OpenRead(filePath);
}
catch (Exception e)
{
using (_reporter.ErrorStream.Lock())
{
_reporter.WriteError($"Failed to read from {filePath}");
_reporter.WriteError(e.Message);
}
return null;
}
}
CodegenConfiguration CreateCodeGenConfiguration(
LottieComposition lottieComposition,
string languageSwitch)
@ -692,7 +670,7 @@ sealed class LottieFileProcessor
// be included in the generated output.
IEnumerable<string> GetToolInvocationInfo(string languageSwitch)
{
var inputFile = new FileInfo(_lottieFilePath);
var inputFile = new FileInfo(_sourceFilePath);
var indent = " ";
@ -818,7 +796,7 @@ sealed class LottieFileProcessor
foreach (var (issue, uapVersionRange) in _translationIssues)
{
_reporter.WriteInfo(InfoType.Issue, IssueToString(_lottieFilePath, issue, uapVersionRange));
_reporter.WriteInfo(InfoType.Issue, IssueToString(_jsonFilePath, issue, uapVersionRange));
}
// NOTE: this is only reporting on the latest version in a multi-version translation.
@ -853,5 +831,5 @@ sealed class LottieFileProcessor
// Convert namespaces to a normalized form: replace "::" with ".".
static string NormalizeNamespace(string @namespace) => @namespace.Replace("::", ".");
public override string ToString() => $"{nameof(LottieFileProcessor)} {_lottieFilePath}";
public override string ToString() => $"{nameof(LottieJsonFileProcessor)} {_jsonFilePath}";
}

Просмотреть файл

@ -151,11 +151,11 @@ sealed class Program
Parallel.ForEach(matchingInputFiles, ((string path, string relativePath) inputFile) =>
#endif // DO_NOT_PROCESS_IN_PARALLEL
{
if (!LottieFileProcessor.ProcessLottieFile(
if (!FileProcessor.ProcessFile(
_options,
_reporter,
inputFile.path,
System.IO.Path.Combine(outputFolder, inputFile.relativePath),
Path.Combine(outputFolder, inputFile.relativePath),
timestamp))
{
succeeded = false;
@ -170,18 +170,18 @@ sealed class Program
if (_options.Languages.Contains(Lang.Stats))
{
// Write the stats. Stats are collected by the Reporter from each LottieFileProcessor
// then written to files here after all of the LottieFileProcessors have finished.
// Write the stats. Stats are collected by the Reporter from each FileProcessor
// then written to files here after all of the FileProcessors have finished.
foreach (var (dataTableName, columnNames, rows) in _reporter.GetDataTables())
{
var tsvFilePath = System.IO.Path.Combine(outputFolder, $"LottieGen_{dataTableName}.tsv");
var tsvFilePath = Path.Combine(outputFolder, $"LottieGen_{dataTableName}.tsv");
_reporter.WriteInfo("Writing stats to:");
_reporter.WriteInfo(InfoType.FilePath, $" {tsvFilePath}");
using var tsvFile = File.CreateText(tsvFilePath);
tsvFile.WriteLine(string.Join("\t", columnNames));
// Sort the rows. This is necessary in order to ensure deterministic output
// when multiple LottieFileProcessors are run in parallel.
// when multiple FileProcessors are run in parallel.
Array.Sort(rows, (a, b) =>
{
for (var i = 0; i < a.Length; i++)
@ -206,10 +206,10 @@ sealed class Program
return succeeded ? RunResult.Success : RunResult.Failure;
}
static string MakeAbsolutePath(string path)
{
return System.IO.Path.IsPathRooted(path) ? path : System.IO.Path.Combine(Directory.GetCurrentDirectory(), path);
}
static string MakeAbsolutePath(string path) =>
Path.IsPathRooted(path)
? path
: Path.Combine(Directory.GetCurrentDirectory(), path);
static void ShowHelp(Reporter.Writer writer)
{
@ -229,7 +229,7 @@ Usage: {0} -InputFile LOTTIEFILE -Language LANG [Other options]
OVERVIEW:
Generates source code from Lottie files for playing in the AnimatedVisualPlayer.
LOTTIEFILE is a Lottie .json file. LOTTIEFILE may contain wildcards.
LOTTIEFILE is a Lottie .json file or .lottie file. LOTTIEFILE may contain wildcards.
LANG is one of cs, cppcx, cppwinrt, lottieyaml, dgml, or stats.
-Language LANG may be specified multiple times.

Просмотреть файл

@ -91,6 +91,40 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.DotLottie
return new MemoryStream(buffer, writable: false);
}
/// <summary>
/// Returns a <see cref="DotLottieFile"/> that reads from the given <see cref="ZipArchive"/>.
/// </summary>
/// <param name="zipArchive">The <see cref="ZipArchive"/>.</param>
/// <returns>A <see cref="DotLottieFile"/> or null on error.</returns>
public static DotLottieFile? FromZipArchive(
ZipArchive zipArchive)
{
// The manifest contains information about the animation.
var manifestEntry = GetManifestEntry(zipArchive);
if (manifestEntry is null)
{
// Not a valid .lottie file.
return null;
}
var manifestBytes = new byte[manifestEntry.Length];
using var manifestStream = manifestEntry.Open();
var bytesRead = manifestStream.Read(
manifestBytes,
0,
manifestBytes.Length);
if (bytesRead != manifestBytes.Length)
{
// Failed to read the manifest
return null;
}
var manifest = Manifest.ParseManifest(manifestBytes);
return manifest is null ? null : new DotLottieFile(zipArchive, manifest);
}
/// <summary>
/// Returns a <see cref="DotLottieFile"/> that reads from the given <see cref="ZipArchive"/>.
/// </summary>
@ -100,7 +134,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.DotLottie
ZipArchive zipArchive)
{
// The manifest contains information about the animation.
var manifestEntry = zipArchive.GetEntry("manifest.json");
var manifestEntry = GetManifestEntry(zipArchive);
if (manifestEntry is null)
{
@ -132,6 +166,9 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.DotLottie
internal ZipArchive ZipArchive { get; }
static ZipArchiveEntry? GetManifestEntry(ZipArchive zipArchive)
=> zipArchive.GetEntry("manifest.json");
ZipArchiveEntry GetEntryForPath(string path)
{
if (path.StartsWith("/"))

Просмотреть файл

@ -8,6 +8,10 @@ using System.IO;
namespace Microsoft.Toolkit.Uwp.UI.Lottie.DotLottie
{
/// <summary>
/// Describes an animation in a .lottie file. Animations
/// are Lottie .json files.
/// </summary>
sealed class DotLottieFileAnimation
{
readonly DotLottieFile _owner;
@ -26,8 +30,11 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.DotLottie
public bool Loop { get; }
public string Path => $"animations/{Id}.json";
/// <summary>
/// The path to the animation in the .lottie archive.
/// </summary>
public string Path => $"/animations/{Id}.json";
public Stream Open() => _owner.ZipArchive.GetEntry(Path).Open();
public Stream Open() => _owner.ZipArchive.GetEntry(Path.Substring(1)).Open();
}
}

Просмотреть файл

@ -159,25 +159,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen
/// </summary>
protected IAnimatedVisualSourceInfo AnimatedVisualSourceInfo => this;
/// <summary>
/// Takes a name and modifies it as necessary to be suited for use as a class name in languages such
/// as C# and C++.
/// Returns null on failure.
/// </summary>
/// <returns>A name, or null.</returns>
public static string? TrySynthesizeClassName(string? name)
{
if (string.IsNullOrWhiteSpace(name))
{
return null;
}
// Remove any leading punctuation.
var prefixSize = name.TakeWhile(c => !char.IsLetterOrDigit(c)).Count();
return SanitizeTypeName(name.Substring(prefixSize));
}
/// <summary>
/// Gets the standard header text used to indicate that a file contains auto-generated content.
/// </summary>
@ -543,34 +524,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen
return _currentAnimatedVisualGenerator!.CallFactoryFor(obj);
}
// Makes the given name suitable for use as a class name in languages such as C# and C++.
static string? SanitizeTypeName(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return null;
}
// If the first character is not a letter, prepend an underscore.
if (!char.IsLetter(name, 0))
{
name = "_" + name;
}
// Replace any disallowed character with underscores.
name =
new string((from ch in name
select char.IsLetterOrDigit(ch) ? ch : '_').ToArray());
// Remove any duplicated underscores.
name = name.Replace("__", "_");
// Capitalize the first letter.
name = name.ToUpperInvariant().Substring(0, 1) + name.Substring(1);
return name;
}
// Makes the given text suitable for use as a single line comment.
static string? SanitizeCommentLine(string text)
{