Skip to content

Commit bfc6340

Browse files
committed
More coverage
1 parent 34b0d39 commit bfc6340

17 files changed

Lines changed: 756 additions & 59 deletions

File tree

examples/graphics/CMakeLists.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ set (target_version "1.0.0")
2626
project (${target_name} VERSION ${target_version})
2727

2828
set (rive_file "data/alien.riv")
29+
set (lottie_file "data/goal.lottie")
2930

3031
# ==== Prepare Android build
3132
set (link_libraries "")
@@ -40,8 +41,10 @@ if (ANDROID)
4041
NAMESPACE yup
4142
RESOURCE_NAMES
4243
RiveFile
44+
LottieFile
4345
RESOURCES
44-
"${CMAKE_CURRENT_LIST_DIR}/${rive_file}")
46+
"${CMAKE_CURRENT_LIST_DIR}/${rive_file}"
47+
"${CMAKE_CURRENT_LIST_DIR}/${lottie_file}")
4548

4649
set (link_libraries "${target_name}_binary_data")
4750
endif()
@@ -64,9 +67,11 @@ yup_standalone_app (
6467
STACK_SIZE 4194304
6568
DEFINITIONS
6669
YUP_EXAMPLE_GRAPHICS_RIVE_FILE="${rive_file}"
70+
YUP_EXAMPLE_GRAPHICS_LOTTIE_FILE="${lottie_file}"
6771
${additional_definitions}
6872
PRELOAD_FILES
6973
"${CMAKE_CURRENT_LIST_DIR}/${rive_file}@${rive_file}"
74+
"${CMAKE_CURRENT_LIST_DIR}/${lottie_file}@${lottie_file}"
7075
MODULES
7176
yup::yup_core
7277
yup::yup_audio_basics

examples/graphics/source/examples/Artboard.h

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,8 @@ class ArtboardDemo : public yup::Component
4141
#if YUP_ANDROID
4242
yup::MemoryInputStream is (yup::RiveFile_data, yup::RiveFile_size, false);
4343
auto artboardFile = yup::ArtboardFile::load (is, *factory);
44-
45-
#else
46-
yup::File riveBasePath;
47-
#if YUP_WASM
48-
riveBasePath = yup::File ("/");
4944
#else
50-
riveBasePath = yup::File (__FILE__)
51-
.getParentDirectory()
52-
.getParentDirectory()
53-
.getParentDirectory();
54-
#endif
55-
56-
auto artboardFile = yup::ArtboardFile::load (riveBasePath.getChildFile (YUP_EXAMPLE_GRAPHICS_RIVE_FILE), *factory);
45+
auto artboardFile = yup::ArtboardFile::load (getAssetPath (YUP_EXAMPLE_GRAPHICS_RIVE_FILE), *factory);
5746
#endif
5847
if (! artboardFile)
5948
return false;

examples/graphics/source/examples/LottieDemo.h

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,12 +161,18 @@ class LottieDemo : public yup::Component
161161
{
162162
playPauseButton->setButtonText ("Play");
163163
};
164-
}
165164

166-
~LottieDemo() override
167-
{
165+
#if YUP_ANDROID
166+
yup::MemoryInputStream is (yup::LottieFile_data, yup::LottieFile_size, false);
167+
loadStream (is);
168+
169+
#else
170+
loadFile (getAssetPath (YUP_EXAMPLE_GRAPHICS_LOTTIE_FILE));
171+
#endif
168172
}
169173

174+
~LottieDemo() override = default;
175+
170176
void refreshDisplay (double lastFrameTimeSeconds) override
171177
{
172178
if (! player.isPlaying())
@@ -269,6 +275,18 @@ class LottieDemo : public yup::Component
269275
});
270276
}
271277

278+
void loadStream (yup::MemoryInputStream& stream)
279+
{
280+
auto anim = yup::Animation::loadFromStream (stream);
281+
if (! anim.isValid())
282+
{
283+
statusLabel->setText ("Failed to load animation from stream", yup::dontSendNotification);
284+
return;
285+
}
286+
287+
initializePlayer (std::move (anim));
288+
}
289+
272290
void loadFile (const yup::File& file)
273291
{
274292
auto anim = yup::Animation::loadFromFile (file);
@@ -278,6 +296,11 @@ class LottieDemo : public yup::Component
278296
return;
279297
}
280298

