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);
796 vWarning << "Layer Attribute Skipped : " << key;
802 // update the static property of layer
803 bool staticFlag = true;
804 for (const auto& child : layer->mChildren) {
805 staticFlag &= child.get()->isStatic();
808 for (const auto& mask : layer->mMasks) {
809 staticFlag &= mask->isStatic();
812 layer->setStatic(staticFlag && layer->mTransform->isStatic() &&
814 layer->mCompRef = compRef;
818 void LottieParserImpl::parseMaskProperty(LOTLayerData *layer)
820 RAPIDJSON_ASSERT(PeekType() == kArrayType);
822 while (NextArrayValue()) {
823 layer->mMasks.push_back(parseMaskObject());
827 std::shared_ptr<LOTMaskData> LottieParserImpl::parseMaskObject()
829 std::shared_ptr<LOTMaskData> sharedMask = std::make_shared<LOTMaskData>();
830 LOTMaskData * obj = sharedMask.get();
832 RAPIDJSON_ASSERT(PeekType() == kObjectType);
834 while (const char *key = NextObjectKey()) {
835 if (0 == strcmp(key, "inv")) {
836 obj->mInv = GetBool();
837 } else if (0 == strcmp(key, "mode")) {
838 const char *str = GetString();
841 obj->mMode = LOTMaskData::Mode::None;
844 obj->mMode = LOTMaskData::Mode::Add;
847 obj->mMode = LOTMaskData::Mode::Substarct;
850 obj->mMode = LOTMaskData::Mode::Intersect;
853 obj->mMode = LOTMaskData::Mode::Difference;
856 obj->mMode = LOTMaskData::Mode::None;
859 } else if (0 == strcmp(key, "pt")) {
860 parseShapeProperty(obj->mShape);
861 } else if (0 == strcmp(key, "o")) {
862 parseProperty(obj->mOpacity);
867 obj->mIsStatic = obj->mShape.isStatic() && obj->mOpacity.isStatic();
871 void LottieParserImpl::parseShapesAttr(LOTLayerData *layer)
873 RAPIDJSON_ASSERT(PeekType() == kArrayType);
875 while (NextArrayValue()) {
880 std::shared_ptr<LOTData> LottieParserImpl::parseObjectTypeAttr()
882 RAPIDJSON_ASSERT(PeekType() == kStringType);
883 const char *type = GetString();
884 if (0 == strcmp(type, "gr")) {
885 return parseGroupObject();
886 } else if (0 == strcmp(type, "rc")) {
887 return parseRectObject();
888 } else if (0 == strcmp(type, "el")) {
889 return parseEllipseObject();
890 } else if (0 == strcmp(type, "tr")) {
891 return parseTransformObject();
892 } else if (0 == strcmp(type, "fl")) {
893 return parseFillObject();
894 } else if (0 == strcmp(type, "st")) {
895 return parseStrokeObject();
896 } else if (0 == strcmp(type, "gf")) {
897 curLayerRef->mHasGradient = true;
898 return parseGFillObject();
899 } else if (0 == strcmp(type, "gs")) {
900 curLayerRef->mHasGradient = true;
901 return parseGStrokeObject();
902 } else if (0 == strcmp(type, "sh")) {
903 return parseShapeObject();
904 } else if (0 == strcmp(type, "sr")) {
905 return parsePolystarObject();
906 } else if (0 == strcmp(type, "tm")) {
907 curLayerRef->mHasPathOperator = true;
908 return parseTrimObject();
909 } else if (0 == strcmp(type, "rp")) {
910 curLayerRef->mHasRepeater = true;
911 return parseReapeaterObject();
914 vDebug << "The Object Type not yet handled = " << type;
920 void LottieParserImpl::parseObject(LOTGroupData *parent)
922 RAPIDJSON_ASSERT(PeekType() == kObjectType);
924 while (const char *key = NextObjectKey()) {
925 if (0 == strcmp(key, "ty")) {
926 auto child = parseObjectTypeAttr();
927 if (child && !child->hidden()) parent->mChildren.push_back(child);
934 std::shared_ptr<LOTData> LottieParserImpl::parseGroupObject()
936 std::shared_ptr<LOTShapeGroupData> sharedGroup =
937 std::make_shared<LOTShapeGroupData>();
939 LOTShapeGroupData *group = sharedGroup.get();
940 while (const char *key = NextObjectKey()) {
941 if (0 == strcmp(key, "it")) {
942 RAPIDJSON_ASSERT(PeekType() == kArrayType);
944 while (NextArrayValue()) {
945 RAPIDJSON_ASSERT(PeekType() == kObjectType);
948 if (group->mChildren.back()->mType == LOTData::Type::Transform) {
949 group->mTransform = std::static_pointer_cast<LOTTransformData>(
950 group->mChildren.back());
951 group->mChildren.pop_back();
957 bool staticFlag = true;
958 for (const auto& child : group->mChildren) {
959 staticFlag &= child.get()->isStatic();
962 group->setStatic(staticFlag && group->mTransform->isStatic());
968 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/rect.json
970 std::shared_ptr<LOTData> LottieParserImpl::parseRectObject()
972 std::shared_ptr<LOTRectData> sharedRect = std::make_shared<LOTRectData>();
973 LOTRectData * obj = sharedRect.get();
975 while (const char *key = NextObjectKey()) {
976 if (0 == strcmp(key, "p")) {
977 parseProperty(obj->mPos);
978 } else if (0 == strcmp(key, "s")) {
979 parseProperty(obj->mSize);
980 } else if (0 == strcmp(key, "r")) {
981 parseProperty(obj->mRound);
982 } else if (0 == strcmp(key, "d")) {
983 obj->mDirection = GetInt();
984 } else if (0 == strcmp(key, "hd")) {
985 obj->mHidden = GetBool();
990 obj->setStatic(obj->mPos.isStatic() && obj->mSize.isStatic() &&
991 obj->mRound.isStatic());
996 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/ellipse.json
998 std::shared_ptr<LOTData> LottieParserImpl::parseEllipseObject()
1000 std::shared_ptr<LOTEllipseData> sharedEllipse =
1001 std::make_shared<LOTEllipseData>();
1002 LOTEllipseData *obj = sharedEllipse.get();
1004 while (const char *key = NextObjectKey()) {
1005 if (0 == strcmp(key, "p")) {
1006 parseProperty(obj->mPos);
1007 } else if (0 == strcmp(key, "s")) {
1008 parseProperty(obj->mSize);
1009 } else if (0 == strcmp(key, "d")) {
1010 obj->mDirection = GetInt();
1011 } else if (0 == strcmp(key, "hd")) {
1012 obj->mHidden = GetBool();
1017 obj->setStatic(obj->mPos.isStatic() && obj->mSize.isStatic());
1018 return sharedEllipse;
1022 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/shape.json
1024 std::shared_ptr<LOTData> LottieParserImpl::parseShapeObject()
1026 std::shared_ptr<LOTShapeData> sharedShape =
1027 std::make_shared<LOTShapeData>();
1028 LOTShapeData *obj = sharedShape.get();
1030 while (const char *key = NextObjectKey()) {
1031 if (0 == strcmp(key, "ks")) {
1032 parseShapeProperty(obj->mShape);
1033 } else if (0 == strcmp(key, "d")) {
1034 obj->mDirection = GetInt();
1035 } else if (0 == strcmp(key, "hd")) {
1036 obj->mHidden = GetBool();
1039 vDebug << "Shape property ignored :" << key;
1044 obj->setStatic(obj->mShape.isStatic());
1050 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/star.json
1052 std::shared_ptr<LOTData> LottieParserImpl::parsePolystarObject()
1054 std::shared_ptr<LOTPolystarData> sharedPolystar =
1055 std::make_shared<LOTPolystarData>();
1056 LOTPolystarData *obj = sharedPolystar.get();
1058 while (const char *key = NextObjectKey()) {
1059 if (0 == strcmp(key, "p")) {
1060 parseProperty(obj->mPos);
1061 } else if (0 == strcmp(key, "pt")) {
1062 parseProperty(obj->mPointCount);
1063 } else if (0 == strcmp(key, "ir")) {
1064 parseProperty(obj->mInnerRadius);
1065 } else if (0 == strcmp(key, "is")) {
1066 parseProperty(obj->mInnerRoundness);
1067 } else if (0 == strcmp(key, "or")) {
1068 parseProperty(obj->mOuterRadius);
1069 } else if (0 == strcmp(key, "os")) {
1070 parseProperty(obj->mOuterRoundness);
1071 } else if (0 == strcmp(key, "r")) {
1072 parseProperty(obj->mRotation);
1073 } else if (0 == strcmp(key, "sy")) {
1074 int starType = GetInt();
1075 if (starType == 1) obj->mType = LOTPolystarData::PolyType::Star;
1076 if (starType == 2) obj->mType = LOTPolystarData::PolyType::Polygon;
1077 } else if (0 == strcmp(key, "d")) {
1078 obj->mDirection = GetInt();
1079 } else if (0 == strcmp(key, "hd")) {
1080 obj->mHidden = GetBool();
1083 vDebug << "Polystar property ignored :" << key;
1089 obj->mPos.isStatic() && obj->mPointCount.isStatic() &&
1090 obj->mInnerRadius.isStatic() && obj->mInnerRoundness.isStatic() &&
1091 obj->mOuterRadius.isStatic() && obj->mOuterRoundness.isStatic() &&
1092 obj->mRotation.isStatic());
1094 return sharedPolystar;
1097 LOTTrimData::TrimType LottieParserImpl::getTrimType()
1099 RAPIDJSON_ASSERT(PeekType() == kNumberType);
1102 return LOTTrimData::TrimType::Simultaneously;
1105 return LOTTrimData::TrimType::Individually;
1108 RAPIDJSON_ASSERT(0);
1114 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/trim.json
1116 std::shared_ptr<LOTData> LottieParserImpl::parseTrimObject()
1118 std::shared_ptr<LOTTrimData> sharedTrim = std::make_shared<LOTTrimData>();
1119 LOTTrimData * obj = sharedTrim.get();
1121 while (const char *key = NextObjectKey()) {
1122 if (0 == strcmp(key, "s")) {
1123 parseProperty(obj->mStart);
1124 } else if (0 == strcmp(key, "e")) {
1125 parseProperty(obj->mEnd);
1126 } else if (0 == strcmp(key, "o")) {
1127 parseProperty(obj->mOffset);
1128 } else if (0 == strcmp(key, "m")) {
1129 obj->mTrimType = getTrimType();
1130 } else if (0 == strcmp(key, "hd")) {
1131 obj->mHidden = GetBool();
1134 vDebug << "Trim property ignored :" << key;
1139 obj->setStatic(obj->mStart.isStatic() && obj->mEnd.isStatic() &&
1140 obj->mOffset.isStatic());
1144 std::shared_ptr<LOTData> LottieParserImpl::parseReapeaterObject()
1146 std::shared_ptr<LOTRepeaterData> sharedRepeater =
1147 std::make_shared<LOTRepeaterData>();
1148 LOTRepeaterData *obj = sharedRepeater.get();
1150 while (const char *key = NextObjectKey()) {
1151 if (0 == strcmp(key, "c")) {
1152 parseProperty(obj->mCopies);
1153 } else if (0 == strcmp(key, "o")) {
1154 parseProperty(obj->mOffset);
1155 } else if (0 == strcmp(key, "tr")) {
1156 obj->mTransform = parseTransformObject();
1157 } else if (0 == strcmp(key, "hd")) {
1158 obj->mHidden = GetBool();
1161 vDebug << "Repeater property ignored :" << key;
1166 obj->setStatic(obj->mCopies.isStatic() && obj->mOffset.isStatic() &&
1167 obj->mTransform->isStatic());
1169 return sharedRepeater;
1173 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/transform.json
1175 std::shared_ptr<LOTTransformData> LottieParserImpl::parseTransformObject()
1177 std::shared_ptr<LOTTransformData> sharedTransform =
1178 std::make_shared<LOTTransformData>();
1179 LOTTransformData *obj = sharedTransform.get();
1181 while (const char *key = NextObjectKey()) {
1182 if (0 == strcmp(key, "a")) {
1183 parseProperty(obj->mAnchor);
1184 } else if (0 == strcmp(key, "p")) {
1186 while (const char *key = NextObjectKey()) {
1187 if (0 == strcmp(key, "k")) {
1188 parsePropertyHelper(obj->mPosition);
1189 } else if (0 == strcmp(key, "s")) {
1190 obj->mSeparate = GetBool();
1191 } else if (obj->mSeparate && (0 == strcmp(key, "x"))) {
1192 parseProperty(obj->mX);
1193 } else if (obj->mSeparate && (0 == strcmp(key, "y"))) {
1194 parseProperty(obj->mY);
1199 } else if (0 == strcmp(key, "r")) {
1200 parseProperty(obj->mRotation);
1201 } else if (0 == strcmp(key, "s")) {
1202 parseProperty(obj->mScale);
1203 } else if (0 == strcmp(key, "sk")) {
1204 parseProperty(obj->mSkew);
1205 } else if (0 == strcmp(key, "sa")) {
1206 parseProperty(obj->mSkewAxis);
1207 } else if (0 == strcmp(key, "o")) {
1208 parseProperty(obj->mOpacity);
1209 } else if (0 == strcmp(key, "hd")) {
1210 obj->mHidden = GetBool();
1215 obj->mStaticMatrix = obj->mAnchor.isStatic() && obj->mPosition.isStatic() &&
1216 obj->mRotation.isStatic() && obj->mScale.isStatic() &&
1217 obj->mSkew.isStatic() && obj->mSkewAxis.isStatic() &&
1218 obj->mX.isStatic() && obj->mY.isStatic();
1220 obj->setStatic(obj->mStaticMatrix && obj->mOpacity.isStatic());
1222 if (obj->mStaticMatrix) obj->cacheMatrix();
1224 return sharedTransform;
1228 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/fill.json
1230 std::shared_ptr<LOTData> LottieParserImpl::parseFillObject()
1232 std::shared_ptr<LOTFillData> sharedFill = std::make_shared<LOTFillData>();
1233 LOTFillData * obj = sharedFill.get();
1235 while (const char *key = NextObjectKey()) {
1236 if (0 == strcmp(key, "c")) {
1237 parseProperty(obj->mColor);
1238 } else if (0 == strcmp(key, "o")) {
1239 parseProperty(obj->mOpacity);
1240 } else if (0 == strcmp(key, "fillEnabled")) {
1241 obj->mEnabled = GetBool();
1242 } else if (0 == strcmp(key, "r")) {
1243 obj->mFillRule = getFillRule();
1244 } else if (0 == strcmp(key, "hd")) {
1245 obj->mHidden = GetBool();
1248 vWarning << "Fill property skipped = " << key;
1253 obj->setStatic(obj->mColor.isStatic() && obj->mOpacity.isStatic());
1259 * https://github.com/airbnb/lottie-web/blob/master/docs/json/helpers/lineCap.json
1261 CapStyle LottieParserImpl::getLineCap()
1263 RAPIDJSON_ASSERT(PeekType() == kNumberType);
1266 return CapStyle::Flat;
1269 return CapStyle::Round;
1272 return CapStyle::Square;
1277 FillRule LottieParserImpl::getFillRule()
1279 RAPIDJSON_ASSERT(PeekType() == kNumberType);
1282 return FillRule::Winding;
1285 return FillRule::EvenOdd;
1288 return FillRule::Winding;
1294 * https://github.com/airbnb/lottie-web/blob/master/docs/json/helpers/lineJoin.json
1296 JoinStyle LottieParserImpl::getLineJoin()
1298 RAPIDJSON_ASSERT(PeekType() == kNumberType);
1301 return JoinStyle::Miter;
1304 return JoinStyle::Round;
1307 return JoinStyle::Bevel;
1313 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/stroke.json
1315 std::shared_ptr<LOTData> LottieParserImpl::parseStrokeObject()
1317 std::shared_ptr<LOTStrokeData> sharedStroke =
1318 std::make_shared<LOTStrokeData>();
1319 LOTStrokeData *obj = sharedStroke.get();
1321 while (const char *key = NextObjectKey()) {
1322 if (0 == strcmp(key, "c")) {
1323 parseProperty(obj->mColor);
1324 } else if (0 == strcmp(key, "o")) {
1325 parseProperty(obj->mOpacity);
1326 } else if (0 == strcmp(key, "w")) {
1327 parseProperty(obj->mWidth);
1328 } else if (0 == strcmp(key, "fillEnabled")) {
1329 obj->mEnabled = GetBool();
1330 } else if (0 == strcmp(key, "lc")) {
1331 obj->mCapStyle = getLineCap();
1332 } else if (0 == strcmp(key, "lj")) {
1333 obj->mJoinStyle = getLineJoin();
1334 } else if (0 == strcmp(key, "ml")) {
1335 RAPIDJSON_ASSERT(PeekType() == kNumberType);
1336 obj->mMeterLimit = GetDouble();
1337 } else if (0 == strcmp(key, "d")) {
1338 parseDashProperty(obj->mDash);
1339 } else if (0 == strcmp(key, "hd")) {
1340 obj->mHidden = GetBool();
1343 vWarning << "Stroke property skipped = " << key;
1348 obj->setStatic(obj->mColor.isStatic() && obj->mOpacity.isStatic() &&
1349 obj->mWidth.isStatic() && obj->mDash.mStatic);
1350 return sharedStroke;
1353 void LottieParserImpl::parseGradientProperty(LOTGradient *obj, const char *key)
1355 if (0 == strcmp(key, "t")) {
1356 RAPIDJSON_ASSERT(PeekType() == kNumberType);
1357 obj->mGradientType = GetInt();
1358 } else if (0 == strcmp(key, "o")) {
1359 parseProperty(obj->mOpacity);
1360 } else if (0 == strcmp(key, "s")) {
1361 parseProperty(obj->mStartPoint);
1362 } else if (0 == strcmp(key, "e")) {
1363 parseProperty(obj->mEndPoint);
1364 } else if (0 == strcmp(key, "h")) {
1365 parseProperty(obj->mHighlightLength);
1366 } else if (0 == strcmp(key, "a")) {
1367 parseProperty(obj->mHighlightAngle);
1368 } else if (0 == strcmp(key, "g")) {
1370 while (const char *key = NextObjectKey()) {
1371 if (0 == strcmp(key, "k")) {
1372 parseProperty(obj->mGradient);
1373 } else if (0 == strcmp(key, "p")) {
1374 obj->mColorPoints = GetInt();
1379 } else if (0 == strcmp(key, "hd")) {
1380 obj->mHidden = GetBool();
1383 vWarning << "Gradient property skipped = " << key;
1388 obj->mOpacity.isStatic() && obj->mStartPoint.isStatic() &&
1389 obj->mEndPoint.isStatic() && obj->mHighlightAngle.isStatic() &&
1390 obj->mHighlightLength.isStatic() && obj->mGradient.isStatic());
1394 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/gfill.json
1396 std::shared_ptr<LOTData> LottieParserImpl::parseGFillObject()
1398 std::shared_ptr<LOTGFillData> sharedGFill =
1399 std::make_shared<LOTGFillData>();
1400 LOTGFillData *obj = sharedGFill.get();
1402 while (const char *key = NextObjectKey()) {
1403 if (0 == strcmp(key, "r")) {
1404 obj->mFillRule = getFillRule();
1406 parseGradientProperty(obj, key);
1412 void LottieParserImpl::parseDashProperty(LOTDashProperty &dash)
1414 dash.mDashCount = 0;
1415 dash.mStatic = true;
1416 RAPIDJSON_ASSERT(PeekType() == kArrayType);
1418 while (NextArrayValue()) {
1419 RAPIDJSON_ASSERT(PeekType() == kObjectType);
1421 while (const char *key = NextObjectKey()) {
1422 if (0 == strcmp(key, "v")) {
1423 parseProperty(dash.mDashArray[dash.mDashCount++]);
1430 // update the staic proprty
1431 for (int i = 0; i < dash.mDashCount; i++) {
1432 if (!dash.mDashArray[i].isStatic()) {
1433 dash.mStatic = false;
1440 * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/gstroke.json
1442 std::shared_ptr<LOTData> LottieParserImpl::parseGStrokeObject()
1444 std::shared_ptr<LOTGStrokeData> sharedGStroke =
1445 std::make_shared<LOTGStrokeData>();
1446 LOTGStrokeData *obj = sharedGStroke.get();
1448 while (const char *key = NextObjectKey()) {
1449 if (0 == strcmp(key, "w")) {
1450 parseProperty(obj->mWidth);
1451 } else if (0 == strcmp(key, "lc")) {
1452 obj->mCapStyle = getLineCap();
1453 } else if (0 == strcmp(key, "lj")) {
1454 obj->mJoinStyle = getLineJoin();
1455 } else if (0 == strcmp(key, "ml")) {
1456 RAPIDJSON_ASSERT(PeekType() == kNumberType);
1457 obj->mMeterLimit = GetDouble();
1458 } else if (0 == strcmp(key, "d")) {
1459 parseDashProperty(obj->mDash);
1461 parseGradientProperty(obj, key);
1465 obj->setStatic(obj->isStatic() && obj->mWidth.isStatic() &&
1466 obj->mDash.mStatic);
1467 return sharedGStroke;
1470 void LottieParserImpl::parseArrayValue(LottieColor &color)
1474 while (NextArrayValue()) {
1475 val[i++] = GetDouble();
1483 void LottieParserImpl::parseArrayValue(VPointF &pt)
1487 while (NextArrayValue()) {
1488 val[i++] = GetDouble();
1494 void LottieParserImpl::parseArrayValue(float &val)
1496 RAPIDJSON_ASSERT(0);
1500 void LottieParserImpl::parseArrayValue(int &val)
1502 RAPIDJSON_ASSERT(0);
1506 void LottieParserImpl::parseArrayValue(std::vector<VPointF> &v)
1508 RAPIDJSON_ASSERT(PeekType() == kArrayType);
1510 while (NextArrayValue()) {
1511 RAPIDJSON_ASSERT(PeekType() == kArrayType);
1514 parseArrayValue(pt);
1519 void LottieParserImpl::getValue(VPointF &pt)
1523 RAPIDJSON_ASSERT(PeekType() == kArrayType);
1525 while (NextArrayValue()) {
1526 val[i++] = GetDouble();
1532 void LottieParserImpl::getValue(float &val)
1534 if (PeekType() == kArrayType) {
1536 while (NextArrayValue()) {
1539 } else if (PeekType() == kNumberType) {
1542 RAPIDJSON_ASSERT(0);
1546 void LottieParserImpl::getValue(LottieColor &color)
1550 RAPIDJSON_ASSERT(PeekType() == kArrayType);
1552 while (NextArrayValue()) {
1553 val[i++] = GetDouble();
1560 void LottieParserImpl::parseArrayValue(LottieGradient &grad)
1562 while (NextArrayValue()) {
1563 grad.mGradient.push_back(GetDouble());
1567 void LottieParserImpl::getValue(LottieGradient &grad)
1569 RAPIDJSON_ASSERT(PeekType() == kArrayType);
1571 while (NextArrayValue()) {
1572 grad.mGradient.push_back(GetDouble());
1576 void LottieParserImpl::getValue(int &val)
1578 if (PeekType() == kArrayType) {
1580 while (NextArrayValue()) {
1583 } else if (PeekType() == kNumberType) {
1586 RAPIDJSON_ASSERT(0);
1590 void LottieParserImpl::getValue(LottieShapeData &obj)
1592 std::vector<VPointF> inPoint; /* "i" */
1593 std::vector<VPointF> outPoint; /* "o" */
1594 std::vector<VPointF> vertices; /* "v" */
1595 std::vector<VPointF> points;
1596 bool closed = false;
1599 * The shape object could be wrapped by a array
1600 * if its part of the keyframe object
1602 bool arrayWrapper = (PeekType() == kArrayType);
1603 if (arrayWrapper) EnterArray();
1605 RAPIDJSON_ASSERT(PeekType() == kObjectType);
1607 while (const char *key = NextObjectKey()) {
1608 if (0 == strcmp(key, "i")) {
1609 parseArrayValue(inPoint);
1610 } else if (0 == strcmp(key, "o")) {
1611 parseArrayValue(outPoint);
1612 } else if (0 == strcmp(key, "v")) {
1613 parseArrayValue(vertices);
1614 } else if (0 == strcmp(key, "c")) {
1617 RAPIDJSON_ASSERT(0);
1621 // exit properly from the array
1622 if (arrayWrapper) NextArrayValue();
1624 // shape data could be empty.
1625 if (inPoint.empty() || outPoint.empty() || vertices.empty()) return;
1628 * Convert the AE shape format to
1629 * list of bazier curves
1630 * The final structure will be Move +size*Cubic + Cubic (if the path is
1633 if (inPoint.size() != outPoint.size() ||
1634 inPoint.size() != vertices.size()) {
1635 vCritical << "The Shape data are corrupted";
1636 points = std::vector<VPointF>();
1638 int size = vertices.size();
1639 points.reserve(3 * size + 4);
1640 points.push_back(vertices[0]);
1641 for (int i = 1; i < size; i++) {
1642 points.push_back(vertices[i - 1] +
1643 outPoint[i - 1]); // CP1 = start + outTangent
1644 points.push_back(vertices[i] +
1645 inPoint[i]); // CP2 = end + inTangent
1646 points.push_back(vertices[i]); // end point
1650 points.push_back(vertices[size - 1] +
1651 outPoint[size - 1]); // CP1 = start + outTangent
1652 points.push_back(vertices[0] +
1653 inPoint[0]); // CP2 = end + inTangent
1654 points.push_back(vertices[0]); // end point
1657 obj.mPoints = std::move(points);
1658 obj.mClosed = closed;
1661 VPointF LottieParserImpl::parseInperpolatorPoint()
1664 RAPIDJSON_ASSERT(PeekType() == kObjectType);
1666 while (const char *key = NextObjectKey()) {
1667 if (0 == strcmp(key, "x")) {
1668 if (PeekType() == kNumberType) {
1669 cp.setX(GetDouble());
1671 RAPIDJSON_ASSERT(PeekType() == kArrayType);
1673 while (NextArrayValue()) {
1674 cp.setX(GetDouble());
1678 if (0 == strcmp(key, "y")) {
1679 if (PeekType() == kNumberType) {
1680 cp.setY(GetDouble());
1682 RAPIDJSON_ASSERT(PeekType() == kArrayType);
1684 while (NextArrayValue()) {
1685 cp.setY(GetDouble());
1693 template <typename T>
1694 bool LottieParserImpl::parseKeyFrameValue(const char * key,
1695 LOTKeyFrameValue<T> &value)
1697 if (0 == strcmp(key, "s")) {
1698 getValue(value.mStartValue);
1699 } else if (0 == strcmp(key, "e")) {
1700 getValue(value.mEndValue);
1708 bool LottieParserImpl::parseKeyFrameValue(const char * key,
1709 LOTKeyFrameValue<VPointF> &value)
1711 if (0 == strcmp(key, "s")) {
1712 getValue(value.mStartValue);
1713 } else if (0 == strcmp(key, "e")) {
1714 getValue(value.mEndValue);
1715 } else if (0 == strcmp(key, "ti")) {
1716 value.mPathKeyFrame = true;
1717 getValue(value.mInTangent);
1718 } else if (0 == strcmp(key, "to")) {
1719 value.mPathKeyFrame = true;
1720 getValue(value.mOutTangent);
1728 * https://github.com/airbnb/lottie-web/blob/master/docs/json/properties/multiDimensionalKeyframed.json
1730 template <typename T>
1731 void LottieParserImpl::parseKeyFrame(LOTAnimInfo<T> &obj)
1734 LOTKeyFrame<T> keyframe;
1737 const char * interpolatorKey = nullptr;
1739 bool lastFrame = true;
1740 while (const char *key = NextObjectKey()) {
1741 if (0 == strcmp(key, "i")) {
1742 inTangent = parseInperpolatorPoint();
1743 } else if (0 == strcmp(key, "o")) {
1744 outTangent = parseInperpolatorPoint();
1745 } else if (0 == strcmp(key, "n")) {
1746 if (PeekType() == kStringType) {
1747 interpolatorKey = GetString();
1749 RAPIDJSON_ASSERT(PeekType() == kArrayType);
1751 while (NextArrayValue()) {
1752 RAPIDJSON_ASSERT(PeekType() == kStringType);
1753 interpolatorKey = GetString();
1757 } else if (0 == strcmp(key, "t")) {
1758 keyframe.mStartFrame = GetDouble();
1759 } else if (parseKeyFrameValue(key, keyframe.mValue)) {
1762 } else if (0 == strcmp(key, "h")) {
1767 vDebug << "key frame property skipped = " << key;
1773 if (!obj.mKeyFrames.empty()) {
1774 // update the endFrame value of current keyframe
1775 obj.mKeyFrames.back().mEndFrame = keyframe.mStartFrame;
1779 interpolatorKey = "hold_interpolator";
1780 inTangent = VPointF();
1781 outTangent = VPointF();
1782 keyframe.mValue.mEndValue = keyframe.mValue.mStartValue;
1783 keyframe.mEndFrame = keyframe.mStartFrame;
1787 if (!(lastFrame || interpolatorKey)) {
1788 snprintf(charArray, 20, "%.2f_%.2f_%.2f_%.2f",
1789 inTangent.x(), inTangent.y(), outTangent.x(), outTangent.y());
1790 interpolatorKey = charArray;
1793 // Try to find the interpolator from cache
1794 if (interpolatorKey) {
1795 auto search = compRef->mInterpolatorCache.find(interpolatorKey);
1796 if (search != compRef->mInterpolatorCache.end()) {
1797 keyframe.mInterpolator = search->second;
1799 keyframe.mInterpolator = std::make_shared<VInterpolator>(
1800 VInterpolator(outTangent, inTangent));
1801 compRef->mInterpolatorCache[interpolatorKey] =
1802 keyframe.mInterpolator;
1804 obj.mKeyFrames.push_back(keyframe);
1809 * https://github.com/airbnb/lottie-web/blob/master/docs/json/properties/shapeKeyframed.json
1813 * https://github.com/airbnb/lottie-web/blob/master/docs/json/properties/shape.json
1815 void LottieParserImpl::parseShapeProperty(LOTAnimatable<LottieShapeData> &obj)
1818 while (const char *key = NextObjectKey()) {
1819 if (0 == strcmp(key, "k")) {
1820 if (PeekType() == kArrayType) {
1822 while (NextArrayValue()) {
1823 RAPIDJSON_ASSERT(PeekType() == kObjectType);
1826 std::make_unique<LOTAnimInfo<LottieShapeData>>();
1827 parseKeyFrame(*obj.mAnimInfo.get());
1830 getValue(obj.mValue);
1834 vDebug << "shape property ignored = " << key;
1841 template <typename T>
1842 void LottieParserImpl::parsePropertyHelper(LOTAnimatable<T> &obj)
1844 if (PeekType() == kNumberType) {
1845 /*single value property with no animation*/
1846 getValue(obj.mValue);
1848 RAPIDJSON_ASSERT(PeekType() == kArrayType);
1850 while (NextArrayValue()) {
1851 /* property with keyframe info*/
1852 if (PeekType() == kObjectType) {
1854 obj.mAnimInfo = std::make_unique<LOTAnimInfo<T>>();
1855 parseKeyFrame(*obj.mAnimInfo.get());
1857 /* Read before modifying.
1858 * as there is no way of knowing if the
1859 * value of the array is either array of numbers
1860 * or array of object without entering the array
1861 * thats why this hack is there
1863 RAPIDJSON_ASSERT(PeekType() == kNumberType);
1864 /*multi value property with no animation*/
1865 parseArrayValue(obj.mValue);
1866 /*break here as we already reached end of array*/
1874 * https://github.com/airbnb/lottie-web/tree/master/docs/json/properties
1876 template <typename T>
1877 void LottieParserImpl::parseProperty(LOTAnimatable<T> &obj)
1880 while (const char *key = NextObjectKey()) {
1881 if (0 == strcmp(key, "k")) {
1882 parsePropertyHelper(obj);
1889 #ifdef DEBUG_PRINT_TREE
1891 class LOTDataInspector {
1893 void visit(LOTCompositionData *obj, std::string level)
1897 << "Composition:: a: " << !obj->isStatic()
1898 << ", v: " << obj->mVersion
1899 << ", stFm: " << obj->startFrame()
1900 << ", endFm: " << obj->endFrame()<< "\n";
1902 visit(obj->mRootLayer.get(), level);
1903 level.erase(level.end() - 1, level.end());
1904 vDebug << " } " << level << "Composition End\n";
1906 void visit(LOTLayerData *obj, std::string level)
1910 << layerType(obj->mLayerType)
1911 << ", id:" << obj->mId << " Pid:" << obj->mParentId
1912 << ", a:" << !obj->isStatic()
1913 << ", "<<matteType(obj->mMatteType)
1914 << ", mask:"<<obj->hasMask()
1915 << ", inFm:" << obj->mInFrame
1916 << ", outFm:" << obj->mOutFrame
1917 << ", stFm:" << obj->mStartFrame
1918 << ", ts:" << obj->mTimeStreatch
1920 visitChildren(static_cast<LOTGroupData *>(obj), level);
1923 << layerType(obj->mLayerType).c_str()
1924 << ", id: " << obj->mId << "\n";
1926 void visitChildren(LOTGroupData *obj, std::string level)
1929 for (const auto& child : obj->mChildren) visit(child.get(), level);
1930 if (obj->mTransform)
1931 visit(obj->mTransform.get(), level);
1934 void visit(LOTData *obj, std::string level) {
1935 switch (obj->mType) {
1936 case LOTData::Type::Repeater: {
1937 vDebug << level << "{ Repeater:";
1938 visitChildren(static_cast<LOTGroupData *>(obj), level);
1939 vDebug << level << "} Repeater";
1942 case LOTData::Type::ShapeGroup: {
1943 vDebug << level << "{ ShapeGroup: a:" << !obj->isStatic();
1944 visitChildren(static_cast<LOTGroupData *>(obj), level);
1945 vDebug << level << "} ShapeGroup";
1948 case LOTData::Type::Layer:{
1949 visit(static_cast<LOTLayerData *>(obj), level);
1952 case LOTData::Type::Trim:{
1953 vDebug << level << "{ Trim: a:" << !obj->isStatic() << " }";
1956 case LOTData::Type::Rect:{
1957 vDebug << level << "{ Rect: a:" << !obj->isStatic() << " }";
1960 case LOTData::Type::Ellipse:{
1961 vDebug << level << "{ Ellipse: a:" << !obj->isStatic() << " }";
1964 case LOTData::Type::Shape:{
1965 vDebug << level << "{ Shape: a:" << !obj->isStatic() << " }";
1968 case LOTData::Type::Polystar:{
1969 vDebug << level << "{ Polystar: a:" << !obj->isStatic() << " }";
1972 case LOTData::Type::Transform:{
1973 vDebug << level << "{ Transform: a: " << !obj->isStatic() << " }";
1976 case LOTData::Type::Stroke:{
1977 vDebug << level << "{ Stroke: a:" << !obj->isStatic() << " }";
1980 case LOTData::Type::GStroke:{
1981 vDebug << level << "{ GStroke: a:" << !obj->isStatic() << " }";
1984 case LOTData::Type::Fill:{
1985 vDebug << level << "{ Fill: a:" << !obj->isStatic() << " }";
1988 case LOTData::Type::GFill:{
1989 auto f = static_cast<LOTGFillData *>(obj);
1990 vDebug << level << "{ GFill: a:" << !f->isStatic()
1991 << ", ty:" << f->mGradientType << ", s:" << f->mStartPoint.value(0)
1992 << ", e:" << f->mEndPoint.value(0) << " }";
2000 std::string matteType(MatteType type)
2003 case MatteType::None:
2004 return "Matte::None";
2006 case MatteType::Alpha:
2007 return "Matte::Alpha";
2009 case MatteType::AlphaInv:
2010 return "Matte::AlphaInv";
2012 case MatteType::Luma:
2013 return "Matte::Luma";
2015 case MatteType::LumaInv:
2016 return "Matte::LumaInv";
2019 return "Matte::Unknown";
2023 std::string layerType(LayerType type)
2026 case LayerType::Precomp:
2027 return "Layer::Precomp";
2029 case LayerType::Null:
2030 return "Layer::Null";
2032 case LayerType::Shape:
2033 return "Layer::Shape";
2035 case LayerType::Solid:
2036 return "Layer::Solid";
2038 case LayerType::Image:
2039 return "Layer::Image";
2041 case LayerType::Text:
2042 return "Layer::Text";
2045 return "Layer::Unknown";
2053 LottieParser::~LottieParser()
2058 LottieParser::LottieParser(char *str) : d(new LottieParserImpl(str))
2060 d->parseComposition();
2063 std::shared_ptr<LOTModel> LottieParser::model()
2065 std::shared_ptr<LOTModel> model = std::make_shared<LOTModel>();
2066 model->mRoot = d->composition();
2067 model->mRoot->processRepeaterObjects();
2069 #ifdef DEBUG_PRINT_TREE
2070 LOTDataInspector inspector;
2071 inspector.visit(model->mRoot.get(), "");