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