299+
initializePlayer (std::move (anim), file);
300+
}
301+
302+
void initializePlayer (yup::Animation anim, const yup::File& file = {})
303+
{
281304
player.stop();
282305
player.setAnimation (std::move (anim));
283306
player.setLooping (looping);

examples/graphics/source/main.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,28 @@
3737
#include <BinaryData.h>
3838
#endif
3939

40+
//==============================================================================
41+
42+
inline yup::File getAssetPath (yup::StringRef subPath = {})
43+
{
44+
yup::File basePath;
45+
46+
#if YUP_WASM
47+
basePath = yup::File ("/");
48+
#else
49+
basePath = yup::File (__FILE__)
50+
.getParentDirectory()
51+
.getParentDirectory();
52+
#endif
53+
54+
if (! subPath.isEmpty())
55+
basePath = basePath.getChildFile (subPath);
56+
57+
return basePath;
58+
}
59+
60+
//==============================================================================
61+
4062
#include "examples/Artboard.h"
4163
#include "examples/Audio.h"
4264
#include "examples/AudioFileDemo.h"

modules/yup_animation/animation/yup_Animation.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,18 @@ Animation Animation::loadFromData (const String& jsonText, const LoadOptions& op
6666
return Animation (std::move (comp));
6767
}
6868

69+
Animation Animation::loadFromStream (InputStream& stream, const LoadOptions& opts)
70+
{
71+
LottieLoadOptions loaderOpts;
72+
loaderOpts.resourceDirectory = opts.resourceDirectory;
73+
74+
auto comp = LottieReader::parseStream (stream, loaderOpts);
75+
if (comp == nullptr)
76+
return {};
77+
78+
return Animation (std::move (comp));
79+
}
80+
6981
Animation Animation::fromComposition (AnimationComposition::Ptr comp)
7082
{
7183
return Animation (std::move (comp));

modules/yup_animation/animation/yup_Animation.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ class YUP_API Animation
7777
/** Loads a Lottie animation from a JSON string. */
7878
[[nodiscard]] static Animation loadFromData (const String& jsonText, const LoadOptions& opts = {});
7979

80+
/** Loads a Lottie animation from an InputStream.
81+
The stream is fully consumed. Both plain JSON and .lottie ZIP streams are supported.
82+
Returns an invalid Animation on failure. */
83+
[[nodiscard]] static Animation loadFromStream (InputStream& stream, const LoadOptions& opts = {});
84+
8085
/** Wraps an already-constructed AnimationComposition. */
8186
[[nodiscard]] static Animation fromComposition (AnimationComposition::Ptr comp);
8287

modules/yup_animation/io/yup_LottieReader.cpp

Lines changed: 83 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -132,23 +132,19 @@ AnimationComposition::Ptr LottieReader::parseFile (const File& file,
132132
return {};
133133
}
134134

135-
// .lottie files are ZIP archives
136-
if (file.getFileExtension().equalsIgnoreCase (".lottie"))
137-
return parseFromZip (file, {}, options, outError);
135+
LottieLoadOptions opts = options;
136+
if (opts.resourceDirectory == File())
137+
opts.resourceDirectory = file.getParentDirectory();
138138

139-
const String text = file.loadFileAsString();
140-
if (text.isEmpty())
139+
auto stream = file.createInputStream();
140+
if (stream == nullptr)
141141
{
142142
if (outError != nullptr)
143-
*outError = "Empty file: " + file.getFullPathName();
143+
*outError = "Failed to open file: " + file.getFullPathName();
144144
return {};
145145
}
146146

147-
LottieLoadOptions opts = options;
148-
if (opts.resourceDirectory == File())
149-
opts.resourceDirectory = file.getParentDirectory();
150-
151-
return parseData (text, opts, outError);
147+
return parseStream (*stream, opts, outError);
152148
}
153149

154150
//==============================================================================
@@ -169,6 +165,78 @@ AnimationComposition::Ptr LottieReader::parseData (const String& jsonText,
169165
return reader.parseRoot (root);
170166
}
171167

168+
//==============================================================================
169+
AnimationComposition::Ptr LottieReader::parseStream (InputStream& stream,
170+
const LottieLoadOptions& options,
171+
String* outError)
172+
{
173+
MemoryBlock data;
174+
const auto bytesRead = stream.readIntoMemoryBlock (data);
175+
if (bytesRead == 0 || data.isEmpty())
176+
{
177+
if (outError != nullptr)
178+
*outError = "Empty or unreadable stream";
179+
return {};
180+
}
181+
182+
// .lottie ZIP archives start with "PK" magic bytes
183+
if (data.getSize() >= 2 && memcmp (data.getData(), "PK", 2) == 0)
184+
{
185+
MemoryInputStream memStream (data, false);
186+
ZipFile zip (memStream);
187+
188+
const auto* manifestEntry = zip.getEntry ("manifest.json", true);
189+
if (manifestEntry == nullptr)
190+
{
191+
if (outError != nullptr)
192+
*outError = "manifest.json not found in .lottie stream";
193+
return {};
194+
}
195+
196+
std::unique_ptr<InputStream> manifestStream (zip.createStreamForEntry (*manifestEntry));
197+
if (manifestStream == nullptr)
198+
return {};
199+
200+
var manifest;
201+
if (JSON::parse (manifestStream->readEntireStreamAsString(), manifest).failed())
202+
{
203+
if (outError != nullptr)
204+
*outError = "Failed to parse manifest.json";
205+
return {};
206+
}
207+
208+
const auto* anims = safeArray (manifest["animations"]);
209+
if (anims == nullptr || anims->isEmpty())
210+
{
211+
if (outError != nullptr)
212+
*outError = "No animations found in manifest";
213+
return {};
214+
}
215+
216+
const auto& anim = (*anims)[0];
217+
const String animId = varString (anim["id"]);
218+
String jsonPath = varString (anim["path"]);
219+
if (jsonPath.isEmpty())
220+
jsonPath = "animations/" + animId + ".json";
221+
222+
const auto* jsonEntry = zip.getEntry (jsonPath, true);
223+
if (jsonEntry == nullptr)
224+
{
225+
if (outError != nullptr)
226+
*outError = "Animation JSON not found inside archive: " + jsonPath;
227+
return {};
228+
}
229+
230+
std::unique_ptr<InputStream> jsonStream (zip.createStreamForEntry (*jsonEntry));
231+
if (jsonStream == nullptr)
232+
return {};
233+
234+
return parseData (jsonStream->readEntireStreamAsString(), options, outError);
235+
}
236+
237+
return parseData (data.toString(), options, outError);
238+
}
239+
172240
//==============================================================================
173241
std::vector<String> LottieReader::listAnimationIds (const File& lottieZipFile)
174242
{
@@ -240,8 +308,8 @@ AnimationComposition::Ptr LottieReader::parseFromZip (const File& lottieZipFile,
240308
if (animationId.isEmpty() || id == animationId)
241309
{
242310
jsonPath = varString (anim["path"]);
243-
if (! jsonPath.endsWithIgnoreCase (".json"))
244-
jsonPath += ".json";
311+
if (jsonPath.isEmpty())
312+
jsonPath = "animations/" + id + ".json";
245313
break;
246314
}
247315
}
@@ -644,7 +712,7 @@ AnimationLayer::Ptr LottieReader::parseLayer (const var& layerObj)
644712
il->assetRefId = varString (layerObj["refId"]);
645713
layer = il;
646714
}
647-
else if (ty == 5) // Text — parsed as NullLayer (text rendering not yet supported)
715+
else if (ty == 5) // Text — TODO: parsed as NullLayer (text rendering not yet supported)
648716
{
649717
layer = new NullLayer();
650718
}
@@ -656,7 +724,7 @@ AnimationLayer::Ptr LottieReader::parseLayer (const var& layerObj)
656724
if (layer == nullptr)
657725
return {};
658726

659-
// Self-parenting check (gap 22)
727+
// Self-parenting check
660728
if (layer->parentId >= 0 && layer->id == layer->parentId)
661729
{
662730
if (errorOut_ != nullptr)

modules/yup_animation/io/yup_LottieReader.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ class YUP_API LottieReader
5757
const LottieLoadOptions& options = {},
5858
String* outError = nullptr);
5959

60+
/** Parses a Lottie animation from an InputStream.
61+
The stream is fully consumed. Both plain Lottie JSON and .lottie ZIP (binary)
62+
streams are supported; the format is auto-detected.
63+
Returns nullptr on failure.
64+
*/
65+
[[nodiscard]] static AnimationComposition::Ptr parseStream (InputStream& stream,
66+
const LottieLoadOptions& options = {},
67+
String* outError = nullptr);
68+
6069
/** Lists animation IDs contained inside a `.lottie` ZIP archive.
6170
Returns an empty vector if the file is not a valid .lottie file.
6271
*/

tests/data/lottie/goal.lottie

10.4 KB
Binary file not shown.

tests/data/lottie/image_embedded.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)