1 /*
2 * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved.
3
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 */
22
23 //#define DEBUG_PARSER
24
25 // This parser implements JSON token-by-token parsing with an API that is
26 // more direct; we don't have to create handler object and
27 // callbacks. Instead, we retrieve values from the JSON stream by calling
28 // GetInt(), GetDouble(), GetString() and GetBool(), traverse into structures
29 // by calling EnterObject() and EnterArray(), and skip over unwanted data by
30 // calling SkipValue(). As we know the lottie file structure this way will be
31 // the efficient way of parsing the file.
32 //
33 // If you aren't sure of what's next in the JSON data, you can use PeekType()
34 // and PeekValue() to look ahead to the next object before reading it.
35 //
36 // If you call the wrong retrieval method--e.g. GetInt when the next JSON token
37 // is not an int, EnterObject or EnterArray when there isn't actually an object
38 // or array to read--the stream parsing will end immediately and no more data
39 // will be delivered.
40 //
41 // After calling EnterObject, you retrieve keys via NextObjectKey() and values
42 // via the normal getters. When NextObjectKey() returns null, you have exited
43 // the object, or you can call SkipObject() to skip to the end of the object
44 // immediately. If you fetch the entire object (i.e. NextObjectKey() returned
45 // null), you should not call SkipObject().
46 //
47 // After calling EnterArray(), you must alternate between calling
48 // NextArrayValue() to see if the array has more data, and then retrieving
49 // values via the normal getters. You can call SkipArray() to skip to the end of
50 // the array immediately. If you fetch the entire array (i.e. NextArrayValue()
51 // returned null), you should not call SkipArray().
52 //
53 // This parser uses in-situ strings, so the JSON buffer will be altered during
54 // the parse.
55
56 #include <array>
57
58 #include "lottiemodel.h"
59 #include "rapidjson/document.h"
60
61 RAPIDJSON_DIAG_PUSH
62 #ifdef __GNUC__
63 RAPIDJSON_DIAG_OFF(effc++)
64 #endif
65
66 using namespace rapidjson;
67
68 using namespace rlottie::internal;
69
70 class LookaheadParserHandler {
71 public:
Null()72 bool Null()
73 {
74 st_ = kHasNull;
75 v_.SetNull();
76 return true;
77 }
Bool(bool b)78 bool Bool(bool b)
79 {
80 st_ = kHasBool;
81 v_.SetBool(b);
82 return true;
83 }
Int(int i)84 bool Int(int i)
85 {
86 st_ = kHasNumber;
87 v_.SetInt(i);
88 return true;
89 }
Uint(unsigned u)90 bool Uint(unsigned u)
91 {
92 st_ = kHasNumber;
93 v_.SetUint(u);
94 return true;
95 }
Int64(int64_t i)96 bool Int64(int64_t i)
97 {
98 st_ = kHasNumber;
99 v_.SetInt64(i);
100 return true;
101 }
Uint64(int64_t u)102 bool Uint64(int64_t u)
103 {
104 st_ = kHasNumber;
105 v_.SetUint64(u);
106 return true;
107 }
Double(double d)108 bool Double(double d)
109 {
110 st_ = kHasNumber;
111 v_.SetDouble(d);
112 return true;
113 }
RawNumber(const char *,SizeType,bool)114 bool RawNumber(const char *, SizeType, bool) { return false; }
String(const char * str,SizeType length,bool)115 bool String(const char *str, SizeType length, bool)
116 {
117 st_ = kHasString;
118 v_.SetString(str, length);
119 return true;
120 }
StartObject()121 bool StartObject()
122 {
123 st_ = kEnteringObject;
124 return true;
125 }
Key(const char * str,SizeType length,bool)126 bool Key(const char *str, SizeType length, bool)
127 {
128 st_ = kHasKey;
129 v_.SetString(str, length);
130 return true;
131 }
EndObject(SizeType)132 bool EndObject(SizeType)
133 {
134 st_ = kExitingObject;
135 return true;
136 }
StartArray()137 bool StartArray()
138 {
139 st_ = kEnteringArray;
140 return true;
141 }
EndArray(SizeType)142 bool EndArray(SizeType)
143 {
144 st_ = kExitingArray;
145 return true;
146 }
147
Error()148 void Error()
149 {
150 st_ = kError;
151 }
152 protected:
153 explicit LookaheadParserHandler(char *str);
154
155 protected:
156 enum LookaheadParsingState {
157 kInit,
158 kError,
159 kHasNull,
160 kHasBool,
161 kHasNumber,
162 kHasString,
163 kHasKey,
164 kEnteringObject,
165 kExitingObject,
166 kEnteringArray,
167 kExitingArray
168 };
169
170 Value v_;
171 LookaheadParsingState st_;
172 Reader r_;
173 InsituStringStream ss_;
174
175 static const int parseFlags = kParseDefaultFlags | kParseInsituFlag;
176 };
177
178 class LottieParserImpl : public LookaheadParserHandler {
179 public:
LottieParserImpl(char * str,std::string dir_path,model::ColorFilter filter)180 LottieParserImpl(char *str, std::string dir_path, model::ColorFilter filter)
181 : LookaheadParserHandler(str),
182 mColorFilter(std::move(filter)),
183 mDirPath(std::move(dir_path))
184 {
185 }
186 bool VerifyType();
187 bool ParseNext();
188
189 public:
allocator()190 VArenaAlloc &allocator() { return compRef->mArenaAlloc; }
191 bool EnterObject();
192 bool EnterArray();
193 const char * NextObjectKey();
194 bool NextArrayValue();
195 int GetInt();
196 double GetDouble();
197 const char * GetString();
198 std::string GetStringObject();
199 bool GetBool();
200 void GetNull();
201
202 void SkipObject();
203 void SkipArray();
204 void SkipValue();
205 Value *PeekValue();
206 int PeekType() const;
IsValid()207 bool IsValid() { return st_ != kError; }
208
209 void Skip(const char *key);
210 model::BlendMode getBlendMode();
211 CapStyle getLineCap();
212 JoinStyle getLineJoin();
213 FillRule getFillRule();
214 model::Trim::TrimType getTrimType();
215 model::MatteType getMatteType();
216 model::Layer::Type getLayerType();
217
composition() const218 std::shared_ptr<model::Composition> composition() const
219 {
220 return mComposition;
221 }
222 void parseComposition();
223 void parseMarkers();
224 void parseMarker();
225 void parseAssets(model::Composition *comp);
226 model::Asset * parseAsset();
227 void parseLayers(model::Composition *comp);
228 model::Layer * parseLayer();
229 void parseMaskProperty(model::Layer *layer);
230 void parseShapesAttr(model::Layer *layer);
231 void parseObject(model::Group *parent);
232 model::Mask * parseMaskObject();
233 model::Object * parseObjectTypeAttr();
234 model::Object * parseGroupObject();
235 model::Rect * parseRectObject();
236 model::RoundedCorner * parseRoundedCorner();
237 void updateRoundedCorner(model::Group *parent, model::RoundedCorner *rc);
238
239 model::Ellipse * parseEllipseObject();
240 model::Path * parseShapeObject();
241 model::Polystar *parsePolystarObject();
242
243 model::Transform * parseTransformObject(bool ddd = false);
244 model::Fill * parseFillObject();
245 model::GradientFill * parseGFillObject();
246 model::Stroke * parseStrokeObject();
247 model::GradientStroke *parseGStrokeObject();
248 model::Trim * parseTrimObject();
249 model::Repeater * parseReapeaterObject();
250
251 void parseGradientProperty(model::Gradient *gradient, const char *key);
252
253 VPointF parseInperpolatorPoint();
254
255 void getValue(VPointF &pt);
256 void getValue(float &fval);
257 void getValue(model::Color &color);
258 void getValue(int &ival);
259 void getValue(model::PathData &shape);
260 void getValue(model::Gradient::Data &gradient);
261 void getValue(std::vector<VPointF> &v);
262 void getValue(model::Repeater::Transform &);
263
264 template <typename T, typename Tag>
parseKeyFrameValue(const char *,model::Value<T,Tag> &)265 bool parseKeyFrameValue(const char *, model::Value<T, Tag> &)
266 {
267 return false;
268 }
269
270 template <typename T>
271 bool parseKeyFrameValue(const char * key,
272 model::Value<T, model::Position> &value);
273 template <typename T, typename Tag>
274 void parseKeyFrame(model::KeyFrames<T, Tag> &obj);
275 template <typename T>
276 void parseProperty(model::Property<T> &obj);
277 template <typename T, typename Tag>
278 void parsePropertyHelper(model::Property<T, Tag> &obj);
279
280 void parseShapeProperty(model::Property<model::PathData> &obj);
281 void parseDashProperty(model::Dash &dash);
282
283 VInterpolator *interpolator(VPointF, VPointF, std::string);
284
285 model::Color toColor(const char *str);
286
287 void resolveLayerRefs();
288 void parsePathInfo();
289
290 private:
291 model::ColorFilter mColorFilter;
292 struct {
293 std::vector<VPointF> mInPoint; /* "i" */
294 std::vector<VPointF> mOutPoint; /* "o" */
295 std::vector<VPointF> mVertices; /* "v" */
296 std::vector<VPointF> mResult;
297 bool mClosed{false};
298
convertLottieParserImpl::__anone5b98c9e0108299 void convert()
300 {
301 // shape data could be empty.
302 if (mInPoint.empty() || mOutPoint.empty() || mVertices.empty()) {
303 mResult.clear();
304 return;
305 }
306
307 /*
308 * Convert the AE shape format to
309 * list of bazier curves
310 * The final structure will be Move +size*Cubic + Cubic (if the path
311 * is closed one)
312 */
313 if (mInPoint.size() != mOutPoint.size() ||
314 mInPoint.size() != mVertices.size()) {
315 mResult.clear();
316 } else {
317 auto size = mVertices.size();
318 mResult.push_back(mVertices[0]);
319 for (size_t i = 1; i < size; i++) {
320 mResult.push_back(
321 mVertices[i - 1] +
322 mOutPoint[i - 1]); // CP1 = start + outTangent
323 mResult.push_back(mVertices[i] +
324 mInPoint[i]); // CP2 = end + inTangent
325 mResult.push_back(mVertices[i]); // end point
326 }
327
328 if (mClosed) {
329 mResult.push_back(
330 mVertices[size - 1] +
331 mOutPoint[size - 1]); // CP1 = start + outTangent
332 mResult.push_back(mVertices[0] +
333 mInPoint[0]); // CP2 = end + inTangent
334 mResult.push_back(mVertices[0]); // end point
335 }
336 }
337 }
resetLottieParserImpl::__anone5b98c9e0108338 void reset()
339 {
340 mInPoint.clear();
341 mOutPoint.clear();
342 mVertices.clear();
343 mResult.clear();
344 mClosed = false;
345 }
updatePathLottieParserImpl::__anone5b98c9e0108346 void updatePath(VPath &out)
347 {
348 if (mResult.empty()) return;
349
350 auto size = mResult.size();
351 auto points = mResult.data();
352 /* reserve exact memory requirement at once
353 * ptSize = size + 1(size + close)
354 * elmSize = size/3 cubic + 1 move + 1 close
355 */
356 out.reserve(size + 1, size / 3 + 2);
357 out.moveTo(points[0]);
358 for (size_t i = 1; i < size; i += 3) {
359 out.cubicTo(points[i], points[i + 1], points[i + 2]);
360 }
361 if (mClosed) out.close();
362 }
363 } mPathInfo;
364
365 protected:
366 std::unordered_map<std::string, VInterpolator *> mInterpolatorCache;
367 std::shared_ptr<model::Composition> mComposition;
368 model::Composition * compRef{nullptr};
369 model::Layer * curLayerRef{nullptr};
370 std::vector<model::Layer *> mLayersToUpdate;
371 std::string mDirPath;
372 void SkipOut(int depth);
373 };
374
LookaheadParserHandler(char * str)375 LookaheadParserHandler::LookaheadParserHandler(char *str)
376 : v_(), st_(kInit), ss_(str)
377 {
378 r_.IterativeParseInit();
379 }
380
VerifyType()381 bool LottieParserImpl::VerifyType()
382 {
383 /* Verify the media type is lottie json.
384 Could add more strict check. */
385 return ParseNext();
386 }
387
ParseNext()388 bool LottieParserImpl::ParseNext()
389 {
390 if (r_.HasParseError()) {
391 st_ = kError;
392 return false;
393 }
394
395 if (!r_.IterativeParseNext<parseFlags>(ss_, *this)) {
396 vCritical << "Lottie file parsing error";
397 st_ = kError;
398 return false;
399 }
400 return true;
401 }
402
EnterObject()403 bool LottieParserImpl::EnterObject()
404 {
405 if (st_ != kEnteringObject) {
406 st_ = kError;
407 return false;
408 }
409
410 ParseNext();
411 return true;
412 }
413
EnterArray()414 bool LottieParserImpl::EnterArray()
415 {
416 if (st_ != kEnteringArray) {
417 st_ = kError;
418 return false;
419 }
420
421 ParseNext();
422 return true;
423 }
424
NextObjectKey()425 const char *LottieParserImpl::NextObjectKey()
426 {
427 if (st_ == kHasKey) {
428 const char *result = v_.GetString();
429 ParseNext();
430 return result;
431 }
432
433 /* SPECIAL CASE
434 * The parser works with a prdefined rule that it will be only
435 * while (NextObjectKey()) for each object but in case of our nested group
436 * object we can call multiple time NextObjectKey() while exiting the object
437 * so ignore those and don't put parser in the error state.
438 * */
439 if (st_ == kExitingArray || st_ == kEnteringObject) {
440 // #ifdef DEBUG_PARSER
441 // vDebug<<"Object: Exiting nested loop";
442 // #endif
443 return nullptr;
444 }
445
446 if (st_ != kExitingObject) {
447 st_ = kError;
448 return nullptr;
449 }
450
451 ParseNext();
452 return nullptr;
453 }
454
NextArrayValue()455 bool LottieParserImpl::NextArrayValue()
456 {
457 if (st_ == kExitingArray) {
458 ParseNext();
459 return false;
460 }
461
462 /* SPECIAL CASE
463 * same as NextObjectKey()
464 */
465 if (st_ == kExitingObject) {
466 return false;
467 }
468
469 if (st_ == kError || st_ == kHasKey) {
470 st_ = kError;
471 return false;
472 }
473
474 return true;
475 }
476
GetInt()477 int LottieParserImpl::GetInt()
478 {
479 if (st_ != kHasNumber || !v_.IsInt()) {
480 st_ = kError;
481 return 0;
482 }
483
484 int result = v_.GetInt();
485 ParseNext();
486 return result;
487 }
488
GetDouble()489 double LottieParserImpl::GetDouble()
490 {
491 if (st_ != kHasNumber) {
492 st_ = kError;
493 return 0.;
494 }
495
496 double result = v_.GetDouble();
497 ParseNext();
498 return result;
499 }
500
GetBool()501 bool LottieParserImpl::GetBool()
502 {
503 if (st_ != kHasBool) {
504 st_ = kError;
505 return false;
506 }
507
508 bool result = v_.GetBool();
509 ParseNext();
510 return result;
511 }
512
GetNull()513 void LottieParserImpl::GetNull()
514 {
515 if (st_ != kHasNull) {
516 st_ = kError;
517 return;
518 }
519
520 ParseNext();
521 }
522
GetString()523 const char *LottieParserImpl::GetString()
524 {
525 if (st_ != kHasString) {
526 st_ = kError;
527 return nullptr;
528 }
529
530 const char *result = v_.GetString();
531 ParseNext();
532 return result;
533 }
534
GetStringObject()535 std::string LottieParserImpl::GetStringObject()
536 {
537 auto str = GetString();
538
539 if (str) {
540 return std::string(str);
541 }
542
543 return {};
544 }
545
SkipOut(int depth)546 void LottieParserImpl::SkipOut(int depth)
547 {
548 do {
549 if (st_ == kEnteringArray || st_ == kEnteringObject) {
550 ++depth;
551 } else if (st_ == kExitingArray || st_ == kExitingObject) {
552 --depth;
553 } else if (st_ == kError) {
554 return;
555 }
556
557 ParseNext();
558 } while (depth > 0);
559 }
560
SkipValue()561 void LottieParserImpl::SkipValue()
562 {
563 SkipOut(0);
564 }
565
SkipArray()566 void LottieParserImpl::SkipArray()
567 {
568 SkipOut(1);
569 }
570
SkipObject()571 void LottieParserImpl::SkipObject()
572 {
573 SkipOut(1);
574 }
575
PeekValue()576 Value *LottieParserImpl::PeekValue()
577 {
578 if (st_ >= kHasNull && st_ <= kHasKey) {
579 return &v_;
580 }
581
582 return nullptr;
583 }
584
585 // returns a rapidjson::Type, or -1 for no value (at end of
586 // object/array)
PeekType() const587 int LottieParserImpl::PeekType() const
588 {
589 if (st_ >= kHasNull && st_ <= kHasKey) {
590 return v_.GetType();
591 }
592
593 if (st_ == kEnteringArray) {
594 return kArrayType;
595 }
596
597 if (st_ == kEnteringObject) {
598 return kObjectType;
599 }
600
601 return -1;
602 }
603
Skip(const char *)604 void LottieParserImpl::Skip(const char * /*key*/)
605 {
606 if (PeekType() == kArrayType) {
607 EnterArray();
608 SkipArray();
609 } else if (PeekType() == kObjectType) {
610 EnterObject();
611 SkipObject();
612 } else {
613 SkipValue();
614 }
615 }
616
getBlendMode()617 model::BlendMode LottieParserImpl::getBlendMode()
618 {
619 auto mode = model::BlendMode::Normal;
620
621 switch (GetInt()) {
622 case 1:
623 mode = model::BlendMode::Multiply;
624 break;
625 case 2:
626 mode = model::BlendMode::Screen;
627 break;
628 case 3:
629 mode = model::BlendMode::OverLay;
630 break;
631 default:
632 break;
633 }
634 return mode;
635 }
636
resolveLayerRefs()637 void LottieParserImpl::resolveLayerRefs()
638 {
639 for (const auto &layer : mLayersToUpdate) {
640 auto search = compRef->mAssets.find(layer->extra()->mPreCompRefId);
641 if (search != compRef->mAssets.end()) {
642 if (layer->mLayerType == model::Layer::Type::Image) {
643 layer->extra()->mAsset = search->second;
644 } else if (layer->mLayerType == model::Layer::Type::Precomp) {
645 layer->mChildren = search->second->mLayers;
646 layer->setStatic(layer->isStatic() &&
647 search->second->isStatic());
648 }
649 }
650 }
651 }
652
parseComposition()653 void LottieParserImpl::parseComposition()
654 {
655 EnterObject();
656 std::shared_ptr<model::Composition> sharedComposition =
657 std::make_shared<model::Composition>();
658 model::Composition *comp = sharedComposition.get();
659 compRef = comp;
660 while (const char *key = NextObjectKey()) {
661 if (0 == strcmp(key, "v")) {
662 comp->mVersion = GetStringObject();
663 } else if (0 == strcmp(key, "w")) {
664 comp->mSize.setWidth(GetInt());
665 } else if (0 == strcmp(key, "h")) {
666 comp->mSize.setHeight(GetInt());
667 } else if (0 == strcmp(key, "ip")) {
668 comp->mStartFrame = GetDouble();
669 } else if (0 == strcmp(key, "op")) {
670 comp->mEndFrame = GetDouble();
671 } else if (0 == strcmp(key, "fr")) {
672 comp->mFrameRate = GetDouble();
673 } else if (0 == strcmp(key, "assets")) {
674 parseAssets(comp);
675 } else if (0 == strcmp(key, "layers")) {
676 parseLayers(comp);
677 } else if (0 == strcmp(key, "markers")) {
678 parseMarkers();
679 } else {
680 #ifdef DEBUG_PARSER
681 vWarning << "Composition Attribute Skipped : " << key;
682 #endif
683 Skip(key);
684 }
685 }
686
687 if (comp->mVersion.empty() || !comp->mRootLayer) {
688 // don't have a valid bodymovin header
689 return;
690 }
691 if (comp->mStartFrame > comp->mEndFrame) {
692 // reveresed animation? missing data?
693 return;
694 }
695 if (!IsValid()) {
696 return;
697 }
698
699 resolveLayerRefs();
700 comp->setStatic(comp->mRootLayer->isStatic());
701 comp->mRootLayer->mInFrame = comp->mStartFrame;
702 comp->mRootLayer->mOutFrame = comp->mEndFrame;
703
704 mComposition = sharedComposition;
705 }
706
parseMarker()707 void LottieParserImpl::parseMarker()
708 {
709 EnterObject();
710 std::string comment;
711 int timeframe{0};
712 int duration{0};
713 while (const char *key = NextObjectKey()) {
714 if (0 == strcmp(key, "cm")) {
715 comment = GetStringObject();
716 } else if (0 == strcmp(key, "tm")) {
717 timeframe = GetDouble();
718 } else if (0 == strcmp(key, "dr")) {
719 duration = GetDouble();
720
721 } else {
722 #ifdef DEBUG_PARSER
723 vWarning << "Marker Attribute Skipped : " << key;
724 #endif
725 Skip(key);
726 }
727 }
728 compRef->mMarkers.emplace_back(std::move(comment), timeframe,
729 timeframe + duration);
730 }
731
parseMarkers()732 void LottieParserImpl::parseMarkers()
733 {
734 EnterArray();
735 while (NextArrayValue()) {
736 parseMarker();
737 }
738 // update the precomp layers with the actual layer object
739 }
740
parseAssets(model::Composition * composition)741 void LottieParserImpl::parseAssets(model::Composition *composition)
742 {
743 EnterArray();
744 while (NextArrayValue()) {
745 auto asset = parseAsset();
746 composition->mAssets[asset->mRefId] = asset;
747 }
748 // update the precomp layers with the actual layer object
749 }
750
751 static constexpr const unsigned char B64index[256] = {
752 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
753 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
754 0, 0, 0, 0, 0, 0, 0, 62, 63, 62, 62, 63, 52, 53, 54, 55, 56, 57,
755 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6,
756 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
757 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
758 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51};
759
b64decode(const char * data,const size_t len)760 std::string b64decode(const char *data, const size_t len)
761 {
762 auto p = reinterpret_cast<const unsigned char *>(data);
763 int pad = len > 0 && (len % 4 || p[len - 1] == '=');
764 const size_t L = ((len + 3) / 4 - pad) * 4;
765 std::string str(L / 4 * 3 + pad, '\0');
766
767 for (size_t i = 0, j = 0; i < L; i += 4) {
768 int n = B64index[p[i]] << 18 | B64index[p[i + 1]] << 12 |
769 B64index[p[i + 2]] << 6 | B64index[p[i + 3]];
770 str[j++] = n >> 16;
771 str[j++] = n >> 8 & 0xFF;
772 str[j++] = n & 0xFF;
773 }
774 if (pad) {
775 int n = B64index[p[L]] << 18 | B64index[p[L + 1]] << 12;
776 str[str.size() - 1] = n >> 16;
777
778 if (len > L + 2 && p[L + 2] != '=') {
779 n |= B64index[p[L + 2]] << 6;
780 str.push_back(n >> 8 & 0xFF);
781 }
782 }
783 return str;
784 }
785
convertFromBase64(const std::string & str)786 static std::string convertFromBase64(const std::string &str)
787 {
788 // usual header look like "data:image/png;base64,"
789 // so need to skip till ','.
790 size_t startIndex = str.find(",", 0);
791 startIndex += 1; // skip ","
792 size_t length = str.length() - startIndex;
793
794 const char *b64Data = str.c_str() + startIndex;
795
796 return b64decode(b64Data, length);
797 }
798
799 /*
800 * std::to_string() function is missing in VS2017
801 * so this is workaround for windows build
802 */
803 #include <sstream>
804 template <class T>
toString(const T & value)805 static std::string toString(const T &value)
806 {
807 std::ostringstream os;
808 os << value;
809 return os.str();
810 }
811
812 /*
813 * https://github.com/airbnb/lottie-web/blob/master/docs/json/layers/shape.json
814 *
815 */
parseAsset()816 model::Asset *LottieParserImpl::parseAsset()
817 {
818 auto asset = allocator().make<model::Asset>();
819 std::string filename;
820 std::string relativePath;
821 bool embededResource = false;
822 EnterObject();
823 while (const char *key = NextObjectKey()) {
824 if (0 == strcmp(key, "w")) {
825 asset->mWidth = GetInt();
826 } else if (0 == strcmp(key, "h")) {
827 asset->mHeight = GetInt();
828 } else if (0 == strcmp(key, "p")) { /* image name */
829 asset->mAssetType = model::Asset::Type::Image;
830 filename = GetStringObject();
831 } else if (0 == strcmp(key, "u")) { /* relative image path */
832 relativePath = GetStringObject();
833 } else if (0 == strcmp(key, "e")) { /* relative image path */
834 embededResource = GetInt();
835 } else if (0 == strcmp(key, "id")) { /* reference id*/
836 if (PeekType() == kStringType) {
837 asset->mRefId = GetStringObject();
838 } else {
839 asset->mRefId = toString(GetInt());
840 }
841 } else if (0 == strcmp(key, "layers")) {
842 asset->mAssetType = model::Asset::Type::Precomp;
843 EnterArray();
844 bool staticFlag = true;
845 while (NextArrayValue()) {
846 auto layer = parseLayer();
847 if (layer) {
848 staticFlag = staticFlag && layer->isStatic();
849 asset->mLayers.push_back(layer);
850 }
851 }
852 asset->setStatic(staticFlag);
853 } else {
854 #ifdef DEBUG_PARSER
855 vWarning << "Asset Attribute Skipped : " << key;
856 #endif
857 Skip(key);
858 }
859 }
860
861 if (asset->mAssetType == model::Asset::Type::Image) {
862 if (embededResource) {
863 // embeder resource should start with "data:"
864 if (filename.compare(0, 5, "data:") == 0) {
865 asset->loadImageData(convertFromBase64(filename));
866 }
867 } else {
868 asset->loadImagePath(mDirPath + relativePath + filename);
869 }
870 }
871
872 return asset;
873 }
874
parseLayers(model::Composition * comp)875 void LottieParserImpl::parseLayers(model::Composition *comp)
876 {
877 comp->mRootLayer = allocator().make<model::Layer>();
878 comp->mRootLayer->mLayerType = model::Layer::Type::Precomp;
879 comp->mRootLayer->setName("__");
880 bool staticFlag = true;
881 EnterArray();
882 while (NextArrayValue()) {
883 auto layer = parseLayer();
884 if (layer) {
885 staticFlag = staticFlag && layer->isStatic();
886 comp->mRootLayer->mChildren.push_back(layer);
887 }
888 }
889 comp->mRootLayer->setStatic(staticFlag);
890 }
891
toColor(const char * str)892 model::Color LottieParserImpl::toColor(const char *str)
893 {
894 if (!str) return {};
895
896 model::Color color;
897 auto len = strlen(str);
898
899 // some resource has empty color string
900 // return a default color for those cases.
901 if (len != 7 || str[0] != '#') return color;
902
903 char tmp[3] = {'\0', '\0', '\0'};
904 tmp[0] = str[1];
905 tmp[1] = str[2];
906 color.r = std::strtol(tmp, nullptr, 16) / 255.0f;
907
908 tmp[0] = str[3];
909 tmp[1] = str[4];
910 color.g = std::strtol(tmp, nullptr, 16) / 255.0f;
911
912 tmp[0] = str[5];
913 tmp[1] = str[6];
914 color.b = std::strtol(tmp, nullptr, 16) / 255.0f;
915
916 return color;
917 }
918
getMatteType()919 model::MatteType LottieParserImpl::getMatteType()
920 {
921 switch (GetInt()) {
922 case 1:
923 return model::MatteType::Alpha;
924 break;
925 case 2:
926 return model::MatteType::AlphaInv;
927 break;
928 case 3:
929 return model::MatteType::Luma;
930 break;
931 case 4:
932 return model::MatteType::LumaInv;
933 break;
934 default:
935 return model::MatteType::None;
936 break;
937 }
938 }
939
getLayerType()940 model::Layer::Type LottieParserImpl::getLayerType()
941 {
942 switch (GetInt()) {
943 case 0:
944 return model::Layer::Type::Precomp;
945 break;
946 case 1:
947 return model::Layer::Type::Solid;
948 break;
949 case 2:
950 return model::Layer::Type::Image;
951 break;
952 case 3:
953 return model::Layer::Type::Null;
954 break;
955 case 4:
956 return model::Layer::Type::Shape;
957 break;
958 case 5:
959 return model::Layer::Type::Text;
960 break;
961 default:
962 return model::Layer::Type::Null;
963 break;
964 }
965 }
966
967 /*
968 * https://github.com/airbnb/lottie-web/blob/master/docs/json/layers/shape.json
969 *
970 */
parseLayer()971 model::Layer *LottieParserImpl::parseLayer()
972 {
973 model::Layer *layer = allocator().make<model::Layer>();
974 curLayerRef = layer;
975 bool ddd = true;
976 EnterObject();
977 while (const char *key = NextObjectKey()) {
978 if (0 == strcmp(key, "ty")) { /* Type of layer*/
979 layer->mLayerType = getLayerType();
980 } else if (0 == strcmp(key, "nm")) { /*Layer name*/
981 layer->setName(GetString());
982 } else if (0 == strcmp(key, "ind")) { /*Layer index in AE. Used for
983 parenting and expressions.*/
984 layer->mId = GetInt();
985 } else if (0 == strcmp(key, "ddd")) { /*3d layer */
986 ddd = GetInt();
987 } else if (0 ==
988 strcmp(key,
989 "parent")) { /*Layer Parent. Uses "ind" of parent.*/
990 layer->mParentId = GetInt();
991 } else if (0 == strcmp(key, "refId")) { /*preComp Layer reference id*/
992 layer->extra()->mPreCompRefId = GetStringObject();
993 layer->mHasGradient = true;
994 mLayersToUpdate.push_back(layer);
995 } else if (0 == strcmp(key, "sr")) { // "Layer Time Stretching"
996 layer->mTimeStreatch = GetDouble();
997 } else if (0 == strcmp(key, "tm")) { // time remapping
998 parseProperty(layer->extra()->mTimeRemap);
999 } else if (0 == strcmp(key, "ip")) {
1000 layer->mInFrame = std::lround(GetDouble());
1001 } else if (0 == strcmp(key, "op")) {
1002 layer->mOutFrame = std::lround(GetDouble());
1003 } else if (0 == strcmp(key, "st")) {
1004 layer->mStartFrame = GetDouble();
1005 } else if (0 == strcmp(key, "bm")) {
1006 layer->mBlendMode = getBlendMode();
1007 } else if (0 == strcmp(key, "ks")) {
1008 EnterObject();
1009 layer->mTransform = parseTransformObject(ddd);
1010 } else if (0 == strcmp(key, "shapes")) {
1011 parseShapesAttr(layer);
1012 } else if (0 == strcmp(key, "w")) {
1013 layer->mLayerSize.setWidth(GetInt());
1014 } else if (0 == strcmp(key, "h")) {
1015 layer->mLayerSize.setHeight(GetInt());
1016 } else if (0 == strcmp(key, "sw")) {
1017 layer->mLayerSize.setWidth(GetInt());
1018 } else if (0 == strcmp(key, "sh")) {
1019 layer->mLayerSize.setHeight(GetInt());
1020 } else if (0 == strcmp(key, "sc")) {
1021 layer->extra()->mSolidColor = toColor(GetString());
1022 } else if (0 == strcmp(key, "tt")) {
1023 layer->mMatteType = getMatteType();
1024 } else if (0 == strcmp(key, "hasMask")) {
1025 layer->mHasMask = GetBool();
1026 } else if (0 == strcmp(key, "masksProperties")) {
1027 parseMaskProperty(layer);
1028 } else if (0 == strcmp(key, "ao")) {
1029 layer->mAutoOrient = GetInt();
1030 } else if (0 == strcmp(key, "hd")) {
1031 layer->setHidden(GetBool());
1032 } else {
1033 #ifdef DEBUG_PARSER
1034 vWarning << "Layer Attribute Skipped : " << key;
1035 #endif
1036 Skip(key);
1037 }
1038 }
1039
1040 if (!layer->mTransform) {
1041 // not a valid layer
1042 return nullptr;
1043 }
1044
1045 // make sure layer data is not corrupted.
1046 if (layer->hasParent() && (layer->id() == layer->parentId()))
1047 return nullptr;
1048
1049 if (layer->mExtra) layer->mExtra->mCompRef = compRef;
1050
1051 if (layer->hidden()) {
1052 // if layer is hidden, only data that is usefull is its
1053 // transform matrix(when it is a parent of some other layer)
1054 // so force it to be a Null Layer and release all resource.
1055 layer->setStatic(layer->mTransform->isStatic());
1056 layer->mLayerType = model::Layer::Type::Null;
1057 layer->mChildren = {};
1058 return layer;
1059 }
1060
1061 // update the static property of layer
1062 bool staticFlag = true;
1063 for (const auto &child : layer->mChildren) {
1064 staticFlag &= child->isStatic();
1065 }
1066
1067 if (layer->hasMask() && layer->mExtra) {
1068 for (const auto &mask : layer->mExtra->mMasks) {
1069 staticFlag &= mask->isStatic();
1070 }
1071 }
1072
1073 layer->setStatic(staticFlag && layer->mTransform->isStatic());
1074
1075 return layer;
1076 }
1077
parseMaskProperty(model::Layer * layer)1078 void LottieParserImpl::parseMaskProperty(model::Layer *layer)
1079 {
1080 EnterArray();
1081 while (NextArrayValue()) {
1082 layer->extra()->mMasks.push_back(parseMaskObject());
1083 }
1084 }
1085
parseMaskObject()1086 model::Mask *LottieParserImpl::parseMaskObject()
1087 {
1088 auto obj = allocator().make<model::Mask>();
1089
1090 EnterObject();
1091 while (const char *key = NextObjectKey()) {
1092 if (0 == strcmp(key, "inv")) {
1093 obj->mInv = GetBool();
1094 } else if (0 == strcmp(key, "mode")) {
1095 const char *str = GetString();
1096 if (!str) {
1097 obj->mMode = model::Mask::Mode::None;
1098 continue;
1099 }
1100 switch (str[0]) {
1101 case 'n':
1102 obj->mMode = model::Mask::Mode::None;
1103 break;
1104 case 'a':
1105 obj->mMode = model::Mask::Mode::Add;
1106 break;
1107 case 's':
1108 obj->mMode = model::Mask::Mode::Substarct;
1109 break;
1110 case 'i':
1111 obj->mMode = model::Mask::Mode::Intersect;
1112 break;
1113 case 'f':
1114 obj->mMode = model::Mask::Mode::Difference;
1115 break;
1116 default:
1117 obj->mMode = model::Mask::Mode::None;
1118 break;
1119 }
1120 } else if (0 == strcmp(key, "pt")) {
1121 parseShapeProperty(obj->mShape);
1122 } else if (0 == strcmp(key, "o")) {
1123 parseProperty(obj->mOpacity);
1124 } else {
1125 Skip(key);
1126 }
1127 }
1128 obj->mIsStatic = obj->mShape.isStatic() && obj->mOpacity.isStatic();
1129 return obj;
1130 }
1131
parseShapesAttr(model::Layer * layer)1132 void LottieParserImpl::parseShapesAttr(model::Layer *layer)
1133 {
1134 EnterArray();
1135 while (NextArrayValue()) {
1136 parseObject(layer);
1137 }
1138 }
1139
parseObjectTypeAttr()1140 model::Object *LottieParserImpl::parseObjectTypeAttr()
1141 {
1142 const char *type = GetString();
1143 if (0 == strcmp(type, "gr")) {
1144 return parseGroupObject();
1145 } else if (0 == strcmp(type, "rc")) {
1146 return parseRectObject();
1147 } else if (0 == strcmp(type, "rd")) {
1148 curLayerRef->mHasRoundedCorner = true;
1149 return parseRoundedCorner();
1150 } else if (0 == strcmp(type, "el")) {
1151 return parseEllipseObject();
1152 } else if (0 == strcmp(type, "tr")) {
1153 return parseTransformObject();
1154 } else if (0 == strcmp(type, "fl")) {
1155 return parseFillObject();
1156 } else if (0 == strcmp(type, "st")) {
1157 return parseStrokeObject();
1158 } else if (0 == strcmp(type, "gf")) {
1159 curLayerRef->mHasGradient = true;
1160 return parseGFillObject();
1161 } else if (0 == strcmp(type, "gs")) {
1162 curLayerRef->mHasGradient = true;
1163 return parseGStrokeObject();
1164 } else if (0 == strcmp(type, "sh")) {
1165 return parseShapeObject();
1166 } else if (0 == strcmp(type, "sr")) {
1167 return parsePolystarObject();
1168 } else if (0 == strcmp(type, "tm")) {
1169 curLayerRef->mHasPathOperator = true;
1170 return parseTrimObject();
1171 } else if (0 == strcmp(type, "rp")) {
1172 curLayerRef->mHasRepeater = true;
1173 return parseReapeaterObject();
1174 } else if (0 == strcmp(type, "mm")) {
1175 vWarning << "Merge Path is not supported yet";
1176 return nullptr;
1177 } else {
1178 #ifdef DEBUG_PARSER
1179 vDebug << "The Object Type not yet handled = " << type;
1180 #endif
1181 return nullptr;
1182 }
1183 }
1184
parseObject(model::Group * parent)1185 void LottieParserImpl::parseObject(model::Group *parent)
1186 {
1187 EnterObject();
1188 while (const char *key = NextObjectKey()) {
1189 if (0 == strcmp(key, "ty")) {
1190 auto child = parseObjectTypeAttr();
1191 if (child && !child->hidden()) {
1192 if (child->type() == model::Object::Type::RoundedCorner) {
1193 updateRoundedCorner(parent, static_cast<model::RoundedCorner *>(child));
1194 }
1195 parent->mChildren.push_back(child);
1196 }
1197 } else {
1198 Skip(key);
1199 }
1200 }
1201 }
1202
updateRoundedCorner(model::Group * group,model::RoundedCorner * rc)1203 void LottieParserImpl::updateRoundedCorner(model::Group *group, model::RoundedCorner *rc)
1204 {
1205 for(auto &e : group->mChildren)
1206 {
1207 if (e->type() == model::Object::Type::Rect) {
1208 static_cast<model::Rect *>(e)->mRoundedCorner = rc;
1209 if (!rc->isStatic()) {
1210 e->setStatic(false);
1211 group->setStatic(false);
1212 //@TODO need to propagate.
1213 }
1214 } else if ( e->type() == model::Object::Type::Group) {
1215 updateRoundedCorner(static_cast<model::Group *>(e), rc);
1216 }
1217 }
1218 }
1219
parseGroupObject()1220 model::Object *LottieParserImpl::parseGroupObject()
1221 {
1222 auto group = allocator().make<model::Group>();
1223
1224 while (const char *key = NextObjectKey()) {
1225 if (0 == strcmp(key, "nm")) {
1226 group->setName(GetString());
1227 } else if (0 == strcmp(key, "it")) {
1228 EnterArray();
1229 while (NextArrayValue()) {
1230 parseObject(group);
1231 }
1232 if (!group->mChildren.empty()
1233 && group->mChildren.back()->type()
1234 == model::Object::Type::Transform) {
1235 group->mTransform =
1236 static_cast<model::Transform *>(group->mChildren.back());
1237 group->mChildren.pop_back();
1238 }
1239 } else {
1240 Skip(key);
1241 }
1242 }
1243 bool staticFlag = true;
1244 for (const auto &child : group->mChildren) {
1245 staticFlag &= child->isStatic();
1246 }
1247
1248 if (group->mTransform) {
1249 group->setStatic(staticFlag && group->mTransform->isStatic());
1250 }
1251
1252 return group;
1253 }
1254
1255 /*
1256 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/rect.json
1257 */
parseRectObject()1258 model::Rect *LottieParserImpl::parseRectObject()
1259 {
1260 auto obj = allocator().make<model::Rect>();
1261
1262 while (const char *key = NextObjectKey()) {
1263 if (0 == strcmp(key, "nm")) {
1264 obj->setName(GetString());
1265 } else if (0 == strcmp(key, "p")) {
1266 parseProperty(obj->mPos);
1267 } else if (0 == strcmp(key, "s")) {
1268 parseProperty(obj->mSize);
1269 } else if (0 == strcmp(key, "r")) {
1270 parseProperty(obj->mRound);
1271 } else if (0 == strcmp(key, "d")) {
1272 obj->mDirection = GetInt();
1273 } else if (0 == strcmp(key, "hd")) {
1274 obj->setHidden(GetBool());
1275 } else {
1276 Skip(key);
1277 }
1278 }
1279 obj->setStatic(obj->mPos.isStatic() && obj->mSize.isStatic() &&
1280 obj->mRound.isStatic());
1281 return obj;
1282 }
1283
1284 /*
1285 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/rect.json
1286 */
parseRoundedCorner()1287 model::RoundedCorner *LottieParserImpl::parseRoundedCorner()
1288 {
1289 auto obj = allocator().make<model::RoundedCorner>();
1290
1291 while (const char *key = NextObjectKey()) {
1292 if (0 == strcmp(key, "nm")) {
1293 obj->setName(GetString());
1294 } else if (0 == strcmp(key, "r")) {
1295 parseProperty(obj->mRadius);
1296 } else if (0 == strcmp(key, "hd")) {
1297 obj->setHidden(GetBool());
1298 } else {
1299 Skip(key);
1300 }
1301 }
1302 obj->setStatic(obj->mRadius.isStatic());
1303 return obj;
1304 }
1305
1306 /*
1307 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/ellipse.json
1308 */
parseEllipseObject()1309 model::Ellipse *LottieParserImpl::parseEllipseObject()
1310 {
1311 auto obj = allocator().make<model::Ellipse>();
1312
1313 while (const char *key = NextObjectKey()) {
1314 if (0 == strcmp(key, "nm")) {
1315 obj->setName(GetString());
1316 } else if (0 == strcmp(key, "p")) {
1317 parseProperty(obj->mPos);
1318 } else if (0 == strcmp(key, "s")) {
1319 parseProperty(obj->mSize);
1320 } else if (0 == strcmp(key, "d")) {
1321 obj->mDirection = GetInt();
1322 } else if (0 == strcmp(key, "hd")) {
1323 obj->setHidden(GetBool());
1324 } else {
1325 Skip(key);
1326 }
1327 }
1328 obj->setStatic(obj->mPos.isStatic() && obj->mSize.isStatic());
1329 return obj;
1330 }
1331
1332 /*
1333 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/shape.json
1334 */
parseShapeObject()1335 model::Path *LottieParserImpl::parseShapeObject()
1336 {
1337 auto obj = allocator().make<model::Path>();
1338
1339 while (const char *key = NextObjectKey()) {
1340 if (0 == strcmp(key, "nm")) {
1341 obj->setName(GetString());
1342 } else if (0 == strcmp(key, "ks")) {
1343 parseShapeProperty(obj->mShape);
1344 } else if (0 == strcmp(key, "d")) {
1345 obj->mDirection = GetInt();
1346 } else if (0 == strcmp(key, "hd")) {
1347 obj->setHidden(GetBool());
1348 } else {
1349 #ifdef DEBUG_PARSER
1350 vDebug << "Shape property ignored :" << key;
1351 #endif
1352 Skip(key);
1353 }
1354 }
1355 obj->setStatic(obj->mShape.isStatic());
1356
1357 return obj;
1358 }
1359
1360 /*
1361 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/star.json
1362 */
parsePolystarObject()1363 model::Polystar *LottieParserImpl::parsePolystarObject()
1364 {
1365 auto obj = allocator().make<model::Polystar>();
1366
1367 while (const char *key = NextObjectKey()) {
1368 if (0 == strcmp(key, "nm")) {
1369 obj->setName(GetString());
1370 } else if (0 == strcmp(key, "p")) {
1371 parseProperty(obj->mPos);
1372 } else if (0 == strcmp(key, "pt")) {
1373 parseProperty(obj->mPointCount);
1374 } else if (0 == strcmp(key, "ir")) {
1375 parseProperty(obj->mInnerRadius);
1376 } else if (0 == strcmp(key, "is")) {
1377 parseProperty(obj->mInnerRoundness);
1378 } else if (0 == strcmp(key, "or")) {
1379 parseProperty(obj->mOuterRadius);
1380 } else if (0 == strcmp(key, "os")) {
1381 parseProperty(obj->mOuterRoundness);
1382 } else if (0 == strcmp(key, "r")) {
1383 parseProperty(obj->mRotation);
1384 } else if (0 == strcmp(key, "sy")) {
1385 int starType = GetInt();
1386 if (starType == 1) obj->mPolyType = model::Polystar::PolyType::Star;
1387 if (starType == 2)
1388 obj->mPolyType = model::Polystar::PolyType::Polygon;
1389 } else if (0 == strcmp(key, "d")) {
1390 obj->mDirection = GetInt();
1391 } else if (0 == strcmp(key, "hd")) {
1392 obj->setHidden(GetBool());
1393 } else {
1394 #ifdef DEBUG_PARSER
1395 vDebug << "Polystar property ignored :" << key;
1396 #endif
1397 Skip(key);
1398 }
1399 }
1400 obj->setStatic(
1401 obj->mPos.isStatic() && obj->mPointCount.isStatic() &&
1402 obj->mInnerRadius.isStatic() && obj->mInnerRoundness.isStatic() &&
1403 obj->mOuterRadius.isStatic() && obj->mOuterRoundness.isStatic() &&
1404 obj->mRotation.isStatic());
1405
1406 return obj;
1407 }
1408
getTrimType()1409 model::Trim::TrimType LottieParserImpl::getTrimType()
1410 {
1411 switch (GetInt()) {
1412 case 1:
1413 return model::Trim::TrimType::Simultaneously;
1414 break;
1415 case 2:
1416 return model::Trim::TrimType::Individually;
1417 break;
1418 default:
1419 Error();
1420 return model::Trim::TrimType::Simultaneously;
1421 break;
1422 }
1423 }
1424
1425 /*
1426 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/trim.json
1427 */
parseTrimObject()1428 model::Trim *LottieParserImpl::parseTrimObject()
1429 {
1430 auto obj = allocator().make<model::Trim>();
1431
1432 while (const char *key = NextObjectKey()) {
1433 if (0 == strcmp(key, "nm")) {
1434 obj->setName(GetString());
1435 } else if (0 == strcmp(key, "s")) {
1436 parseProperty(obj->mStart);
1437 } else if (0 == strcmp(key, "e")) {
1438 parseProperty(obj->mEnd);
1439 } else if (0 == strcmp(key, "o")) {
1440 parseProperty(obj->mOffset);
1441 } else if (0 == strcmp(key, "m")) {
1442 obj->mTrimType = getTrimType();
1443 } else if (0 == strcmp(key, "hd")) {
1444 obj->setHidden(GetBool());
1445 } else {
1446 #ifdef DEBUG_PARSER
1447 vDebug << "Trim property ignored :" << key;
1448 #endif
1449 Skip(key);
1450 }
1451 }
1452 obj->setStatic(obj->mStart.isStatic() && obj->mEnd.isStatic() &&
1453 obj->mOffset.isStatic());
1454 return obj;
1455 }
1456
getValue(model::Repeater::Transform & obj)1457 void LottieParserImpl::getValue(model::Repeater::Transform &obj)
1458 {
1459 EnterObject();
1460
1461 while (const char *key = NextObjectKey()) {
1462 if (0 == strcmp(key, "a")) {
1463 parseProperty(obj.mAnchor);
1464 } else if (0 == strcmp(key, "p")) {
1465 parseProperty(obj.mPosition);
1466 } else if (0 == strcmp(key, "r")) {
1467 parseProperty(obj.mRotation);
1468 } else if (0 == strcmp(key, "s")) {
1469 parseProperty(obj.mScale);
1470 } else if (0 == strcmp(key, "so")) {
1471 parseProperty(obj.mStartOpacity);
1472 } else if (0 == strcmp(key, "eo")) {
1473 parseProperty(obj.mEndOpacity);
1474 } else {
1475 Skip(key);
1476 }
1477 }
1478 }
1479
parseReapeaterObject()1480 model::Repeater *LottieParserImpl::parseReapeaterObject()
1481 {
1482 auto obj = allocator().make<model::Repeater>();
1483
1484 obj->setContent(allocator().make<model::Group>());
1485
1486 while (const char *key = NextObjectKey()) {
1487 if (0 == strcmp(key, "nm")) {
1488 obj->setName(GetString());
1489 } else if (0 == strcmp(key, "c")) {
1490 parseProperty(obj->mCopies);
1491 float maxCopy = 0.0;
1492 if (!obj->mCopies.isStatic()) {
1493 for (auto &keyFrame : obj->mCopies.animation().frames_) {
1494 if (maxCopy < keyFrame.value_.start_)
1495 maxCopy = keyFrame.value_.start_;
1496 if (maxCopy < keyFrame.value_.end_)
1497 maxCopy = keyFrame.value_.end_;
1498 }
1499 } else {
1500 maxCopy = obj->mCopies.value();
1501 }
1502 obj->mMaxCopies = maxCopy;
1503 } else if (0 == strcmp(key, "o")) {
1504 parseProperty(obj->mOffset);
1505 } else if (0 == strcmp(key, "tr")) {
1506 getValue(obj->mTransform);
1507 } else if (0 == strcmp(key, "hd")) {
1508 obj->setHidden(GetBool());
1509 } else {
1510 #ifdef DEBUG_PARSER
1511 vDebug << "Repeater property ignored :" << key;
1512 #endif
1513 Skip(key);
1514 }
1515 }
1516 obj->setStatic(obj->mCopies.isStatic() && obj->mOffset.isStatic() &&
1517 obj->mTransform.isStatic());
1518
1519 return obj;
1520 }
1521
1522 /*
1523 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/transform.json
1524 */
parseTransformObject(bool ddd)1525 model::Transform *LottieParserImpl::parseTransformObject(bool ddd)
1526 {
1527 auto objT = allocator().make<model::Transform>();
1528
1529 auto obj = allocator().make<model::Transform::Data>();
1530 if (ddd) {
1531 obj->createExtraData();
1532 obj->mExtra->m3DData = true;
1533 }
1534
1535 while (const char *key = NextObjectKey()) {
1536 if (0 == strcmp(key, "nm")) {
1537 objT->setName(GetString());
1538 } else if (0 == strcmp(key, "a")) {
1539 parseProperty(obj->mAnchor);
1540 } else if (0 == strcmp(key, "p")) {
1541 EnterObject();
1542 bool separate = false;
1543 while (const char *key = NextObjectKey()) {
1544 if (0 == strcmp(key, "k")) {
1545 parsePropertyHelper(obj->mPosition);
1546 } else if (0 == strcmp(key, "s")) {
1547 obj->createExtraData();
1548 obj->mExtra->mSeparate = GetBool();
1549 separate = true;
1550 } else if (separate && (0 == strcmp(key, "x"))) {
1551 parseProperty(obj->mExtra->mSeparateX);
1552 } else if (separate && (0 == strcmp(key, "y"))) {
1553 parseProperty(obj->mExtra->mSeparateY);
1554 } else {
1555 Skip(key);
1556 }
1557 }
1558 } else if (0 == strcmp(key, "r")) {
1559 parseProperty(obj->mRotation);
1560 } else if (0 == strcmp(key, "s")) {
1561 parseProperty(obj->mScale);
1562 } else if (0 == strcmp(key, "o")) {
1563 parseProperty(obj->mOpacity);
1564 } else if (0 == strcmp(key, "hd")) {
1565 objT->setHidden(GetBool());
1566 } else if (0 == strcmp(key, "rx")) {
1567 if (!obj->mExtra) return nullptr;
1568 parseProperty(obj->mExtra->m3DRx);
1569 } else if (0 == strcmp(key, "ry")) {
1570 if (!obj->mExtra) return nullptr;
1571 parseProperty(obj->mExtra->m3DRy);
1572 } else if (0 == strcmp(key, "rz")) {
1573 if (!obj->mExtra) return nullptr;
1574 parseProperty(obj->mExtra->m3DRz);
1575 } else {
1576 Skip(key);
1577 }
1578 }
1579 bool isStatic = obj->mAnchor.isStatic() && obj->mPosition.isStatic() &&
1580 obj->mRotation.isStatic() && obj->mScale.isStatic() &&
1581 obj->mOpacity.isStatic();
1582 if (obj->mExtra) {
1583 isStatic = isStatic && obj->mExtra->m3DRx.isStatic() &&
1584 obj->mExtra->m3DRy.isStatic() &&
1585 obj->mExtra->m3DRz.isStatic() &&
1586 obj->mExtra->mSeparateX.isStatic() &&
1587 obj->mExtra->mSeparateY.isStatic();
1588 }
1589
1590 objT->set(obj, isStatic);
1591
1592 return objT;
1593 }
1594
1595 /*
1596 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/fill.json
1597 */
parseFillObject()1598 model::Fill *LottieParserImpl::parseFillObject()
1599 {
1600 auto obj = allocator().make<model::Fill>();
1601
1602 while (const char *key = NextObjectKey()) {
1603 if (0 == strcmp(key, "nm")) {
1604 obj->setName(GetString());
1605 } else if (0 == strcmp(key, "c")) {
1606 parseProperty(obj->mColor);
1607 } else if (0 == strcmp(key, "o")) {
1608 parseProperty(obj->mOpacity);
1609 } else if (0 == strcmp(key, "fillEnabled")) {
1610 obj->mEnabled = GetBool();
1611 } else if (0 == strcmp(key, "r")) {
1612 obj->mFillRule = getFillRule();
1613 } else if (0 == strcmp(key, "hd")) {
1614 obj->setHidden(GetBool());
1615 } else {
1616 #ifdef DEBUG_PARSER
1617 vWarning << "Fill property skipped = " << key;
1618 #endif
1619 Skip(key);
1620 }
1621 }
1622 obj->setStatic(obj->mColor.isStatic() && obj->mOpacity.isStatic());
1623
1624 return obj;
1625 }
1626
1627 /*
1628 * https://github.com/airbnb/lottie-web/blob/master/docs/json/helpers/lineCap.json
1629 */
getLineCap()1630 CapStyle LottieParserImpl::getLineCap()
1631 {
1632 switch (GetInt()) {
1633 case 1:
1634 return CapStyle::Flat;
1635 break;
1636 case 2:
1637 return CapStyle::Round;
1638 break;
1639 default:
1640 return CapStyle::Square;
1641 break;
1642 }
1643 }
1644
getFillRule()1645 FillRule LottieParserImpl::getFillRule()
1646 {
1647 switch (GetInt()) {
1648 case 1:
1649 return FillRule::Winding;
1650 break;
1651 case 2:
1652 return FillRule::EvenOdd;
1653 break;
1654 default:
1655 return FillRule::Winding;
1656 break;
1657 }
1658 }
1659
1660 /*
1661 * https://github.com/airbnb/lottie-web/blob/master/docs/json/helpers/lineJoin.json
1662 */
getLineJoin()1663 JoinStyle LottieParserImpl::getLineJoin()
1664 {
1665 switch (GetInt()) {
1666 case 1:
1667 return JoinStyle::Miter;
1668 break;
1669 case 2:
1670 return JoinStyle::Round;
1671 break;
1672 default:
1673 return JoinStyle::Bevel;
1674 break;
1675 }
1676 }
1677
1678 /*
1679 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/stroke.json
1680 */
parseStrokeObject()1681 model::Stroke *LottieParserImpl::parseStrokeObject()
1682 {
1683 auto obj = allocator().make<model::Stroke>();
1684
1685 while (const char *key = NextObjectKey()) {
1686 if (0 == strcmp(key, "nm")) {
1687 obj->setName(GetString());
1688 } else if (0 == strcmp(key, "c")) {
1689 parseProperty(obj->mColor);
1690 } else if (0 == strcmp(key, "o")) {
1691 parseProperty(obj->mOpacity);
1692 } else if (0 == strcmp(key, "w")) {
1693 parseProperty(obj->mWidth);
1694 } else if (0 == strcmp(key, "fillEnabled")) {
1695 obj->mEnabled = GetBool();
1696 } else if (0 == strcmp(key, "lc")) {
1697 obj->mCapStyle = getLineCap();
1698 } else if (0 == strcmp(key, "lj")) {
1699 obj->mJoinStyle = getLineJoin();
1700 } else if (0 == strcmp(key, "ml")) {
1701 obj->mMiterLimit = GetDouble();
1702 } else if (0 == strcmp(key, "d")) {
1703 parseDashProperty(obj->mDash);
1704 } else if (0 == strcmp(key, "hd")) {
1705 obj->setHidden(GetBool());
1706 } else {
1707 #ifdef DEBUG_PARSER
1708 vWarning << "Stroke property skipped = " << key;
1709 #endif
1710 Skip(key);
1711 }
1712 }
1713 obj->setStatic(obj->mColor.isStatic() && obj->mOpacity.isStatic() &&
1714 obj->mWidth.isStatic() && obj->mDash.isStatic());
1715 return obj;
1716 }
1717
parseGradientProperty(model::Gradient * obj,const char * key)1718 void LottieParserImpl::parseGradientProperty(model::Gradient *obj,
1719 const char * key)
1720 {
1721 if (0 == strcmp(key, "t")) {
1722 obj->mGradientType = GetInt();
1723 } else if (0 == strcmp(key, "o")) {
1724 parseProperty(obj->mOpacity);
1725 } else if (0 == strcmp(key, "s")) {
1726 parseProperty(obj->mStartPoint);
1727 } else if (0 == strcmp(key, "e")) {
1728 parseProperty(obj->mEndPoint);
1729 } else if (0 == strcmp(key, "h")) {
1730 parseProperty(obj->mHighlightLength);
1731 } else if (0 == strcmp(key, "a")) {
1732 parseProperty(obj->mHighlightAngle);
1733 } else if (0 == strcmp(key, "g")) {
1734 EnterObject();
1735 while (const char *key = NextObjectKey()) {
1736 if (0 == strcmp(key, "k")) {
1737 parseProperty(obj->mGradient);
1738 } else if (0 == strcmp(key, "p")) {
1739 obj->mColorPoints = GetInt();
1740 } else {
1741 Skip(nullptr);
1742 }
1743 }
1744 } else if (0 == strcmp(key, "hd")) {
1745 obj->setHidden(GetBool());
1746 } else {
1747 #ifdef DEBUG_PARSER
1748 vWarning << "Gradient property skipped = " << key;
1749 #endif
1750 Skip(key);
1751 }
1752 obj->setStatic(
1753 obj->mOpacity.isStatic() && obj->mStartPoint.isStatic() &&
1754 obj->mEndPoint.isStatic() && obj->mHighlightAngle.isStatic() &&
1755 obj->mHighlightLength.isStatic() && obj->mGradient.isStatic());
1756 }
1757
1758 /*
1759 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/gfill.json
1760 */
parseGFillObject()1761 model::GradientFill *LottieParserImpl::parseGFillObject()
1762 {
1763 auto obj = allocator().make<model::GradientFill>();
1764
1765 while (const char *key = NextObjectKey()) {
1766 if (0 == strcmp(key, "nm")) {
1767 obj->setName(GetString());
1768 } else if (0 == strcmp(key, "r")) {
1769 obj->mFillRule = getFillRule();
1770 } else {
1771 parseGradientProperty(obj, key);
1772 }
1773 }
1774 return obj;
1775 }
1776
parseDashProperty(model::Dash & dash)1777 void LottieParserImpl::parseDashProperty(model::Dash &dash)
1778 {
1779 EnterArray();
1780 while (NextArrayValue()) {
1781 EnterObject();
1782 while (const char *key = NextObjectKey()) {
1783 if (0 == strcmp(key, "v")) {
1784 dash.mData.emplace_back();
1785 parseProperty(dash.mData.back());
1786 } else {
1787 Skip(key);
1788 }
1789 }
1790 }
1791 }
1792
1793 /*
1794 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/gstroke.json
1795 */
parseGStrokeObject()1796 model::GradientStroke *LottieParserImpl::parseGStrokeObject()
1797 {
1798 auto obj = allocator().make<model::GradientStroke>();
1799
1800 while (const char *key = NextObjectKey()) {
1801 if (0 == strcmp(key, "nm")) {
1802 obj->setName(GetString());
1803 } else if (0 == strcmp(key, "w")) {
1804 parseProperty(obj->mWidth);
1805 } else if (0 == strcmp(key, "lc")) {
1806 obj->mCapStyle = getLineCap();
1807 } else if (0 == strcmp(key, "lj")) {
1808 obj->mJoinStyle = getLineJoin();
1809 } else if (0 == strcmp(key, "ml")) {
1810 obj->mMiterLimit = GetDouble();
1811 } else if (0 == strcmp(key, "d")) {
1812 parseDashProperty(obj->mDash);
1813 } else {
1814 parseGradientProperty(obj, key);
1815 }
1816 }
1817
1818 obj->setStatic(obj->isStatic() && obj->mWidth.isStatic() &&
1819 obj->mDash.isStatic());
1820 return obj;
1821 }
1822
getValue(std::vector<VPointF> & v)1823 void LottieParserImpl::getValue(std::vector<VPointF> &v)
1824 {
1825 EnterArray();
1826 while (NextArrayValue()) {
1827 EnterArray();
1828 VPointF pt;
1829 getValue(pt);
1830 v.push_back(pt);
1831 }
1832 }
1833
getValue(VPointF & pt)1834 void LottieParserImpl::getValue(VPointF &pt)
1835 {
1836 float val[4] = {0.f};
1837 int i = 0;
1838
1839 if (PeekType() == kArrayType) EnterArray();
1840
1841 while (NextArrayValue()) {
1842 const auto value = GetDouble();
1843 if (i < 4) {
1844 val[i++] = value;
1845 }
1846 }
1847 pt.setX(val[0]);
1848 pt.setY(val[1]);
1849 }
1850
getValue(float & val)1851 void LottieParserImpl::getValue(float &val)
1852 {
1853 if (PeekType() == kArrayType) {
1854 EnterArray();
1855 if (NextArrayValue()) val = GetDouble();
1856 // discard rest
1857 while (NextArrayValue()) {
1858 GetDouble();
1859 }
1860 } else if (PeekType() == kNumberType) {
1861 val = GetDouble();
1862 } else {
1863 Error();
1864 }
1865 }
1866
getValue(model::Color & color)1867 void LottieParserImpl::getValue(model::Color &color)
1868 {
1869 float val[4] = {0.f};
1870 int i = 0;
1871 if (PeekType() == kArrayType) EnterArray();
1872
1873 while (NextArrayValue()) {
1874 const auto value = GetDouble();
1875 if (i < 4) {
1876 val[i++] = value;
1877 }
1878 }
1879
1880 if (mColorFilter) mColorFilter(val[0], val[1], val[2]);
1881
1882 color.r = val[0];
1883 color.g = val[1];
1884 color.b = val[2];
1885 }
1886
getValue(model::Gradient::Data & grad)1887 void LottieParserImpl::getValue(model::Gradient::Data &grad)
1888 {
1889 if (PeekType() == kArrayType) EnterArray();
1890
1891 while (NextArrayValue()) {
1892 grad.mGradient.push_back(GetDouble());
1893 }
1894 }
1895
getValue(int & val)1896 void LottieParserImpl::getValue(int &val)
1897 {
1898 if (PeekType() == kArrayType) {
1899 EnterArray();
1900 while (NextArrayValue()) {
1901 val = GetInt();
1902 }
1903 } else if (PeekType() == kNumberType) {
1904 val = GetInt();
1905 } else {
1906 Error();
1907 }
1908 }
1909
parsePathInfo()1910 void LottieParserImpl::parsePathInfo()
1911 {
1912 mPathInfo.reset();
1913
1914 /*
1915 * The shape object could be wrapped by a array
1916 * if its part of the keyframe object
1917 */
1918 bool arrayWrapper = (PeekType() == kArrayType);
1919 if (arrayWrapper) EnterArray();
1920
1921 EnterObject();
1922 while (const char *key = NextObjectKey()) {
1923 if (0 == strcmp(key, "i")) {
1924 getValue(mPathInfo.mInPoint);
1925 } else if (0 == strcmp(key, "o")) {
1926 getValue(mPathInfo.mOutPoint);
1927 } else if (0 == strcmp(key, "v")) {
1928 getValue(mPathInfo.mVertices);
1929 } else if (0 == strcmp(key, "c")) {
1930 mPathInfo.mClosed = GetBool();
1931 } else {
1932 Error();
1933 Skip(nullptr);
1934 }
1935 }
1936 // exit properly from the array
1937 if (arrayWrapper) NextArrayValue();
1938
1939 mPathInfo.convert();
1940 }
1941
getValue(model::PathData & obj)1942 void LottieParserImpl::getValue(model::PathData &obj)
1943 {
1944 parsePathInfo();
1945 obj.mPoints = mPathInfo.mResult;
1946 obj.mClosed = mPathInfo.mClosed;
1947 }
1948
parseInperpolatorPoint()1949 VPointF LottieParserImpl::parseInperpolatorPoint()
1950 {
1951 VPointF cp;
1952 EnterObject();
1953 while (const char *key = NextObjectKey()) {
1954 if (0 == strcmp(key, "x")) {
1955 getValue(cp.rx());
1956 }
1957 if (0 == strcmp(key, "y")) {
1958 getValue(cp.ry());
1959 }
1960 }
1961 return cp;
1962 }
1963
1964 template <typename T>
parseKeyFrameValue(const char * key,model::Value<T,model::Position> & value)1965 bool LottieParserImpl::parseKeyFrameValue(
1966 const char *key, model::Value<T, model::Position> &value)
1967 {
1968 if (0 == strcmp(key, "ti")) {
1969 value.hasTangent_ = true;
1970 getValue(value.inTangent_);
1971 } else if (0 == strcmp(key, "to")) {
1972 value.hasTangent_ = true;
1973 getValue(value.outTangent_);
1974 } else {
1975 return false;
1976 }
1977 return true;
1978 }
1979
interpolator(VPointF inTangent,VPointF outTangent,std::string key)1980 VInterpolator *LottieParserImpl::interpolator(VPointF inTangent,
1981 VPointF outTangent,
1982 std::string key)
1983 {
1984 if (key.empty()) {
1985 std::array<char, 20> temp;
1986 snprintf(temp.data(), temp.size(), "%.2f_%.2f_%.2f_%.2f", inTangent.x(),
1987 inTangent.y(), outTangent.x(), outTangent.y());
1988 key = temp.data();
1989 }
1990
1991 auto search = mInterpolatorCache.find(key);
1992
1993 if (search != mInterpolatorCache.end()) {
1994 return search->second;
1995 }
1996
1997 auto obj = allocator().make<VInterpolator>(outTangent, inTangent);
1998 mInterpolatorCache[std::move(key)] = obj;
1999 return obj;
2000 }
2001
2002 /*
2003 * https://github.com/airbnb/lottie-web/blob/master/docs/json/properties/multiDimensionalKeyframed.json
2004 */
2005 template <typename T, typename Tag>
parseKeyFrame(model::KeyFrames<T,Tag> & obj)2006 void LottieParserImpl::parseKeyFrame(model::KeyFrames<T, Tag> &obj)
2007 {
2008 struct ParsedField {
2009 std::string interpolatorKey;
2010 bool interpolator{false};
2011 bool value{false};
2012 bool hold{false};
2013 bool noEndValue{true};
2014 };
2015
2016 EnterObject();
2017 ParsedField parsed;
2018 typename model::KeyFrames<T, Tag>::Frame keyframe;
2019 VPointF inTangent;
2020 VPointF outTangent;
2021
2022 while (const char *key = NextObjectKey()) {
2023 if (0 == strcmp(key, "i")) {
2024 parsed.interpolator = true;
2025 inTangent = parseInperpolatorPoint();
2026 } else if (0 == strcmp(key, "o")) {
2027 outTangent = parseInperpolatorPoint();
2028 } else if (0 == strcmp(key, "t")) {
2029 keyframe.start_ = GetDouble();
2030 } else if (0 == strcmp(key, "s")) {
2031 parsed.value = true;
2032 getValue(keyframe.value_.start_);
2033 continue;
2034 } else if (0 == strcmp(key, "e")) {
2035 parsed.noEndValue = false;
2036 getValue(keyframe.value_.end_);
2037 continue;
2038 } else if (0 == strcmp(key, "n")) {
2039 if (PeekType() == kStringType) {
2040 parsed.interpolatorKey = GetStringObject();
2041 } else {
2042 EnterArray();
2043 while (NextArrayValue()) {
2044 if (parsed.interpolatorKey.empty()) {
2045 parsed.interpolatorKey = GetStringObject();
2046 } else {
2047 // skip rest of the string
2048 Skip(nullptr);
2049 }
2050 }
2051 }
2052 continue;
2053 } else if (parseKeyFrameValue(key, keyframe.value_)) {
2054 continue;
2055 } else if (0 == strcmp(key, "h")) {
2056 parsed.hold = GetInt();
2057 continue;
2058 } else {
2059 #ifdef DEBUG_PARSER
2060 vDebug << "key frame property skipped = " << key;
2061 #endif
2062 Skip(key);
2063 }
2064 }
2065
2066 auto &list = obj.frames_;
2067 if (!list.empty()) {
2068 // update the endFrame value of current keyframe
2069 list.back().end_ = keyframe.start_;
2070 // if no end value provided, copy start value to previous frame
2071 if (parsed.value && parsed.noEndValue) {
2072 list.back().value_.end_ = keyframe.value_.start_;
2073 }
2074 }
2075
2076 if (parsed.hold) {
2077 keyframe.value_.end_ = keyframe.value_.start_;
2078 keyframe.end_ = keyframe.start_;
2079 list.push_back(std::move(keyframe));
2080 } else if (parsed.interpolator) {
2081 keyframe.interpolator_ = interpolator(
2082 inTangent, outTangent, std::move(parsed.interpolatorKey));
2083 list.push_back(std::move(keyframe));
2084 } else {
2085 // its the last frame discard.
2086 }
2087 }
2088
2089 /*
2090 * https://github.com/airbnb/lottie-web/blob/master/docs/json/properties/shapeKeyframed.json
2091 */
2092
2093 /*
2094 * https://github.com/airbnb/lottie-web/blob/master/docs/json/properties/shape.json
2095 */
parseShapeProperty(model::Property<model::PathData> & obj)2096 void LottieParserImpl::parseShapeProperty(model::Property<model::PathData> &obj)
2097 {
2098 EnterObject();
2099 while (const char *key = NextObjectKey()) {
2100 if (0 == strcmp(key, "k")) {
2101 if (PeekType() == kArrayType) {
2102 EnterArray();
2103 while (NextArrayValue()) {
2104 parseKeyFrame(obj.animation());
2105 }
2106 } else {
2107 if (!obj.isStatic()) {
2108 st_ = kError;
2109 return;
2110 }
2111 getValue(obj.value());
2112 }
2113 } else {
2114 #ifdef DEBUG_PARSER
2115 vDebug << "shape property ignored = " << key;
2116 #endif
2117 Skip(nullptr);
2118 }
2119 }
2120 obj.cache();
2121 }
2122
2123 template <typename T, typename Tag>
parsePropertyHelper(model::Property<T,Tag> & obj)2124 void LottieParserImpl::parsePropertyHelper(model::Property<T, Tag> &obj)
2125 {
2126 if (PeekType() == kNumberType) {
2127 if (!obj.isStatic()) {
2128 st_ = kError;
2129 return;
2130 }
2131 /*single value property with no animation*/
2132 getValue(obj.value());
2133 } else {
2134 EnterArray();
2135 while (NextArrayValue()) {
2136 /* property with keyframe info*/
2137 if (PeekType() == kObjectType) {
2138 parseKeyFrame(obj.animation());
2139 } else {
2140 /* Read before modifying.
2141 * as there is no way of knowing if the
2142 * value of the array is either array of numbers
2143 * or array of object without entering the array
2144 * thats why this hack is there
2145 */
2146 if (!obj.isStatic()) {
2147 st_ = kError;
2148 return;
2149 }
2150 /*multi value property with no animation*/
2151 getValue(obj.value());
2152 /*break here as we already reached end of array*/
2153 break;
2154 }
2155 }
2156 obj.cache();
2157 }
2158 }
2159
2160 /*
2161 * https://github.com/airbnb/lottie-web/tree/master/docs/json/properties
2162 */
2163 template <typename T>
parseProperty(model::Property<T> & obj)2164 void LottieParserImpl::parseProperty(model::Property<T> &obj)
2165 {
2166 EnterObject();
2167 while (const char *key = NextObjectKey()) {
2168 if (0 == strcmp(key, "k")) {
2169 parsePropertyHelper(obj);
2170 } else {
2171 Skip(key);
2172 }
2173 }
2174 }
2175
2176 #ifdef LOTTIE_DUMP_TREE_SUPPORT
2177
2178 class ObjectInspector {
2179 public:
visit(model::Composition * obj,std::string level)2180 void visit(model::Composition *obj, std::string level)
2181 {
2182 vDebug << " { " << level << "Composition:: a: " << !obj->isStatic()
2183 << ", v: " << obj->mVersion << ", stFm: " << obj->startFrame()
2184 << ", endFm: " << obj->endFrame()
2185 << ", W: " << obj->size().width()
2186 << ", H: " << obj->size().height() << "\n";
2187 level.append("\t");
2188 visit(obj->mRootLayer, level);
2189 level.erase(level.end() - 1, level.end());
2190 vDebug << " } " << level << "Composition End\n";
2191 }
visit(model::Layer * obj,std::string level)2192 void visit(model::Layer *obj, std::string level)
2193 {
2194 vDebug << level << "{ " << layerType(obj->mLayerType)
2195 << ", name: " << obj->name() << ", id:" << obj->mId
2196 << " Pid:" << obj->mParentId << ", a:" << !obj->isStatic()
2197 << ", " << matteType(obj->mMatteType)
2198 << ", mask:" << obj->hasMask() << ", inFm:" << obj->mInFrame
2199 << ", outFm:" << obj->mOutFrame << ", stFm:" << obj->mStartFrame
2200 << ", ts:" << obj->mTimeStreatch << ", ao:" << obj->autoOrient()
2201 << ", W:" << obj->layerSize().width()
2202 << ", H:" << obj->layerSize().height();
2203
2204 if (obj->mLayerType == model::Layer::Type::Image)
2205 vDebug << level << "\t{ "
2206 << "ImageInfo:"
2207 << " W :" << obj->extra()->mAsset->mWidth
2208 << ", H :" << obj->extra()->mAsset->mHeight << " }"
2209 << "\n";
2210 else {
2211 vDebug << level;
2212 }
2213 visitChildren(static_cast<model::Group *>(obj), level);
2214 vDebug << level << "} " << layerType(obj->mLayerType).c_str()
2215 << ", id: " << obj->mId << "\n";
2216 }
visitChildren(model::Group * obj,std::string level)2217 void visitChildren(model::Group *obj, std::string level)
2218 {
2219 level.append("\t");
2220 for (const auto &child : obj->mChildren) visit(child, level);
2221 if (obj->mTransform) visit(obj->mTransform, level);
2222 }
2223
visit(model::Object * obj,std::string level)2224 void visit(model::Object *obj, std::string level)
2225 {
2226 switch (obj->type()) {
2227 case model::Object::Type::Repeater: {
2228 auto r = static_cast<model::Repeater *>(obj);
2229 vDebug << level << "{ Repeater: name: " << obj->name()
2230 << " , a:" << !obj->isStatic()
2231 << ", copies:" << r->maxCopies()
2232 << ", offset:" << r->offset(0);
2233 visitChildren(r->mContent, level);
2234 vDebug << level << "} Repeater";
2235 break;
2236 }
2237 case model::Object::Type::Group: {
2238 vDebug << level << "{ Group: name: " << obj->name()
2239 << " , a:" << !obj->isStatic();
2240 visitChildren(static_cast<model::Group *>(obj), level);
2241 vDebug << level << "} Group";
2242 break;
2243 }
2244 case model::Object::Type::Layer: {
2245 visit(static_cast<model::Layer *>(obj), level);
2246 break;
2247 }
2248 case model::Object::Type::Trim: {
2249 vDebug << level << "{ Trim: name: " << obj->name()
2250 << " , a:" << !obj->isStatic() << " }";
2251 break;
2252 }
2253 case model::Object::Type::Rect: {
2254 vDebug << level << "{ Rect: name: " << obj->name()
2255 << " , a:" << !obj->isStatic() << " }";
2256 break;
2257 }
2258 case model::Object::Type::RoundedCorner: {
2259 vDebug << level << "{ RoundedCorner: name: " << obj->name()
2260 << " , a:" << !obj->isStatic() << " }";
2261 break;
2262 }
2263 case model::Object::Type::Ellipse: {
2264 vDebug << level << "{ Ellipse: name: " << obj->name()
2265 << " , a:" << !obj->isStatic() << " }";
2266 break;
2267 }
2268 case model::Object::Type::Path: {
2269 vDebug << level << "{ Shape: name: " << obj->name()
2270 << " , a:" << !obj->isStatic() << " }";
2271 break;
2272 }
2273 case model::Object::Type::Polystar: {
2274 vDebug << level << "{ Polystar: name: " << obj->name()
2275 << " , a:" << !obj->isStatic() << " }";
2276 break;
2277 }
2278 case model::Object::Type::Transform: {
2279 vDebug << level << "{ Transform: name: " << obj->name()
2280 << " , a: " << !obj->isStatic() << " }";
2281 break;
2282 }
2283 case model::Object::Type::Stroke: {
2284 vDebug << level << "{ Stroke: name: " << obj->name()
2285 << " , a:" << !obj->isStatic() << " }";
2286 break;
2287 }
2288 case model::Object::Type::GStroke: {
2289 vDebug << level << "{ GStroke: name: " << obj->name()
2290 << " , a:" << !obj->isStatic() << " }";
2291 break;
2292 }
2293 case model::Object::Type::Fill: {
2294 vDebug << level << "{ Fill: name: " << obj->name()
2295 << " , a:" << !obj->isStatic() << " }";
2296 break;
2297 }
2298 case model::Object::Type::GFill: {
2299 auto f = static_cast<model::GradientFill *>(obj);
2300 vDebug << level << "{ GFill: name: " << obj->name()
2301 << " , a:" << !f->isStatic() << ", ty:" << f->mGradientType
2302 << ", s:" << f->mStartPoint.value(0)
2303 << ", e:" << f->mEndPoint.value(0) << " }";
2304 break;
2305 }
2306 default:
2307 break;
2308 }
2309 }
2310
matteType(model::MatteType type)2311 std::string matteType(model::MatteType type)
2312 {
2313 switch (type) {
2314 case model::MatteType::None:
2315 return "Matte::None";
2316 break;
2317 case model::MatteType::Alpha:
2318 return "Matte::Alpha";
2319 break;
2320 case model::MatteType::AlphaInv:
2321 return "Matte::AlphaInv";
2322 break;
2323 case model::MatteType::Luma:
2324 return "Matte::Luma";
2325 break;
2326 case model::MatteType::LumaInv:
2327 return "Matte::LumaInv";
2328 break;
2329 default:
2330 return "Matte::Unknown";
2331 break;
2332 }
2333 }
layerType(model::Layer::Type type)2334 std::string layerType(model::Layer::Type type)
2335 {
2336 switch (type) {
2337 case model::Layer::Type::Precomp:
2338 return "Layer::Precomp";
2339 break;
2340 case model::Layer::Type::Null:
2341 return "Layer::Null";
2342 break;
2343 case model::Layer::Type::Shape:
2344 return "Layer::Shape";
2345 break;
2346 case model::Layer::Type::Solid:
2347 return "Layer::Solid";
2348 break;
2349 case model::Layer::Type::Image:
2350 return "Layer::Image";
2351 break;
2352 case model::Layer::Type::Text:
2353 return "Layer::Text";
2354 break;
2355 default:
2356 return "Layer::Unknown";
2357 break;
2358 }
2359 }
2360 };
2361
2362 #endif
2363
parse(char * str,std::string dir_path,model::ColorFilter filter)2364 std::shared_ptr<model::Composition> model::parse(char * str,
2365 std::string dir_path,
2366 model::ColorFilter filter)
2367 {
2368 LottieParserImpl obj(str, std::move(dir_path), std::move(filter));
2369
2370 if (obj.VerifyType()) {
2371 obj.parseComposition();
2372 auto composition = obj.composition();
2373 if (composition) {
2374 composition->processRepeaterObjects();
2375 composition->updateStats();
2376
2377 #ifdef LOTTIE_DUMP_TREE_SUPPORT
2378 ObjectInspector inspector;
2379 inspector.visit(composition.get(), "");
2380 #endif
2381
2382 return composition;
2383 }
2384 }
2385
2386 vWarning << "Input data is not Lottie format!";
2387 return {};
2388 }
2389
2390 RAPIDJSON_DIAG_POP
2391