2 #include "lottieparser.h"
7 #define DEBUG_PRINT_TREE
9 // This parser implements JSON token-by-token parsing with an API that is
10 // more direct; we don't have to create handler object and
11 // callbacks. Instead, we retrieve values from the JSON stream by calling
12 // GetInt(), GetDouble(), GetString() and GetBool(), traverse into structures
13 // by calling EnterObject() and EnterArray(), and skip over unwanted data by
14 // calling SkipValue(). As we know the lottie file structure this way will be
15 // the efficient way of parsing the file.
17 // If you aren't sure of what's next in the JSON data, you can use PeekType()
18 // and PeekValue() to look ahead to the next object before reading it.
20 // If you call the wrong retrieval method--e.g. GetInt when the next JSON token
21 // is not an int, EnterObject or EnterArray when there isn't actually an object
22 // or array to read--the stream parsing will end immediately and no more data
25 // After calling EnterObject, you retrieve keys via NextObjectKey() and values
26 // via the normal getters. When NextObjectKey() returns null, you have exited
27 // the object, or you can call SkipObject() to skip to the end of the object
28 // immediately. If you fetch the entire object (i.e. NextObjectKey() returned
29 // null), you should not call SkipObject().
31 // After calling EnterArray(), you must alternate between calling
32 // NextArrayValue() to see if the array has more data, and then retrieving
33 // values via the normal getters. You can call SkipArray() to skip to the end of
34 // the array immediately. If you fetch the entire array (i.e. NextArrayValue()
35 // returned null), you should not call SkipArray().
37 // This parser uses in-situ strings, so the JSON buffer will be altered during
41 #include "lottiemodel.h"
42 #include "rapidjson/document.h"
43 #include "velapsedtimer.h"
47 RAPIDJSON_DIAG_OFF(effc++)
50 using namespace rapidjson;
52 class LookaheadParserHandler {
84 bool Uint64(uint64_t u)
96 bool RawNumber(const char *, SizeType, bool) { return false; }
97 bool String(const char *str, SizeType length, bool)
100 v_.SetString(str, length);
105 st_ = kEnteringObject;
108 bool Key(const char *str, SizeType length, bool)
111 v_.SetString(str, length);
114 bool EndObject(SizeType)
116 st_ = kExitingObject;
121 st_ = kEnteringArray;
124 bool EndArray(SizeType)
131 LookaheadParserHandler(char *str);
135 enum LookaheadParsingState {
150 LookaheadParsingState st_;
152 InsituStringStream ss_;
154 static const int parseFlags = kParseDefaultFlags | kParseInsituFlag;
157 class LottieParserImpl : protected LookaheadParserHandler {
159 LottieParserImpl(char *str) : LookaheadParserHandler(str) {}
164 const char *NextObjectKey();
165 bool NextArrayValue();
168 const char *GetString();
176 int PeekType(); // returns a rapidjson::Type, or -1 for no value (at end of
179 bool IsValid() { return st_ != kError; }
181 void Skip(const char *key);
183 LottieBlendMode getBlendMode();
184 CapStyle getLineCap();
185 JoinStyle getLineJoin();
186 FillRule getFillRule();
187 LOTTrimData::TrimType getTrimType();
188 MatteType getMatteType();
189 LayerType getLayerType();
191 std::shared_ptr<LOTCompositionData> composition() const
195 void parseComposition();
196 void parseAssets(LOTCompositionData *comp);
197 std::shared_ptr<LOTAsset> parseAsset();
198 void parseLayers(LOTCompositionData *comp);
199 std::shared_ptr<LOTData> parseLayer();
200 void parseMaskProperty(LOTLayerData *layer);
201 void parseShapesAttr(LOTLayerData *layer);
202 void parseObject(LOTGroupData *parent);
203 std::shared_ptr<LOTMaskData> parseMaskObject();
204 std::shared_ptr<LOTData> parseObjectTypeAttr();
205 std::shared_ptr<LOTData> parseGroupObject();
206 std::shared_ptr<LOTData> parseRectObject();
207 std::shared_ptr<LOTData> parseEllipseObject();
208 std::shared_ptr<LOTData> parseShapeObject();
209 std::shared_ptr<LOTData> parsePolystarObject();
211 std::shared_ptr<LOTTransformData> parseTransformObject();
212 std::shared_ptr<LOTData> parseFillObject();
213 std::shared_ptr<LOTData> parseGFillObject();
214 std::shared_ptr<LOTData> parseStrokeObject();
215 std::shared_ptr<LOTData> parseGStrokeObject();
216 std::shared_ptr<LOTData> parseTrimObject();
217 std::shared_ptr<LOTData> parseReapeaterObject();
219 void parseGradientProperty(LOTGradient *gradient, const char *key);
221 VPointF parseInperpolatorPoint();
222 void parseArrayValue(VPointF &pt);
223 void parseArrayValue(LottieColor &pt);
224 void parseArrayValue(float &val);
225 void parseArrayValue(int &val);
226 void parseArrayValue(LottieGradient &gradient);
227 void getValue(VPointF &val);
228 void getValue(float &val);
229 void getValue(LottieColor &val);
230 void getValue(int &val);
231 void getValue(LottieShapeData &shape);
232 void getValue(LottieGradient &gradient);
233 template <typename T>
234 bool parseKeyFrameValue(const char *key, LOTKeyFrameValue<T> &value);
235 template <typename T>
236 void parseKeyFrame(LOTAnimInfo<T> &obj);
237 template <typename T>
238 void parseProperty(LOTAnimatable<T> &obj);
239 template <typename T>
240 void parsePropertyHelper(LOTAnimatable<T> &obj);
242 void parseShapeKeyFrame(LOTAnimInfo<LottieShapeData> &obj);
243 void parseShapeProperty(LOTAnimatable<LottieShapeData> &obj);
244 void parseArrayValue(std::vector<VPointF> &v);
245 void parseDashProperty(LOTDashProperty &dash);
247 LottieColor toColor(const char *str);
249 void resolveLayerRefs();
252 std::shared_ptr<LOTCompositionData> mComposition;
253 LOTCompositionData * compRef;
254 LOTLayerData * curLayerRef;
255 std::vector<std::shared_ptr<LOTLayerData>> mLayersToUpdate;
256 void SkipOut(int depth);
259 LookaheadParserHandler::LookaheadParserHandler(char *str)
260 : v_(), st_(kInit), r_(), ss_(str)
262 r_.IterativeParseInit();
266 void LookaheadParserHandler::ParseNext()
268 if (r_.HasParseError()) {
273 if (!r_.IterativeParseNext<parseFlags>(ss_, *this)) {
274 vCritical << "Lottie file parsing error";
279 bool LottieParserImpl::EnterObject()
281 if (st_ != kEnteringObject) {
283 RAPIDJSON_ASSERT(false);
291 bool LottieParserImpl::EnterArray()
293 if (st_ != kEnteringArray) {
295 RAPIDJSON_ASSERT(false);
303 const char *LottieParserImpl::NextObjectKey()
305 if (st_ == kHasKey) {
306 const char *result = v_.GetString();
312 * The parser works with a prdefined rule that it will be only
313 * while (NextObjectKey()) for each object but in case of our nested group
314 * object we can call multiple time NextObjectKey() while exiting the object
315 * so ignore those and don't put parser in the error state.
317 if (st_ == kExitingArray || st_ == kEnteringObject) {
318 // #ifdef DEBUG_PARSER
319 // vDebug<<"Object: Exiting nested loop";
324 if (st_ != kExitingObject) {
325 RAPIDJSON_ASSERT(false);
334 bool LottieParserImpl::NextArrayValue()
336 if (st_ == kExitingArray) {
342 * same as NextObjectKey()
344 if (st_ == kExitingObject) {
345 // #ifdef DEBUG_PARSER
346 // vDebug<<"Array: Exiting nested loop";
351 if (st_ == kError || st_ == kHasKey) {
352 RAPIDJSON_ASSERT(false);
360 int LottieParserImpl::GetInt()
362 if (st_ != kHasNumber || !v_.IsInt()) {
364 RAPIDJSON_ASSERT(false);
368 int result = v_.GetInt();
373 double LottieParserImpl::GetDouble()
375 if (st_ != kHasNumber) {
377 RAPIDJSON_ASSERT(false);
381 double result = v_.GetDouble();
386 bool LottieParserImpl::GetBool()
388 if (st_ != kHasBool) {
390 RAPIDJSON_ASSERT(false);
394 bool result = v_.GetBool();
399 void LottieParserImpl::GetNull()
401 if (st_ != kHasNull) {
409 const char *LottieParserImpl::GetString()
411 if (st_ != kHasString) {
413 RAPIDJSON_ASSERT(false);
417 const char *result = v_.GetString();
422 void LottieParserImpl::SkipOut(int depth)
425 if (st_ == kEnteringArray || st_ == kEnteringObject) {
427 } else if (st_ == kExitingArray || st_ == kExitingObject) {
429 } else if (st_ == kError) {
430 RAPIDJSON_ASSERT(false);
438 void LottieParserImpl::SkipValue()
443 void LottieParserImpl::SkipArray()
448 void LottieParserImpl::SkipObject()
453 Value *LottieParserImpl::PeekValue()
455 if (st_ >= kHasNull && st_ <= kHasKey) {
462 int LottieParserImpl::PeekType()
464 if (st_ >= kHasNull && st_ <= kHasKey) {
468 if (st_ == kEnteringArray) {
472 if (st_ == kEnteringObject) {
479 void LottieParserImpl::Skip(const char *key)
481 if (PeekType() == kArrayType) {
484 } else if (PeekType() == kObjectType) {
492 LottieBlendMode LottieParserImpl::getBlendMode()
494 RAPIDJSON_ASSERT(PeekType() == kNumberType);
495 LottieBlendMode mode = LottieBlendMode::Normal;
499 mode = LottieBlendMode::Multiply;
502 mode = LottieBlendMode::Screen;
505 mode = LottieBlendMode::OverLay;
512 VRect LottieParserImpl::getRect()
515 RAPIDJSON_ASSERT(PeekType() == kObjectType);
517 while (const char *key = NextObjectKey()) {
518 if (0 == strcmp(key, "l")) {
519 RAPIDJSON_ASSERT(PeekType() == kNumberType);
521 } else if (0 == strcmp(key, "r")) {
522 RAPIDJSON_ASSERT(PeekType() == kNumberType);
523 r.setRight(GetInt());
524 } else if (0 == strcmp(key, "t")) {
525 RAPIDJSON_ASSERT(PeekType() == kNumberType);
527 } else if (0 == strcmp(key, "b")) {
528 RAPIDJSON_ASSERT(PeekType() == kNumberType);
529 r.setBottom(GetInt());
531 RAPIDJSON_ASSERT(false);
537 void LottieParserImpl::resolveLayerRefs()
539 for (const auto& i : mLayersToUpdate) {
540 LOTLayerData *layer = i.get();
541 auto search = compRef->mAssets.find(layer->mPreCompRefId);
542 if (search != compRef->mAssets.end()) {
543 layer->mChildren = search->second.get()->mLayers;
548 void LottieParserImpl::parseComposition()
550 RAPIDJSON_ASSERT(PeekType() == kObjectType);
552 std::shared_ptr<LOTCompositionData> sharedComposition =
553 std::make_shared<LOTCompositionData>();
554 LOTCompositionData *comp = sharedComposition.get();
556 while (const char *key = NextObjectKey()) {
557 if (0 == strcmp(key, "v")) {
558 RAPIDJSON_ASSERT(PeekType() == kStringType);
559 comp->mVersion = std::string(GetString());
560 } else if (0 == strcmp(key, "w")) {
561 RAPIDJSON_ASSERT(PeekType() == kNumberType);
562 comp->mSize.setWidth(GetInt());
563 } else if (0 == strcmp(key, "h")) {
564 RAPIDJSON_ASSERT(PeekType() == kNumberType);
565 comp->mSize.setHeight(GetInt());
566 } else if (0 == strcmp(key, "ip")) {
567 RAPIDJSON_ASSERT(PeekType() == kNumberType);
568 comp->mStartFrame = GetDouble();
569 } else if (0 == strcmp(key, "op")) {
570 RAPIDJSON_ASSERT(PeekType() == kNumberType);
571 comp->mEndFrame = GetDouble();
572 } else if (0 == strcmp(key, "fr")) {
573 RAPIDJSON_ASSERT(PeekType() == kNumberType);
574 comp->mFrameRate = GetDouble();
575 } else if (0 == strcmp(key, "assets")) {
577 } else if (0 == strcmp(key, "layers")) {
581 vWarning << "Composition Attribute Skipped : " << key;
587 comp->setStatic(comp->mRootLayer->isStatic());
588 comp->mRootLayer->mInFrame = comp->mStartFrame;
589 comp->mRootLayer->mOutFrame = comp->mEndFrame;
591 mComposition = sharedComposition;
594 void LottieParserImpl::parseAssets(LOTCompositionData *composition)
596 RAPIDJSON_ASSERT(PeekType() == kArrayType);
598 while (NextArrayValue()) {
599 std::shared_ptr<LOTAsset> asset = parseAsset();
600 composition->mAssets[asset->mRefId] = asset;
602 // update the precomp layers with the actual layer object
606 * https://github.com/airbnb/lottie-web/blob/master/docs/json/layers/shape.json
609 std::shared_ptr<LOTAsset> LottieParserImpl::parseAsset()
611 RAPIDJSON_ASSERT(PeekType() == kObjectType);
612 std::shared_ptr<LOTAsset> sharedAsset = std::make_shared<LOTAsset>();
613 LOTAsset * asset = sharedAsset.get();
615 while (const char *key = NextObjectKey()) {
616 if (0 == strcmp(key, "ty")) { /* Type of layer: Shape. Value 4.*/
617 RAPIDJSON_ASSERT(PeekType() == kNumberType);
618 asset->mAssetType = GetInt();
619 } else if (0 == strcmp(key, "id")) { /* reference id*/
620 RAPIDJSON_ASSERT(PeekType() == kStringType);
621 asset->mRefId = std::string(GetString());
622 } else if (0 == strcmp(key, "layers")) {
623 RAPIDJSON_ASSERT(PeekType() == kArrayType);
625 while (NextArrayValue()) {
626 std::shared_ptr<LOTData> layer = parseLayer();
627 asset->mLayers.push_back(layer);
631 vWarning << "Asset Attribute Skipped : " << key;
639 void LottieParserImpl::parseLayers(LOTCompositionData *comp)
641 comp->mRootLayer = std::make_shared<LOTLayerData>();
642 comp->mRootLayer->mRoot = true;
643 comp->mRootLayer->mLayerType = LayerType::Precomp;
644 comp->mRootLayer->mTransform = std::make_shared<LOTTransformData>();
645 bool staticFlag = true;
646 RAPIDJSON_ASSERT(PeekType() == kArrayType);
648 while (NextArrayValue()) {
649 std::shared_ptr<LOTData> layer = parseLayer();
650 staticFlag &= layer->isStatic();
651 comp->mRootLayer->mChildren.push_back(layer);
653 comp->mRootLayer->setStatic(staticFlag);
656 LottieColor LottieParserImpl::toColor(const char *str)
660 RAPIDJSON_ASSERT(strlen(str) == 7);
661 RAPIDJSON_ASSERT(str[0] == '#');
663 char tmp[3] = {'\0', '\0', '\0'};
666 color.r = std::strtol(tmp, NULL, 16) / 255.0;
670 color.g = std::strtol(tmp, NULL, 16) / 255.0;
674 color.b = std::strtol(tmp, NULL, 16) / 255.0;
679 MatteType LottieParserImpl::getMatteType()
681 RAPIDJSON_ASSERT(PeekType() == kNumberType);
684 return MatteType::Alpha;
687 return MatteType::AlphaInv;
690 return MatteType::Luma;
693 return MatteType::LumaInv;
696 return MatteType::None;
701 LayerType LottieParserImpl::getLayerType()
703 RAPIDJSON_ASSERT(PeekType() == kNumberType);
706 return LayerType::Precomp;
709 return LayerType::Solid;
712 return LayerType::Image;
715 return LayerType::Null;
718 return LayerType::Shape;
721 return LayerType::Text;
724 return LayerType::Null;
730 * https://github.com/airbnb/lottie-web/blob/master/docs/json/layers/shape.json
733 std::shared_ptr<LOTData> LottieParserImpl::parseLayer()
735 RAPIDJSON_ASSERT(PeekType() == kObjectType);
736 std::shared_ptr<LOTLayerData> sharedLayer =
737 std::make_shared<LOTLayerData>();
738 LOTLayerData *layer = sharedLayer.get();
740 bool hasLayerRef = false;
742 while (const char *key = NextObjectKey()) {
743 if (0 == strcmp(key, "ty")) { /* Type of layer*/
744 layer->mLayerType = getLayerType();
745 } else if (0 == strcmp(key, "ind")) { /*Layer index in AE. Used for
746 parenting and expressions.*/
747 RAPIDJSON_ASSERT(PeekType() == kNumberType);
748 layer->mId = GetInt();
749 } else if (0 == strcmp(key, "parent")) { /*Layer Parent. Uses "ind" of parent.*/
750 RAPIDJSON_ASSERT(PeekType() == kNumberType);
751 layer->mParentId = GetInt();
752 } else if (0 == strcmp(key, "refId")) { /*preComp Layer reference id*/
753 RAPIDJSON_ASSERT(PeekType() == kStringType);
754 layer->mPreCompRefId = std::string(GetString());
755 layer->mHasGradient = true;
756 mLayersToUpdate.push_back(sharedLayer);
758 } else if (0 == strcmp(key, "sr")) { // "Layer Time Stretching"
759 RAPIDJSON_ASSERT(PeekType() == kNumberType);
760 layer->mTimeStreatch = GetDouble();
761 } else if (0 == strcmp(key, "tm")) { // time remapping
762 parseProperty(layer->mTimeRemap);
763 } else if (0 == strcmp(key, "ip")) {
764 RAPIDJSON_ASSERT(PeekType() == kNumberType);
765 layer->mInFrame = std::round(GetDouble());
766 } else if (0 == strcmp(key, "op")) {
767 RAPIDJSON_ASSERT(PeekType() == kNumberType);
768 layer->mOutFrame = std::round(GetDouble());
769 } else if (0 == strcmp(key, "st")) {
770 RAPIDJSON_ASSERT(PeekType() == kNumberType);
771 layer->mStartFrame = GetDouble();
772 } else if (0 == strcmp(key, "bounds")) {
773 layer->mBound = getRect();
774 } else if (0 == strcmp(key, "bm")) {
775 layer->mBlendMode = getBlendMode();
776 } else if (0 == strcmp(key, "ks")) {
777 RAPIDJSON_ASSERT(PeekType() == kObjectType);
779 layer->mTransform = parseTransformObject();
780 } else if (0 == strcmp(key, "shapes")) {
781 parseShapesAttr(layer);
782 } else if (0 == strcmp(key, "sw")) {
783 layer->mSolidLayer.mWidth = GetInt();
784 } else if (0 == strcmp(key, "sh")) {
785 layer->mSolidLayer.mHeight = GetInt();
786 } else if (0 == strcmp(key, "sc")) {
787 layer->mSolidLayer.mColor = toColor(GetString());
788 } else if (0 == strcmp(key, "tt")) {
789 layer->mMatteType = getMatteType();
790 } else if (0 == strcmp(key, "hasMask")) {
791 layer->mHasMask = GetBool();
792 } else if (0 == strcmp(key, "masksProperties")) {
793 parseMaskProperty(layer);
794 } else if (0 == strcmp(key, "ao")) {
795 layer->mAutoOrient = GetInt();
798 vWarning << "Layer Attribute Skipped : " << key;
804 // update the static property of layer
805 bool staticFlag = true;
806 for (const auto& child : layer->mChildren) {
807 staticFlag &= child.get()->isStatic();
810 for (const auto& mask : layer->mMasks) {
811 staticFlag &= mask->isStatic();
814 layer->setStatic(staticFlag && layer->mTransform->isStatic() &&
816 layer->mCompRef = compRef;
820 void LottieParserImpl::parseMaskProperty(LOTLayerData *layer)
822 RAPIDJSON_ASSERT(PeekType() == kArrayType);
824 while (NextArrayValue()) {
825 layer->mMasks.push_back(parseMaskObject());
829 std::shared_ptr<LOTMaskData> LottieParserImpl::parseMaskObject()
831 std::shared_ptr<LOTMaskData> sharedMask = std::make_shared<LOTMaskData>();
832 LOTMaskData * obj = sharedMask.get();
834 RAPIDJSON_ASSERT(PeekType() == kObjectType);
836 while (const char *key = NextObjectKey()) {
837 if (0 == strcmp(key, "inv")) {
838 obj->mInv = GetBool();
839 } else if (0 == strcmp(key, "mode")) {
840 const char *str = GetString();
843 obj->mMode = LOTMaskData::Mode::None;
846 obj->mMode = LOTMaskData::Mode::Add;
849 obj->mMode = LOTMaskData::Mode::Substarct;
852 obj->mMode = LOTMaskData::Mode::Intersect;
855 obj->mMode = LOTMaskData::Mode::Difference;
858 obj->mMode = LOTMaskData::Mode::None;
861 } else if (0 == strcmp(key, "pt")) {
862 parseShapeProperty(obj->mShape);
863 } else if (0 == strcmp(key, "o")) {
864 parseProperty(obj->mOpacity);
869 obj->mIsStatic = obj->mShape.isStatic() && obj->mOpacity.isStatic();
873 void LottieParserImpl::parseShapesAttr(LOTLayerData *layer)
875 RAPIDJSON_ASSERT(PeekType() == kArrayType);
877 while (NextArrayValue()) {
882 std::shared_ptr<LOTData> LottieParserImpl::parseObjectTypeAttr()
884 RAPIDJSON_ASSERT(PeekType() == kStringType);
885 const char *type = GetString();
886 if (0 == strcmp(type, "gr")) {
887 return parseGroupObject();
888 } else if (0 == strcmp(type, "rc")) {
889 return parseRectObject();
890 } else if (0 == strcmp(type, "el")) {
891 return parseEllipseObject();
892 } else if (0 == strcmp(type, "tr")) {
893 return parseTransformObject();
894 } else if (0 == strcmp(type, "fl")) {
895 return parseFillObject();
896 } else if (0 == strcmp(type, "st")) {
897 return parseStrokeObject();
898 } else if (0 == strcmp(type, "gf")) {
899 curLayerRef->mHasGradient = true;
900 return parseGFillObject();
901 } else if (0 == strcmp(type, "gs")) {
902 curLayerRef->mHasGradient = true;
903 return parseGStrokeObject();
904 } else if (0 == strcmp(type, "sh")) {
905 return parseShapeObject();
906 } else if (0 == strcmp(type, "sr")) {
907 return parsePolystarObject();
908 } else if (0 == strcmp(type, "tm")) {
909 curLayerRef->mHasPathOperator = true;
910 return parseTrimObject();
911 } else if (0 == strcmp(type, "rp")) {
912 curLayerRef->mHasRepeater = true;
913 return parseReapeaterObject();
916 vDebug << "The Object Type not yet handled = " << type;
922 void LottieParserImpl::parseObject(LOTGroupData *parent)
924 RAPIDJSON_ASSERT(PeekType() == kObjectType);
926 while (const char *key = NextObjectKey()) {
927 if (0 == strcmp(key, "ty")) {
928 auto child = parseObjectTypeAttr();
929 if (child && !child->hidden()) parent->mChildren.push_back(child);
936 std::shared_ptr<LOTData> LottieParserImpl::parseGroupObject()
938 std::shared_ptr<LOTShapeGroupData> sharedGroup =
939 std::make_shared<LOTShapeGroupData>();
941 LOTShapeGroupData *group = sharedGroup.get();
942 while (const char *key = NextObjectKey()) {
943 if (0 == strcmp(key, "it")) {
944 RAPIDJSON_ASSERT(PeekType() == kArrayType);
946 while (NextArrayValue()) {
947 RAPIDJSON_ASSERT(PeekType() == kObjectType);
950 if (group->mChildren.back()->mType == LOTData::Type::Transform) {
951 group->mTransform = std::static_pointer_cast<LOTTransformData>(
952 group->mChildren.back());
953 group->mChildren.pop_back();
959 bool staticFlag = true;
960 for (const auto& child : group->mChildren) {
961 staticFlag &= child.get()->isStatic();
964 group->setStatic(staticFlag && group->mTransform->isStatic());
970 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/rect.json
972 std::shared_ptr<LOTData> LottieParserImpl::parseRectObject()
974 std::shared_ptr<LOTRectData> sharedRect = std::make_shared<LOTRectData>();
975 LOTRectData * obj = sharedRect.get();
977 while (const char *key = NextObjectKey()) {
978 if (0 == strcmp(key, "p")) {
979 parseProperty(obj->mPos);
980 } else if (0 == strcmp(key, "s")) {
981 parseProperty(obj->mSize);
982 } else if (0 == strcmp(key, "r")) {
983 parseProperty(obj->mRound);
984 } else if (0 == strcmp(key, "d")) {
985 obj->mDirection = GetInt();
986 } else if (0 == strcmp(key, "hd")) {
987 obj->mHidden = GetBool();
992 obj->setStatic(obj->mPos.isStatic() && obj->mSize.isStatic() &&
993 obj->mRound.isStatic());
998 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/ellipse.json
1000 std::shared_ptr<LOTData> LottieParserImpl::parseEllipseObject()
1002 std::shared_ptr<LOTEllipseData> sharedEllipse =
1003 std::make_shared<LOTEllipseData>();
1004 LOTEllipseData *obj = sharedEllipse.get();
1006 while (const char *key = NextObjectKey()) {
1007 if (0 == strcmp(key, "p")) {
1008 parseProperty(obj->mPos);
1009 } else if (0 == strcmp(key, "s")) {
1010 parseProperty(obj->mSize);
1011 } else if (0 == strcmp(key, "d")) {
1012 obj->mDirection = GetInt();
1013 } else if (0 == strcmp(key, "hd")) {
1014 obj->mHidden = GetBool();
1019 obj->setStatic(obj->mPos.isStatic() && obj->mSize.isStatic());
1020 return sharedEllipse;
1024 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/shape.json
1026 std::shared_ptr<LOTData> LottieParserImpl::parseShapeObject()
1028 std::shared_ptr<LOTShapeData> sharedShape =
1029 std::make_shared<LOTShapeData>();
1030 LOTShapeData *obj = sharedShape.get();
1032 while (const char *key = NextObjectKey()) {
1033 if (0 == strcmp(key, "ks")) {
1034 parseShapeProperty(obj->mShape);
1035 } else if (0 == strcmp(key, "d")) {
1036 obj->mDirection = GetInt();
1037 } else if (0 == strcmp(key, "hd")) {
1038 obj->mHidden = GetBool();
1041 vDebug << "Shape property ignored :" << key;
1046 obj->setStatic(obj->mShape.isStatic());
1052 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/star.json
1054 std::shared_ptr<LOTData> LottieParserImpl::parsePolystarObject()
1056 std::shared_ptr<LOTPolystarData> sharedPolystar =
1057 std::make_shared<LOTPolystarData>();
1058 LOTPolystarData *obj = sharedPolystar.get();
1060 while (const char *key = NextObjectKey()) {
1061 if (0 == strcmp(key, "p")) {
1062 parseProperty(obj->mPos);
1063 } else if (0 == strcmp(key, "pt")) {
1064 parseProperty(obj->mPointCount);
1065 } else if (0 == strcmp(key, "ir")) {
1066 parseProperty(obj->mInnerRadius);
1067 } else if (0 == strcmp(key, "is")) {
1068 parseProperty(obj->mInnerRoundness);
1069 } else if (0 == strcmp(key, "or")) {
1070 parseProperty(obj->mOuterRadius);
1071 } else if (0 == strcmp(key, "os")) {
1072 parseProperty(obj->mOuterRoundness);
1073 } else if (0 == strcmp(key, "r")) {
1074 parseProperty(obj->mRotation);
1075 } else if (0 == strcmp(key, "sy")) {
1076 int starType = GetInt();
1077 if (starType == 1) obj->mType = LOTPolystarData::PolyType::Star;
1078 if (starType == 2) obj->mType = LOTPolystarData::PolyType::Polygon;
1079 } else if (0 == strcmp(key, "d")) {
1080 obj->mDirection = GetInt();
1081 } else if (0 == strcmp(key, "hd")) {
1082 obj->mHidden = GetBool();
1085 vDebug << "Polystar property ignored :" << key;
1091 obj->mPos.isStatic() && obj->mPointCount.isStatic() &&
1092 obj->mInnerRadius.isStatic() && obj->mInnerRoundness.isStatic() &&
1093 obj->mOuterRadius.isStatic() && obj->mOuterRoundness.isStatic() &&
1094 obj->mRotation.isStatic());
1096 return sharedPolystar;
1099 LOTTrimData::TrimType LottieParserImpl::getTrimType()
1101 RAPIDJSON_ASSERT(PeekType() == kNumberType);
1104 return LOTTrimData::TrimType::Simultaneously;
1107 return LOTTrimData::TrimType::Individually;
1110 RAPIDJSON_ASSERT(0);
1116 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/trim.json
1118 std::shared_ptr<LOTData> LottieParserImpl::parseTrimObject()
1120 std::shared_ptr<LOTTrimData> sharedTrim = std::make_shared<LOTTrimData>();
1121 LOTTrimData * obj = sharedTrim.get();
1123 while (const char *key = NextObjectKey()) {
1124 if (0 == strcmp(key, "s")) {
1125 parseProperty(obj->mStart);
1126 } else if (0 == strcmp(key, "e")) {
1127 parseProperty(obj->mEnd);
1128 } else if (0 == strcmp(key, "o")) {
1129 parseProperty(obj->mOffset);
1130 } else if (0 == strcmp(key, "m")) {
1131 obj->mTrimType = getTrimType();
1132 } else if (0 == strcmp(key, "hd")) {
1133 obj->mHidden = GetBool();
1136 vDebug << "Trim property ignored :" << key;
1141 obj->setStatic(obj->mStart.isStatic() && obj->mEnd.isStatic() &&
1142 obj->mOffset.isStatic());
1146 std::shared_ptr<LOTData> LottieParserImpl::parseReapeaterObject()
1148 std::shared_ptr<LOTRepeaterData> sharedRepeater =
1149 std::make_shared<LOTRepeaterData>();
1150 LOTRepeaterData *obj = sharedRepeater.get();
1152 while (const char *key = NextObjectKey()) {
1153 if (0 == strcmp(key, "c")) {
1154 parseProperty(obj->mCopies);
1155 } else if (0 == strcmp(key, "o")) {
1156 parseProperty(obj->mOffset);
1157 } else if (0 == strcmp(key, "tr")) {
1158 obj->mTransform = parseTransformObject();
1159 } else if (0 == strcmp(key, "hd")) {
1160 obj->mHidden = GetBool();
1163 vDebug << "Repeater property ignored :" << key;
1168 obj->setStatic(obj->mCopies.isStatic() && obj->mOffset.isStatic() &&
1169 obj->mTransform->isStatic());
1171 return sharedRepeater;
1175 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/transform.json
1177 std::shared_ptr<LOTTransformData> LottieParserImpl::parseTransformObject()
1179 std::shared_ptr<LOTTransformData> sharedTransform =
1180 std::make_shared<LOTTransformData>();
1181 LOTTransformData *obj = sharedTransform.get();
1183 while (const char *key = NextObjectKey()) {
1184 if (0 == strcmp(key, "a")) {
1185 parseProperty(obj->mAnchor);
1186 } else if (0 == strcmp(key, "p")) {
1188 while (const char *key = NextObjectKey()) {
1189 if (0 == strcmp(key, "k")) {
1190 parsePropertyHelper(obj->mPosition);
1191 } else if (0 == strcmp(key, "s")) {
1192 obj->mSeparate = GetBool();
1193 } else if (obj->mSeparate && (0 == strcmp(key, "x"))) {
1194 parseProperty(obj->mX);
1195 } else if (obj->mSeparate && (0 == strcmp(key, "y"))) {
1196 parseProperty(obj->mY);
1201 } else if (0 == strcmp(key, "r")) {
1202 parseProperty(obj->mRotation);
1203 } else if (0 == strcmp(key, "s")) {
1204 parseProperty(obj->mScale);
1205 } else if (0 == strcmp(key, "sk")) {
1206 parseProperty(obj->mSkew);
1207 } else if (0 == strcmp(key, "sa")) {
1208 parseProperty(obj->mSkewAxis);
1209 } else if (0 == strcmp(key, "o")) {
1210 parseProperty(obj->mOpacity);
1211 } else if (0 == strcmp(key, "hd")) {
1212 obj->mHidden = GetBool();
1217 obj->mStaticMatrix = obj->mAnchor.isStatic() && obj->mPosition.isStatic() &&
1218 obj->mRotation.isStatic() && obj->mScale.isStatic() &&
1219 obj->mSkew.isStatic() && obj->mSkewAxis.isStatic() &&
1220 obj->mX.isStatic() && obj->mY.isStatic();
1222 obj->setStatic(obj->mStaticMatrix && obj->mOpacity.isStatic());
1224 if (obj->mStaticMatrix) obj->cacheMatrix();
1226 return sharedTransform;
1230 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/fill.json
1232 std::shared_ptr<LOTData> LottieParserImpl::parseFillObject()
1234 std::shared_ptr<LOTFillData> sharedFill = std::make_shared<LOTFillData>();
1235 LOTFillData * obj = sharedFill.get();
1237 while (const char *key = NextObjectKey()) {
1238 if (0 == strcmp(key, "c")) {
1239 parseProperty(obj->mColor);
1240 } else if (0 == strcmp(key, "o")) {
1241 parseProperty(obj->mOpacity);
1242 } else if (0 == strcmp(key, "fillEnabled")) {
1243 obj->mEnabled = GetBool();
1244 } else if (0 == strcmp(key, "r")) {
1245 obj->mFillRule = getFillRule();
1246 } else if (0 == strcmp(key, "hd")) {
1247 obj->mHidden = GetBool();
1250 vWarning << "Fill property skipped = " << key;
1255 obj->setStatic(obj->mColor.isStatic() && obj->mOpacity.isStatic());
1261 * https://github.com/airbnb/lottie-web/blob/master/docs/json/helpers/lineCap.json
1263 CapStyle LottieParserImpl::getLineCap()
1265 RAPIDJSON_ASSERT(PeekType() == kNumberType);
1268 return CapStyle::Flat;
1271 return CapStyle::Round;
1274 return CapStyle::Square;
1279 FillRule LottieParserImpl::getFillRule()
1281 RAPIDJSON_ASSERT(PeekType() == kNumberType);
1284 return FillRule::Winding;
1287 return FillRule::EvenOdd;
1290 return FillRule::Winding;
1296 * https://github.com/airbnb/lottie-web/blob/master/docs/json/helpers/lineJoin.json
1298 JoinStyle LottieParserImpl::getLineJoin()
1300 RAPIDJSON_ASSERT(PeekType() == kNumberType);
1303 return JoinStyle::Miter;
1306 return JoinStyle::Round;
1309 return JoinStyle::Bevel;
1315 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/stroke.json
1317 std::shared_ptr<LOTData> LottieParserImpl::parseStrokeObject()
1319 std::shared_ptr<LOTStrokeData> sharedStroke =
1320 std::make_shared<LOTStrokeData>();
1321 LOTStrokeData *obj = sharedStroke.get();
1323 while (const char *key = NextObjectKey()) {
1324 if (0 == strcmp(key, "c")) {
1325 parseProperty(obj->mColor);
1326 } else if (0 == strcmp(key, "o")) {
1327 parseProperty(obj->mOpacity);
1328 } else if (0 == strcmp(key, "w")) {
1329 parseProperty(obj->mWidth);
1330 } else if (0 == strcmp(key, "fillEnabled")) {
1331 obj->mEnabled = GetBool();
1332 } else if (0 == strcmp(key, "lc")) {
1333 obj->mCapStyle = getLineCap();
1334 } else if (0 == strcmp(key, "lj")) {
1335 obj->mJoinStyle = getLineJoin();
1336 } else if (0 == strcmp(key, "ml")) {
1337 RAPIDJSON_ASSERT(PeekType() == kNumberType);
1338 obj->mMeterLimit = GetDouble();
1339 } else if (0 == strcmp(key, "d")) {
1340 parseDashProperty(obj->mDash);
1341 } else if (0 == strcmp(key, "hd")) {
1342 obj->mHidden = GetBool();
1345 vWarning << "Stroke property skipped = " << key;
1350 obj->setStatic(obj->mColor.isStatic() && obj->mOpacity.isStatic() &&
1351 obj->mWidth.isStatic() && obj->mDash.mStatic);
1352 return sharedStroke;
1355 void LottieParserImpl::parseGradientProperty(LOTGradient *obj, const char *key)
1357 if (0 == strcmp(key, "t")) {
1358 RAPIDJSON_ASSERT(PeekType() == kNumberType);
1359 obj->mGradientType = GetInt();
1360 } else if (0 == strcmp(key, "o")) {
1361 parseProperty(obj->mOpacity);
1362 } else if (0 == strcmp(key, "s")) {
1363 parseProperty(obj->mStartPoint);
1364 } else if (0 == strcmp(key, "e")) {
1365 parseProperty(obj->mEndPoint);
1366 } else if (0 == strcmp(key, "h")) {
1367 parseProperty(obj->mHighlightLength);
1368 } else if (0 == strcmp(key, "a")) {
1369 parseProperty(obj->mHighlightAngle);
1370 } else if (0 == strcmp(key, "g")) {
1372 while (const char *key = NextObjectKey()) {
1373 if (0 == strcmp(key, "k")) {
1374 parseProperty(obj->mGradient);
1375 } else if (0 == strcmp(key, "p")) {
1376 obj->mColorPoints = GetInt();
1381 } else if (0 == strcmp(key, "hd")) {
1382 obj->mHidden = GetBool();
1385 vWarning << "Gradient property skipped = " << key;
1390 obj->mOpacity.isStatic() && obj->mStartPoint.isStatic() &&
1391 obj->mEndPoint.isStatic() && obj->mHighlightAngle.isStatic() &&
1392 obj->mHighlightLength.isStatic() && obj->mGradient.isStatic());
1396 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/gfill.json
1398 std::shared_ptr<LOTData> LottieParserImpl::parseGFillObject()
1400 std::shared_ptr<LOTGFillData> sharedGFill =
1401 std::make_shared<LOTGFillData>();
1402 LOTGFillData *obj = sharedGFill.get();
1404 while (const char *key = NextObjectKey()) {
1405 if (0 == strcmp(key, "r")) {
1406 obj->mFillRule = getFillRule();
1408 parseGradientProperty(obj, key);
1414 void LottieParserImpl::parseDashProperty(LOTDashProperty &dash)
1416 dash.mDashCount = 0;
1417 dash.mStatic = true;
1418 RAPIDJSON_ASSERT(PeekType() == kArrayType);
1420 while (NextArrayValue()) {
1421 RAPIDJSON_ASSERT(PeekType() == kObjectType);
1423 while (const char *key = NextObjectKey()) {
1424 if (0 == strcmp(key, "v")) {
1425 parseProperty(dash.mDashArray[dash.mDashCount++]);
1432 // update the staic proprty
1433 for (int i = 0; i < dash.mDashCount; i++) {
1434 if (!dash.mDashArray[i].isStatic()) {
1435 dash.mStatic = false;
1442 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/gstroke.json
1444 std::shared_ptr<LOTData> LottieParserImpl::parseGStrokeObject()
1446 std::shared_ptr<LOTGStrokeData> sharedGStroke =
1447 std::make_shared<LOTGStrokeData>();
1448 LOTGStrokeData *obj = sharedGStroke.get();
1450 while (const char *key = NextObjectKey()) {
1451 if (0 == strcmp(key, "w")) {
1452 parseProperty(obj->mWidth);
1453 } else if (0 == strcmp(key, "lc")) {
1454 obj->mCapStyle = getLineCap();
1455 } else if (0 == strcmp(key, "lj")) {
1456 obj->mJoinStyle = getLineJoin();
1457 } else if (0 == strcmp(key, "ml")) {
1458 RAPIDJSON_ASSERT(PeekType() == kNumberType);
1459 obj->mMeterLimit = GetDouble();
1460 } else if (0 == strcmp(key, "d")) {
1461 parseDashProperty(obj->mDash);
1463 parseGradientProperty(obj, key);
1467 obj->setStatic(obj->isStatic() && obj->mWidth.isStatic() &&
1468 obj->mDash.mStatic);
1469 return sharedGStroke;
1472 void LottieParserImpl::parseArrayValue(LottieColor &color)
1476 while (NextArrayValue()) {
1477 val[i++] = GetDouble();
1485 void LottieParserImpl::parseArrayValue(VPointF &pt)
1489 while (NextArrayValue()) {
1490 val[i++] = GetDouble();
1496 void LottieParserImpl::parseArrayValue(float &val)
1498 RAPIDJSON_ASSERT(0);
1502 void LottieParserImpl::parseArrayValue(int &val)
1504 RAPIDJSON_ASSERT(0);
1508 void LottieParserImpl::parseArrayValue(std::vector<VPointF> &v)
1510 RAPIDJSON_ASSERT(PeekType() == kArrayType);
1512 while (NextArrayValue()) {
1513 RAPIDJSON_ASSERT(PeekType() == kArrayType);
1516 parseArrayValue(pt);
1521 void LottieParserImpl::getValue(VPointF &pt)
1525 RAPIDJSON_ASSERT(PeekType() == kArrayType);
1527 while (NextArrayValue()) {
1528 val[i++] = GetDouble();
1534 void LottieParserImpl::getValue(float &val)
1536 if (PeekType() == kArrayType) {
1538 while (NextArrayValue()) {
1541 } else if (PeekType() == kNumberType) {
1544 RAPIDJSON_ASSERT(0);
1548 void LottieParserImpl::getValue(LottieColor &color)
1552 RAPIDJSON_ASSERT(PeekType() == kArrayType);
1554 while (NextArrayValue()) {
1555 val[i++] = GetDouble();
1562 void LottieParserImpl::parseArrayValue(LottieGradient &grad)
1564 while (NextArrayValue()) {
1565 grad.mGradient.push_back(GetDouble());
1569 void LottieParserImpl::getValue(LottieGradient &grad)
1571 RAPIDJSON_ASSERT(PeekType() == kArrayType);
1573 while (NextArrayValue()) {
1574 grad.mGradient.push_back(GetDouble());
1578 void LottieParserImpl::getValue(int &val)
1580 if (PeekType() == kArrayType) {
1582 while (NextArrayValue()) {
1585 } else if (PeekType() == kNumberType) {
1588 RAPIDJSON_ASSERT(0);
1592 void LottieParserImpl::getValue(LottieShapeData &obj)
1594 std::vector<VPointF> inPoint; /* "i" */
1595 std::vector<VPointF> outPoint; /* "o" */
1596 std::vector<VPointF> vertices; /* "v" */
1597 std::vector<VPointF> points;
1598 bool closed = false;
1601 * The shape object could be wrapped by a array
1602 * if its part of the keyframe object
1604 bool arrayWrapper = (PeekType() == kArrayType);
1605 if (arrayWrapper) EnterArray();
1607 RAPIDJSON_ASSERT(PeekType() == kObjectType);
1609 while (const char *key = NextObjectKey()) {
1610 if (0 == strcmp(key, "i")) {
1611 parseArrayValue(inPoint);
1612 } else if (0 == strcmp(key, "o")) {
1613 parseArrayValue(outPoint);
1614 } else if (0 == strcmp(key, "v")) {
1615 parseArrayValue(vertices);
1616 } else if (0 == strcmp(key, "c")) {
1619 RAPIDJSON_ASSERT(0);
1623 // exit properly from the array
1624 if (arrayWrapper) NextArrayValue();
1626 // shape data could be empty.
1627 if (inPoint.empty() || outPoint.empty() || vertices.empty()) return;
1630 * Convert the AE shape format to
1631 * list of bazier curves
1632 * The final structure will be Move +size*Cubic + Cubic (if the path is
1635 if (inPoint.size() != outPoint.size() ||
1636 inPoint.size() != vertices.size()) {
1637 vCritical << "The Shape data are corrupted";
1638 points = std::vector<VPointF>();
1640 int size = vertices.size();
1641 points.reserve(3 * size + 4);
1642 points.push_back(vertices[0]);
1643 for (int i = 1; i < size; i++) {
1644 points.push_back(vertices[i - 1] +
1645 outPoint[i - 1]); // CP1 = start + outTangent
1646 points.push_back(vertices[i] +
1647 inPoint[i]); // CP2 = end + inTangent
1648 points.push_back(vertices[i]); // end point
1652 points.push_back(vertices[size - 1] +
1653 outPoint[size - 1]); // CP1 = start + outTangent
1654 points.push_back(vertices[0] +
1655 inPoint[0]); // CP2 = end + inTangent
1656 points.push_back(vertices[0]); // end point
1659 obj.mPoints = std::move(points);
1660 obj.mClosed = closed;
1663 VPointF LottieParserImpl::parseInperpolatorPoint()
1666 RAPIDJSON_ASSERT(PeekType() == kObjectType);
1668 while (const char *key = NextObjectKey()) {
1669 if (0 == strcmp(key, "x")) {
1670 if (PeekType() == kNumberType) {
1671 cp.setX(GetDouble());
1673 RAPIDJSON_ASSERT(PeekType() == kArrayType);
1675 while (NextArrayValue()) {
1676 cp.setX(GetDouble());
1680 if (0 == strcmp(key, "y")) {
1681 if (PeekType() == kNumberType) {
1682 cp.setY(GetDouble());
1684 RAPIDJSON_ASSERT(PeekType() == kArrayType);
1686 while (NextArrayValue()) {
1687 cp.setY(GetDouble());
1695 template <typename T>
1696 bool LottieParserImpl::parseKeyFrameValue(const char * key,
1697 LOTKeyFrameValue<T> &value)
1699 if (0 == strcmp(key, "s")) {
1700 getValue(value.mStartValue);
1701 } else if (0 == strcmp(key, "e")) {
1702 getValue(value.mEndValue);
1710 bool LottieParserImpl::parseKeyFrameValue(const char * key,
1711 LOTKeyFrameValue<VPointF> &value)
1713 if (0 == strcmp(key, "s")) {
1714 getValue(value.mStartValue);
1715 } else if (0 == strcmp(key, "e")) {
1716 getValue(value.mEndValue);
1717 } else if (0 == strcmp(key, "ti")) {
1718 value.mPathKeyFrame = true;
1719 getValue(value.mInTangent);
1720 } else if (0 == strcmp(key, "to")) {
1721 value.mPathKeyFrame = true;
1722 getValue(value.mOutTangent);
1730 * https://github.com/airbnb/lottie-web/blob/master/docs/json/properties/multiDimensionalKeyframed.json
1732 template <typename T>
1733 void LottieParserImpl::parseKeyFrame(LOTAnimInfo<T> &obj)
1736 LOTKeyFrame<T> keyframe;
1739 const char * interpolatorKey = nullptr;
1741 bool lastFrame = true;
1742 while (const char *key = NextObjectKey()) {
1743 if (0 == strcmp(key, "i")) {
1744 inTangent = parseInperpolatorPoint();
1745 } else if (0 == strcmp(key, "o")) {
1746 outTangent = parseInperpolatorPoint();
1747 } else if (0 == strcmp(key, "n")) {
1748 if (PeekType() == kStringType) {
1749 interpolatorKey = GetString();
1751 RAPIDJSON_ASSERT(PeekType() == kArrayType);
1753 while (NextArrayValue()) {
1754 RAPIDJSON_ASSERT(PeekType() == kStringType);
1755 interpolatorKey = GetString();
1759 } else if (0 == strcmp(key, "t")) {
1760 keyframe.mStartFrame = GetDouble();
1761 } else if (parseKeyFrameValue(key, keyframe.mValue)) {
1764 } else if (0 == strcmp(key, "h")) {
1769 vDebug << "key frame property skipped = " << key;
1775 if (!obj.mKeyFrames.empty()) {
1776 // update the endFrame value of current keyframe
1777 obj.mKeyFrames.back().mEndFrame = keyframe.mStartFrame;
1781 interpolatorKey = "hold_interpolator";
1782 inTangent = VPointF();
1783 outTangent = VPointF();
1784 keyframe.mValue.mEndValue = keyframe.mValue.mStartValue;
1785 keyframe.mEndFrame = keyframe.mStartFrame;
1789 if (!(lastFrame || interpolatorKey)) {
1790 snprintf(charArray, 20, "%.2f_%.2f_%.2f_%.2f",
1791 inTangent.x(), inTangent.y(), outTangent.x(), outTangent.y());
1792 interpolatorKey = charArray;
1795 // Try to find the interpolator from cache
1796 if (interpolatorKey) {
1797 auto search = compRef->mInterpolatorCache.find(interpolatorKey);
1798 if (search != compRef->mInterpolatorCache.end()) {
1799 keyframe.mInterpolator = search->second;
1801 keyframe.mInterpolator = std::make_shared<VInterpolator>(
1802 VInterpolator(outTangent, inTangent));
1803 compRef->mInterpolatorCache[interpolatorKey] =
1804 keyframe.mInterpolator;
1806 obj.mKeyFrames.push_back(keyframe);
1811 * https://github.com/airbnb/lottie-web/blob/master/docs/json/properties/shapeKeyframed.json
1815 * https://github.com/airbnb/lottie-web/blob/master/docs/json/properties/shape.json
1817 void LottieParserImpl::parseShapeProperty(LOTAnimatable<LottieShapeData> &obj)
1820 while (const char *key = NextObjectKey()) {
1821 if (0 == strcmp(key, "k")) {
1822 if (PeekType() == kArrayType) {
1824 while (NextArrayValue()) {
1825 RAPIDJSON_ASSERT(PeekType() == kObjectType);
1828 std::make_unique<LOTAnimInfo<LottieShapeData>>();
1829 parseKeyFrame(*obj.mAnimInfo.get());
1832 getValue(obj.mValue);
1836 vDebug << "shape property ignored = " << key;
1843 template <typename T>
1844 void LottieParserImpl::parsePropertyHelper(LOTAnimatable<T> &obj)
1846 if (PeekType() == kNumberType) {
1847 /*single value property with no animation*/
1848 getValue(obj.mValue);
1850 RAPIDJSON_ASSERT(PeekType() == kArrayType);
1852 while (NextArrayValue()) {
1853 /* property with keyframe info*/
1854 if (PeekType() == kObjectType) {
1856 obj.mAnimInfo = std::make_unique<LOTAnimInfo<T>>();
1857 parseKeyFrame(*obj.mAnimInfo.get());
1859 /* Read before modifying.
1860 * as there is no way of knowing if the
1861 * value of the array is either array of numbers
1862 * or array of object without entering the array
1863 * thats why this hack is there
1865 RAPIDJSON_ASSERT(PeekType() == kNumberType);
1866 /*multi value property with no animation*/
1867 parseArrayValue(obj.mValue);
1868 /*break here as we already reached end of array*/
1876 * https://github.com/airbnb/lottie-web/tree/master/docs/json/properties
1878 template <typename T>
1879 void LottieParserImpl::parseProperty(LOTAnimatable<T> &obj)
1882 while (const char *key = NextObjectKey()) {
1883 if (0 == strcmp(key, "k")) {
1884 parsePropertyHelper(obj);
1891 #ifdef DEBUG_PRINT_TREE
1893 class LOTDataInspector {
1895 void visit(LOTCompositionData *obj, std::string level)
1899 << "Composition:: a: " << !obj->isStatic()
1900 << ", v: " << obj->mVersion
1901 << ", stFm: " << obj->startFrame()
1902 << ", endFm: " << obj->endFrame()<< "\n";
1904 visit(obj->mRootLayer.get(), level);
1905 level.erase(level.end() - 1, level.end());
1906 vDebug << " } " << level << "Composition End\n";
1908 void visit(LOTLayerData *obj, std::string level)
1912 << layerType(obj->mLayerType)
1913 << ", id:" << obj->mId << " Pid:" << obj->mParentId
1914 << ", a:" << !obj->isStatic()
1915 << ", "<<matteType(obj->mMatteType)
1916 << ", mask:"<<obj->hasMask()
1917 << ", inFm:" << obj->mInFrame
1918 << ", outFm:" << obj->mOutFrame
1919 << ", stFm:" << obj->mStartFrame
1920 << ", ts:" << obj->mTimeStreatch
1921 << ", ao:" << obj->autoOrient()
1923 visitChildren(static_cast<LOTGroupData *>(obj), level);
1926 << layerType(obj->mLayerType).c_str()
1927 << ", id: " << obj->mId << "\n";
1929 void visitChildren(LOTGroupData *obj, std::string level)
1932 for (const auto& child : obj->mChildren) visit(child.get(), level);
1933 if (obj->mTransform)
1934 visit(obj->mTransform.get(), level);
1937 void visit(LOTData *obj, std::string level) {
1938 switch (obj->mType) {
1939 case LOTData::Type::Repeater: {
1940 vDebug << level << "{ Repeater:";
1941 visitChildren(static_cast<LOTGroupData *>(obj), level);
1942 vDebug << level << "} Repeater";
1945 case LOTData::Type::ShapeGroup: {
1946 vDebug << level << "{ ShapeGroup: a:" << !obj->isStatic();
1947 visitChildren(static_cast<LOTGroupData *>(obj), level);
1948 vDebug << level << "} ShapeGroup";
1951 case LOTData::Type::Layer:{
1952 visit(static_cast<LOTLayerData *>(obj), level);
1955 case LOTData::Type::Trim:{
1956 vDebug << level << "{ Trim: a:" << !obj->isStatic() << " }";
1959 case LOTData::Type::Rect:{
1960 vDebug << level << "{ Rect: a:" << !obj->isStatic() << " }";
1963 case LOTData::Type::Ellipse:{
1964 vDebug << level << "{ Ellipse: a:" << !obj->isStatic() << " }";
1967 case LOTData::Type::Shape:{
1968 vDebug << level << "{ Shape: a:" << !obj->isStatic() << " }";
1971 case LOTData::Type::Polystar:{
1972 vDebug << level << "{ Polystar: a:" << !obj->isStatic() << " }";
1975 case LOTData::Type::Transform:{
1976 vDebug << level << "{ Transform: a: " << !obj->isStatic() << " }";
1979 case LOTData::Type::Stroke:{
1980 vDebug << level << "{ Stroke: a:" << !obj->isStatic() << " }";
1983 case LOTData::Type::GStroke:{
1984 vDebug << level << "{ GStroke: a:" << !obj->isStatic() << " }";
1987 case LOTData::Type::Fill:{
1988 vDebug << level << "{ Fill: a:" << !obj->isStatic() << " }";
1991 case LOTData::Type::GFill:{
1992 auto f = static_cast<LOTGFillData *>(obj);
1993 vDebug << level << "{ GFill: a:" << !f->isStatic()
1994 << ", ty:" << f->mGradientType << ", s:" << f->mStartPoint.value(0)
1995 << ", e:" << f->mEndPoint.value(0) << " }";
2003 std::string matteType(MatteType type)
2006 case MatteType::None:
2007 return "Matte::None";
2009 case MatteType::Alpha:
2010 return "Matte::Alpha";
2012 case MatteType::AlphaInv:
2013 return "Matte::AlphaInv";
2015 case MatteType::Luma:
2016 return "Matte::Luma";
2018 case MatteType::LumaInv:
2019 return "Matte::LumaInv";
2022 return "Matte::Unknown";
2026 std::string layerType(LayerType type)
2029 case LayerType::Precomp:
2030 return "Layer::Precomp";
2032 case LayerType::Null:
2033 return "Layer::Null";
2035 case LayerType::Shape:
2036 return "Layer::Shape";
2038 case LayerType::Solid:
2039 return "Layer::Solid";
2041 case LayerType::Image:
2042 return "Layer::Image";
2044 case LayerType::Text:
2045 return "Layer::Text";
2048 return "Layer::Unknown";
2056 LottieParser::~LottieParser()
2061 LottieParser::LottieParser(char *str) : d(new LottieParserImpl(str))
2063 d->parseComposition();
2066 std::shared_ptr<LOTModel> LottieParser::model()
2068 std::shared_ptr<LOTModel> model = std::make_shared<LOTModel>();
2069 model->mRoot = d->composition();
2070 model->mRoot->processRepeaterObjects();
2072 #ifdef DEBUG_PRINT_TREE
2073 LOTDataInspector inspector;
2074 inspector.visit(model->mRoot.get(), "");