summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/Benau/go_rlottie/lottie_lottieparser.cpp
diff options
context:
space:
mode:
authorBenau <Benau@users.noreply.github.com>2021-08-25 04:32:50 +0800
committerGitHub <noreply@github.com>2021-08-24 22:32:50 +0200
commit53cafa9f3d0c8be33821fc7338b1da97e91d9cc6 (patch)
tree964a225219099a1a1c282e27913767da588191b4 /vendor/github.com/Benau/go_rlottie/lottie_lottieparser.cpp
parentd4195deb3a6305c49c50ff30e8af978c7f1bdd92 (diff)
downloadmatterbridge-msglm-53cafa9f3d0c8be33821fc7338b1da97e91d9cc6.tar.gz
matterbridge-msglm-53cafa9f3d0c8be33821fc7338b1da97e91d9cc6.tar.bz2
matterbridge-msglm-53cafa9f3d0c8be33821fc7338b1da97e91d9cc6.zip
Convert .tgs with go libraries (and cgo) (telegram) (#1569)
This commit adds support for go/cgo tgs conversion when building with the -tags `cgo` The default binaries are still "pure" go and uses the old way of converting. * Move lottie_convert.py conversion code to its own file * Add optional libtgsconverter * Update vendor * Apply suggestions from code review * Update bridge/helper/libtgsconverter.go Co-authored-by: Wim <wim@42.be>
Diffstat (limited to 'vendor/github.com/Benau/go_rlottie/lottie_lottieparser.cpp')
-rw-r--r--vendor/github.com/Benau/go_rlottie/lottie_lottieparser.cpp2390
1 files changed, 2390 insertions, 0 deletions
diff --git a/vendor/github.com/Benau/go_rlottie/lottie_lottieparser.cpp b/vendor/github.com/Benau/go_rlottie/lottie_lottieparser.cpp
new file mode 100644
index 00000000..91839d41
--- /dev/null
+++ b/vendor/github.com/Benau/go_rlottie/lottie_lottieparser.cpp
@@ -0,0 +1,2390 @@
+/*
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+//#define DEBUG_PARSER
+
+// This parser implements JSON token-by-token parsing with an API that is
+// more direct; we don't have to create handler object and
+// callbacks. Instead, we retrieve values from the JSON stream by calling
+// GetInt(), GetDouble(), GetString() and GetBool(), traverse into structures
+// by calling EnterObject() and EnterArray(), and skip over unwanted data by
+// calling SkipValue(). As we know the lottie file structure this way will be
+// the efficient way of parsing the file.
+//
+// If you aren't sure of what's next in the JSON data, you can use PeekType()
+// and PeekValue() to look ahead to the next object before reading it.
+//
+// If you call the wrong retrieval method--e.g. GetInt when the next JSON token
+// is not an int, EnterObject or EnterArray when there isn't actually an object
+// or array to read--the stream parsing will end immediately and no more data
+// will be delivered.
+//
+// After calling EnterObject, you retrieve keys via NextObjectKey() and values
+// via the normal getters. When NextObjectKey() returns null, you have exited
+// the object, or you can call SkipObject() to skip to the end of the object
+// immediately. If you fetch the entire object (i.e. NextObjectKey() returned
+// null), you should not call SkipObject().
+//
+// After calling EnterArray(), you must alternate between calling
+// NextArrayValue() to see if the array has more data, and then retrieving
+// values via the normal getters. You can call SkipArray() to skip to the end of
+// the array immediately. If you fetch the entire array (i.e. NextArrayValue()
+// returned null), you should not call SkipArray().
+//
+// This parser uses in-situ strings, so the JSON buffer will be altered during
+// the parse.
+
+#include <array>
+
+#include "lottie_lottiemodel.h"
+#include "lottie_rapidjson_document.h"
+
+RAPIDJSON_DIAG_PUSH
+#ifdef __GNUC__
+RAPIDJSON_DIAG_OFF(effc++)
+#endif
+
+using namespace rapidjson;
+
+using namespace rlottie::internal;
+
+class LookaheadParserHandler {
+public:
+ bool Null()
+ {
+ st_ = kHasNull;
+ v_.SetNull();
+ return true;
+ }
+ bool Bool(bool b)
+ {
+ st_ = kHasBool;
+ v_.SetBool(b);
+ return true;
+ }
+ bool Int(int i)
+ {
+ st_ = kHasNumber;
+ v_.SetInt(i);
+ return true;
+ }
+ bool Uint(unsigned u)
+ {
+ st_ = kHasNumber;
+ v_.SetUint(u);
+ return true;
+ }
+ bool Int64(int64_t i)
+ {
+ st_ = kHasNumber;
+ v_.SetInt64(i);
+ return true;
+ }
+ bool Uint64(int64_t u)
+ {
+ st_ = kHasNumber;
+ v_.SetUint64(u);
+ return true;
+ }
+ bool Double(double d)
+ {
+ st_ = kHasNumber;
+ v_.SetDouble(d);
+ return true;
+ }
+ bool RawNumber(const char *, SizeType, bool) { return false; }
+ bool String(const char *str, SizeType length, bool)
+ {
+ st_ = kHasString;
+ v_.SetString(str, length);
+ return true;
+ }
+ bool StartObject()
+ {
+ st_ = kEnteringObject;
+ return true;
+ }
+ bool Key(const char *str, SizeType length, bool)
+ {
+ st_ = kHasKey;
+ v_.SetString(str, length);
+ return true;
+ }
+ bool EndObject(SizeType)
+ {
+ st_ = kExitingObject;
+ return true;
+ }
+ bool StartArray()
+ {
+ st_ = kEnteringArray;
+ return true;
+ }
+ bool EndArray(SizeType)
+ {
+ st_ = kExitingArray;
+ return true;
+ }
+
+ void Error()
+ {
+ st_ = kError;
+ }
+protected:
+ explicit LookaheadParserHandler(char *str);
+
+protected:
+ enum LookaheadParsingState {
+ kInit,
+ kError,
+ kHasNull,
+ kHasBool,
+ kHasNumber,
+ kHasString,
+ kHasKey,
+ kEnteringObject,
+ kExitingObject,
+ kEnteringArray,
+ kExitingArray
+ };
+
+ Value v_;
+ LookaheadParsingState st_;
+ Reader r_;
+ InsituStringStream ss_;
+
+ static const int parseFlags = kParseDefaultFlags | kParseInsituFlag;
+};
+
+class LottieParserImpl : public LookaheadParserHandler {
+public:
+ LottieParserImpl(char *str, std::string dir_path, model::ColorFilter filter)
+ : LookaheadParserHandler(str),
+ mColorFilter(std::move(filter)),
+ mDirPath(std::move(dir_path))
+ {
+ }
+ bool VerifyType();
+ bool ParseNext();
+
+public:
+ VArenaAlloc &allocator() { return compRef->mArenaAlloc; }
+ bool EnterObject();
+ bool EnterArray();
+ const char * NextObjectKey();
+ bool NextArrayValue();
+ int GetInt();
+ double GetDouble();
+ const char * GetString();
+ std::string GetStringObject();
+ bool GetBool();
+ void GetNull();
+
+ void SkipObject();
+ void SkipArray();
+ void SkipValue();
+ Value *PeekValue();
+ int PeekType() const;
+ bool IsValid() { return st_ != kError; }
+
+ void Skip(const char *key);
+ model::BlendMode getBlendMode();
+ CapStyle getLineCap();
+ JoinStyle getLineJoin();
+ FillRule getFillRule();
+ model::Trim::TrimType getTrimType();
+ model::MatteType getMatteType();
+ model::Layer::Type getLayerType();
+
+ std::shared_ptr<model::Composition> composition() const
+ {
+ return mComposition;
+ }
+ void parseComposition();
+ void parseMarkers();
+ void parseMarker();
+ void parseAssets(model::Composition *comp);
+ model::Asset * parseAsset();
+ void parseLayers(model::Composition *comp);
+ model::Layer * parseLayer();
+ void parseMaskProperty(model::Layer *layer);
+ void parseShapesAttr(model::Layer *layer);
+ void parseObject(model::Group *parent);
+ model::Mask * parseMaskObject();
+ model::Object * parseObjectTypeAttr();
+ model::Object * parseGroupObject();
+ model::Rect * parseRectObject();
+ model::RoundedCorner * parseRoundedCorner();
+ void updateRoundedCorner(model::Group *parent, model::RoundedCorner *rc);
+
+ model::Ellipse * parseEllipseObject();
+ model::Path * parseShapeObject();
+ model::Polystar *parsePolystarObject();
+
+ model::Transform * parseTransformObject(bool ddd = false);
+ model::Fill * parseFillObject();
+ model::GradientFill * parseGFillObject();
+ model::Stroke * parseStrokeObject();
+ model::GradientStroke *parseGStrokeObject();
+ model::Trim * parseTrimObject();
+ model::Repeater * parseReapeaterObject();
+
+ void parseGradientProperty(model::Gradient *gradient, const char *key);
+
+ VPointF parseInperpolatorPoint();
+
+ void getValue(VPointF &pt);
+ void getValue(float &fval);
+ void getValue(model::Color &color);
+ void getValue(int &ival);
+ void getValue(model::PathData &shape);
+ void getValue(model::Gradient::Data &gradient);
+ void getValue(std::vector<VPointF> &v);
+ void getValue(model::Repeater::Transform &);
+
+ template <typename T, typename Tag>
+ bool parseKeyFrameValue(const char *, model::Value<T, Tag> &)
+ {
+ return false;
+ }
+
+ template <typename T>
+ bool parseKeyFrameValue(const char * key,
+ model::Value<T, model::Position> &value);
+ template <typename T, typename Tag>
+ void parseKeyFrame(model::KeyFrames<T, Tag> &obj);
+ template <typename T>
+ void parseProperty(model::Property<T> &obj);
+ template <typename T, typename Tag>
+ void parsePropertyHelper(model::Property<T, Tag> &obj);
+
+ void parseShapeProperty(model::Property<model::PathData> &obj);
+ void parseDashProperty(model::Dash &dash);
+
+ VInterpolator *interpolator(VPointF, VPointF, std::string);
+
+ model::Color toColor(const char *str);
+
+ void resolveLayerRefs();
+ void parsePathInfo();
+
+private:
+ model::ColorFilter mColorFilter;
+ struct {
+ std::vector<VPointF> mInPoint; /* "i" */
+ std::vector<VPointF> mOutPoint; /* "o" */
+ std::vector<VPointF> mVertices; /* "v" */
+ std::vector<VPointF> mResult;
+ bool mClosed{false};
+
+ void convert()
+ {
+ // shape data could be empty.
+ if (mInPoint.empty() || mOutPoint.empty() || mVertices.empty()) {
+ mResult.clear();
+ return;
+ }
+
+ /*
+ * Convert the AE shape format to
+ * list of bazier curves
+ * The final structure will be Move +size*Cubic + Cubic (if the path
+ * is closed one)
+ */
+ if (mInPoint.size() != mOutPoint.size() ||
+ mInPoint.size() != mVertices.size()) {
+ mResult.clear();
+ } else {
+ auto size = mVertices.size();
+ mResult.push_back(mVertices[0]);
+ for (size_t i = 1; i < size; i++) {
+ mResult.push_back(
+ mVertices[i - 1] +
+ mOutPoint[i - 1]); // CP1 = start + outTangent
+ mResult.push_back(mVertices[i] +
+ mInPoint[i]); // CP2 = end + inTangent
+ mResult.push_back(mVertices[i]); // end point
+ }
+
+ if (mClosed) {
+ mResult.push_back(
+ mVertices[size - 1] +
+ mOutPoint[size - 1]); // CP1 = start + outTangent
+ mResult.push_back(mVertices[0] +
+ mInPoint[0]); // CP2 = end + inTangent
+ mResult.push_back(mVertices[0]); // end point
+ }
+ }
+ }
+ void reset()
+ {
+ mInPoint.clear();
+ mOutPoint.clear();
+ mVertices.clear();
+ mResult.clear();
+ mClosed = false;
+ }
+ void updatePath(VPath &out)
+ {
+ if (mResult.empty()) return;
+
+ auto size = mResult.size();
+ auto points = mResult.data();
+ /* reserve exact memory requirement at once
+ * ptSize = size + 1(size + close)
+ * elmSize = size/3 cubic + 1 move + 1 close
+ */
+ out.reserve(size + 1, size / 3 + 2);
+ out.moveTo(points[0]);
+ for (size_t i = 1; i < size; i += 3) {
+ out.cubicTo(points[i], points[i + 1], points[i + 2]);
+ }
+ if (mClosed) out.close();
+ }
+ } mPathInfo;
+
+protected:
+ std::unordered_map<std::string, VInterpolator *> mInterpolatorCache;
+ std::shared_ptr<model::Composition> mComposition;
+ model::Composition * compRef{nullptr};
+ model::Layer * curLayerRef{nullptr};
+ std::vector<model::Layer *> mLayersToUpdate;
+ std::string mDirPath;
+ void SkipOut(int depth);
+};
+
+LookaheadParserHandler::LookaheadParserHandler(char *str)
+ : v_(), st_(kInit), ss_(str)
+{
+ r_.IterativeParseInit();
+}
+
+bool LottieParserImpl::VerifyType()
+{
+ /* Verify the media type is lottie json.
+ Could add more strict check. */
+ return ParseNext();
+}
+
+bool LottieParserImpl::ParseNext()
+{
+ if (r_.HasParseError()) {
+ st_ = kError;
+ return false;
+ }
+
+ if (!r_.IterativeParseNext<parseFlags>(ss_, *this)) {
+ vCritical << "Lottie file parsing error";
+ st_ = kError;
+ return false;
+ }
+ return true;
+}
+
+bool LottieParserImpl::EnterObject()
+{
+ if (st_ != kEnteringObject) {
+ st_ = kError;
+ return false;
+ }
+
+ ParseNext();
+ return true;
+}
+
+bool LottieParserImpl::EnterArray()
+{
+ if (st_ != kEnteringArray) {
+ st_ = kError;
+ return false;
+ }
+
+ ParseNext();
+ return true;
+}
+
+const char *LottieParserImpl::NextObjectKey()
+{
+ if (st_ == kHasKey) {
+ const char *result = v_.GetString();
+ ParseNext();
+ return result;
+ }
+
+ /* SPECIAL CASE
+ * The parser works with a prdefined rule that it will be only
+ * while (NextObjectKey()) for each object but in case of our nested group
+ * object we can call multiple time NextObjectKey() while exiting the object
+ * so ignore those and don't put parser in the error state.
+ * */
+ if (st_ == kExitingArray || st_ == kEnteringObject) {
+ // #ifdef DEBUG_PARSER
+ // vDebug<<"Object: Exiting nested loop";
+ // #endif
+ return nullptr;
+ }
+
+ if (st_ != kExitingObject) {
+ st_ = kError;
+ return nullptr;
+ }
+
+ ParseNext();
+ return nullptr;
+}
+
+bool LottieParserImpl::NextArrayValue()
+{
+ if (st_ == kExitingArray) {
+ ParseNext();
+ return false;
+ }
+
+ /* SPECIAL CASE
+ * same as NextObjectKey()
+ */
+ if (st_ == kExitingObject) {
+ return false;
+ }
+
+ if (st_ == kError || st_ == kHasKey) {
+ st_ = kError;
+ return false;
+ }
+
+ return true;
+}
+
+int LottieParserImpl::GetInt()
+{
+ if (st_ != kHasNumber || !v_.IsInt()) {
+ st_ = kError;
+ return 0;
+ }
+
+ int result = v_.GetInt();
+ ParseNext();
+ return result;
+}
+
+double LottieParserImpl::GetDouble()
+{
+ if (st_ != kHasNumber) {
+ st_ = kError;
+ return 0.;
+ }
+
+ double result = v_.GetDouble();
+ ParseNext();
+ return result;
+}
+
+bool LottieParserImpl::GetBool()
+{
+ if (st_ != kHasBool) {
+ st_ = kError;
+ return false;
+ }
+
+ bool result = v_.GetBool();
+ ParseNext();
+ return result;
+}
+
+void LottieParserImpl::GetNull()
+{
+ if (st_ != kHasNull) {
+ st_ = kError;
+ return;
+ }
+
+ ParseNext();
+}
+
+const char *LottieParserImpl::GetString()
+{
+ if (st_ != kHasString) {
+ st_ = kError;
+ return nullptr;
+ }
+
+ const char *result = v_.GetString();
+ ParseNext();
+ return result;
+}
+
+std::string LottieParserImpl::GetStringObject()
+{
+ auto str = GetString();
+
+ if (str) {
+ return std::string(str);
+ }
+
+ return {};
+}
+
+void LottieParserImpl::SkipOut(int depth)
+{
+ do {
+ if (st_ == kEnteringArray || st_ == kEnteringObject) {
+ ++depth;
+ } else if (st_ == kExitingArray || st_ == kExitingObject) {
+ --depth;
+ } else if (st_ == kError) {
+ return;
+ }
+
+ ParseNext();
+ } while (depth > 0);
+}
+
+void LottieParserImpl::SkipValue()
+{
+ SkipOut(0);
+}
+
+void LottieParserImpl::SkipArray()
+{
+ SkipOut(1);
+}
+
+void LottieParserImpl::SkipObject()
+{
+ SkipOut(1);
+}
+
+Value *LottieParserImpl::PeekValue()
+{
+ if (st_ >= kHasNull && st_ <= kHasKey) {
+ return &v_;
+ }
+
+ return nullptr;
+}
+
+// returns a rapidjson::Type, or -1 for no value (at end of
+// object/array)
+int LottieParserImpl::PeekType() const
+{
+ if (st_ >= kHasNull && st_ <= kHasKey) {
+ return v_.GetType();
+ }
+
+ if (st_ == kEnteringArray) {
+ return kArrayType;
+ }
+
+ if (st_ == kEnteringObject) {
+ return kObjectType;
+ }
+
+ return -1;
+}
+
+void LottieParserImpl::Skip(const char * /*key*/)
+{
+ if (PeekType() == kArrayType) {
+ EnterArray();
+ SkipArray();
+ } else if (PeekType() == kObjectType) {
+ EnterObject();
+ SkipObject();
+ } else {
+ SkipValue();
+ }
+}
+
+model::BlendMode LottieParserImpl::getBlendMode()
+{
+ auto mode = model::BlendMode::Normal;
+
+ switch (GetInt()) {
+ case 1:
+ mode = model::BlendMode::Multiply;
+ break;
+ case 2:
+ mode = model::BlendMode::Screen;
+ break;
+ case 3:
+ mode = model::BlendMode::OverLay;
+ break;
+ default:
+ break;
+ }
+ return mode;
+}
+
+void LottieParserImpl::resolveLayerRefs()
+{
+ for (const auto &layer : mLayersToUpdate) {
+ auto search = compRef->mAssets.find(layer->extra()->mPreCompRefId);
+ if (search != compRef->mAssets.end()) {
+ if (layer->mLayerType == model::Layer::Type::Image) {
+ layer->extra()->mAsset = search->second;
+ } else if (layer->mLayerType == model::Layer::Type::Precomp) {
+ layer->mChildren = search->second->mLayers;
+ layer->setStatic(layer->isStatic() &&
+ search->second->isStatic());
+ }
+ }
+ }
+}
+
+void LottieParserImpl::parseComposition()
+{
+ EnterObject();
+ std::shared_ptr<model::Composition> sharedComposition =
+ std::make_shared<model::Composition>();
+ model::Composition *comp = sharedComposition.get();
+ compRef = comp;
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "v")) {
+ comp->mVersion = GetStringObject();
+ } else if (0 == strcmp(key, "w")) {
+ comp->mSize.setWidth(GetInt());
+ } else if (0 == strcmp(key, "h")) {
+ comp->mSize.setHeight(GetInt());
+ } else if (0 == strcmp(key, "ip")) {
+ comp->mStartFrame = GetDouble();
+ } else if (0 == strcmp(key, "op")) {
+ comp->mEndFrame = GetDouble();
+ } else if (0 == strcmp(key, "fr")) {
+ comp->mFrameRate = GetDouble();
+ } else if (0 == strcmp(key, "assets")) {
+ parseAssets(comp);
+ } else if (0 == strcmp(key, "layers")) {
+ parseLayers(comp);
+ } else if (0 == strcmp(key, "markers")) {
+ parseMarkers();
+ } else {
+#ifdef DEBUG_PARSER
+ vWarning << "Composition Attribute Skipped : " << key;
+#endif
+ Skip(key);
+ }
+ }
+
+ if (comp->mVersion.empty() || !comp->mRootLayer) {
+ // don't have a valid bodymovin header
+ return;
+ }
+ if (comp->mStartFrame > comp->mEndFrame) {
+ // reveresed animation? missing data?
+ return;
+ }
+ if (!IsValid()) {
+ return;
+ }
+
+ resolveLayerRefs();
+ comp->setStatic(comp->mRootLayer->isStatic());
+ comp->mRootLayer->mInFrame = comp->mStartFrame;
+ comp->mRootLayer->mOutFrame = comp->mEndFrame;
+
+ mComposition = sharedComposition;
+}
+
+void LottieParserImpl::parseMarker()
+{
+ EnterObject();
+ std::string comment;
+ int timeframe{0};
+ int duration{0};
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "cm")) {
+ comment = GetStringObject();
+ } else if (0 == strcmp(key, "tm")) {
+ timeframe = GetDouble();
+ } else if (0 == strcmp(key, "dr")) {
+ duration = GetDouble();
+
+ } else {
+#ifdef DEBUG_PARSER
+ vWarning << "Marker Attribute Skipped : " << key;
+#endif
+ Skip(key);
+ }
+ }
+ compRef->mMarkers.emplace_back(std::move(comment), timeframe,
+ timeframe + duration);
+}
+
+void LottieParserImpl::parseMarkers()
+{
+ EnterArray();
+ while (NextArrayValue()) {
+ parseMarker();
+ }
+ // update the precomp layers with the actual layer object
+}
+
+void LottieParserImpl::parseAssets(model::Composition *composition)
+{
+ EnterArray();
+ while (NextArrayValue()) {
+ auto asset = parseAsset();
+ composition->mAssets[asset->mRefId] = asset;
+ }
+ // update the precomp layers with the actual layer object
+}
+
+static constexpr const unsigned char B64index[256] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 62, 63, 62, 62, 63, 52, 53, 54, 55, 56, 57,
+ 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6,
+ 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
+ 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51};
+
+std::string b64decode(const char *data, const size_t len)
+{
+ auto p = reinterpret_cast<const unsigned char *>(data);
+ int pad = len > 0 && (len % 4 || p[len - 1] == '=');
+ const size_t L = ((len + 3) / 4 - pad) * 4;
+ std::string str(L / 4 * 3 + pad, '\0');
+
+ for (size_t i = 0, j = 0; i < L; i += 4) {
+ int n = B64index[p[i]] << 18 | B64index[p[i + 1]] << 12 |
+ B64index[p[i + 2]] << 6 | B64index[p[i + 3]];
+ str[j++] = n >> 16;
+ str[j++] = n >> 8 & 0xFF;
+ str[j++] = n & 0xFF;
+ }
+ if (pad) {
+ int n = B64index[p[L]] << 18 | B64index[p[L + 1]] << 12;
+ str[str.size() - 1] = n >> 16;
+
+ if (len > L + 2 && p[L + 2] != '=') {
+ n |= B64index[p[L + 2]] << 6;
+ str.push_back(n >> 8 & 0xFF);
+ }
+ }
+ return str;
+}
+
+static std::string convertFromBase64(const std::string &str)
+{
+ // usual header look like "data:image/png;base64,"
+ // so need to skip till ','.
+ size_t startIndex = str.find(",", 0);
+ startIndex += 1; // skip ","
+ size_t length = str.length() - startIndex;
+
+ const char *b64Data = str.c_str() + startIndex;
+
+ return b64decode(b64Data, length);
+}
+
+/*
+ * std::to_string() function is missing in VS2017
+ * so this is workaround for windows build
+ */
+#include <sstream>
+template <class T>
+static std::string toString(const T &value)
+{
+ std::ostringstream os;
+ os << value;
+ return os.str();
+}
+
+/*
+ * https://github.com/airbnb/lottie-web/blob/master/docs/json/layers/shape.json
+ *
+ */
+model::Asset *LottieParserImpl::parseAsset()
+{
+ auto asset = allocator().make<model::Asset>();
+ std::string filename;
+ std::string relativePath;
+ bool embededResource = false;
+ EnterObject();
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "w")) {
+ asset->mWidth = GetInt();
+ } else if (0 == strcmp(key, "h")) {
+ asset->mHeight = GetInt();
+ } else if (0 == strcmp(key, "p")) { /* image name */
+ asset->mAssetType = model::Asset::Type::Image;
+ filename = GetStringObject();
+ } else if (0 == strcmp(key, "u")) { /* relative image path */
+ relativePath = GetStringObject();
+ } else if (0 == strcmp(key, "e")) { /* relative image path */
+ embededResource = GetInt();
+ } else if (0 == strcmp(key, "id")) { /* reference id*/
+ if (PeekType() == kStringType) {
+ asset->mRefId = GetStringObject();
+ } else {
+ asset->mRefId = toString(GetInt());
+ }
+ } else if (0 == strcmp(key, "layers")) {
+ asset->mAssetType = model::Asset::Type::Precomp;
+ EnterArray();
+ bool staticFlag = true;
+ while (NextArrayValue()) {
+ auto layer = parseLayer();
+ if (layer) {
+ staticFlag = staticFlag && layer->isStatic();
+ asset->mLayers.push_back(layer);
+ }
+ }
+ asset->setStatic(staticFlag);
+ } else {
+#ifdef DEBUG_PARSER
+ vWarning << "Asset Attribute Skipped : " << key;
+#endif
+ Skip(key);
+ }
+ }
+
+ if (asset->mAssetType == model::Asset::Type::Image) {
+ if (embededResource) {
+ // embeder resource should start with "data:"
+ if (filename.compare(0, 5, "data:") == 0) {
+ asset->loadImageData(convertFromBase64(filename));
+ }
+ } else {
+ asset->loadImagePath(mDirPath + relativePath + filename);
+ }
+ }
+
+ return asset;
+}
+
+void LottieParserImpl::parseLayers(model::Composition *comp)
+{
+ comp->mRootLayer = allocator().make<model::Layer>();
+ comp->mRootLayer->mLayerType = model::Layer::Type::Precomp;
+ comp->mRootLayer->setName("__");
+ bool staticFlag = true;
+ EnterArray();
+ while (NextArrayValue()) {
+ auto layer = parseLayer();
+ if (layer) {
+ staticFlag = staticFlag && layer->isStatic();
+ comp->mRootLayer->mChildren.push_back(layer);
+ }
+ }
+ comp->mRootLayer->setStatic(staticFlag);
+}
+
+model::Color LottieParserImpl::toColor(const char *str)
+{
+ if (!str) return {};
+
+ model::Color color;
+ auto len = strlen(str);
+
+ // some resource has empty color string
+ // return a default color for those cases.
+ if (len != 7 || str[0] != '#') return color;
+
+ char tmp[3] = {'\0', '\0', '\0'};
+ tmp[0] = str[1];
+ tmp[1] = str[2];
+ color.r = std::strtol(tmp, nullptr, 16) / 255.0f;
+
+ tmp[0] = str[3];
+ tmp[1] = str[4];
+ color.g = std::strtol(tmp, nullptr, 16) / 255.0f;
+
+ tmp[0] = str[5];
+ tmp[1] = str[6];
+ color.b = std::strtol(tmp, nullptr, 16) / 255.0f;
+
+ return color;
+}
+
+model::MatteType LottieParserImpl::getMatteType()
+{
+ switch (GetInt()) {
+ case 1:
+ return model::MatteType::Alpha;
+ break;
+ case 2:
+ return model::MatteType::AlphaInv;
+ break;
+ case 3:
+ return model::MatteType::Luma;
+ break;
+ case 4:
+ return model::MatteType::LumaInv;
+ break;
+ default:
+ return model::MatteType::None;
+ break;
+ }
+}
+
+model::Layer::Type LottieParserImpl::getLayerType()
+{
+ switch (GetInt()) {
+ case 0:
+ return model::Layer::Type::Precomp;
+ break;
+ case 1:
+ return model::Layer::Type::Solid;
+ break;
+ case 2:
+ return model::Layer::Type::Image;
+ break;
+ case 3:
+ return model::Layer::Type::Null;
+ break;
+ case 4:
+ return model::Layer::Type::Shape;
+ break;
+ case 5:
+ return model::Layer::Type::Text;
+ break;
+ default:
+ return model::Layer::Type::Null;
+ break;
+ }
+}
+
+/*
+ * https://github.com/airbnb/lottie-web/blob/master/docs/json/layers/shape.json
+ *
+ */
+model::Layer *LottieParserImpl::parseLayer()
+{
+ model::Layer *layer = allocator().make<model::Layer>();
+ curLayerRef = layer;
+ bool ddd = true;
+ EnterObject();
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "ty")) { /* Type of layer*/
+ layer->mLayerType = getLayerType();
+ } else if (0 == strcmp(key, "nm")) { /*Layer name*/
+ layer->setName(GetString());
+ } else if (0 == strcmp(key, "ind")) { /*Layer index in AE. Used for
+ parenting and expressions.*/
+ layer->mId = GetInt();
+ } else if (0 == strcmp(key, "ddd")) { /*3d layer */
+ ddd = GetInt();
+ } else if (0 ==
+ strcmp(key,
+ "parent")) { /*Layer Parent. Uses "ind" of parent.*/
+ layer->mParentId = GetInt();
+ } else if (0 == strcmp(key, "refId")) { /*preComp Layer reference id*/
+ layer->extra()->mPreCompRefId = GetStringObject();
+ layer->mHasGradient = true;
+ mLayersToUpdate.push_back(layer);
+ } else if (0 == strcmp(key, "sr")) { // "Layer Time Stretching"
+ layer->mTimeStreatch = GetDouble();
+ } else if (0 == strcmp(key, "tm")) { // time remapping
+ parseProperty(layer->extra()->mTimeRemap);
+ } else if (0 == strcmp(key, "ip")) {
+ layer->mInFrame = std::lround(GetDouble());
+ } else if (0 == strcmp(key, "op")) {
+ layer->mOutFrame = std::lround(GetDouble());
+ } else if (0 == strcmp(key, "st")) {
+ layer->mStartFrame = GetDouble();
+ } else if (0 == strcmp(key, "bm")) {
+ layer->mBlendMode = getBlendMode();
+ } else if (0 == strcmp(key, "ks")) {
+ EnterObject();
+ layer->mTransform = parseTransformObject(ddd);
+ } else if (0 == strcmp(key, "shapes")) {
+ parseShapesAttr(layer);
+ } else if (0 == strcmp(key, "w")) {
+ layer->mLayerSize.setWidth(GetInt());
+ } else if (0 == strcmp(key, "h")) {
+ layer->mLayerSize.setHeight(GetInt());
+ } else if (0 == strcmp(key, "sw")) {
+ layer->mLayerSize.setWidth(GetInt());
+ } else if (0 == strcmp(key, "sh")) {
+ layer->mLayerSize.setHeight(GetInt());
+ } else if (0 == strcmp(key, "sc")) {
+ layer->extra()->mSolidColor = toColor(GetString());
+ } else if (0 == strcmp(key, "tt")) {
+ layer->mMatteType = getMatteType();
+ } else if (0 == strcmp(key, "hasMask")) {
+ layer->mHasMask = GetBool();
+ } else if (0 == strcmp(key, "masksProperties")) {
+ parseMaskProperty(layer);
+ } else if (0 == strcmp(key, "ao")) {
+ layer->mAutoOrient = GetInt();
+ } else if (0 == strcmp(key, "hd")) {
+ layer->setHidden(GetBool());
+ } else {
+#ifdef DEBUG_PARSER
+ vWarning << "Layer Attribute Skipped : " << key;
+#endif
+ Skip(key);
+ }
+ }
+
+ if (!layer->mTransform) {
+ // not a valid layer
+ return nullptr;
+ }
+
+ // make sure layer data is not corrupted.
+ if (layer->hasParent() && (layer->id() == layer->parentId()))
+ return nullptr;
+
+ if (layer->mExtra) layer->mExtra->mCompRef = compRef;
+
+ if (layer->hidden()) {
+ // if layer is hidden, only data that is usefull is its
+ // transform matrix(when it is a parent of some other layer)
+ // so force it to be a Null Layer and release all resource.
+ layer->setStatic(layer->mTransform->isStatic());
+ layer->mLayerType = model::Layer::Type::Null;
+ layer->mChildren = {};
+ return layer;
+ }
+
+ // update the static property of layer
+ bool staticFlag = true;
+ for (const auto &child : layer->mChildren) {
+ staticFlag &= child->isStatic();
+ }
+
+ if (layer->hasMask() && layer->mExtra) {
+ for (const auto &mask : layer->mExtra->mMasks) {
+ staticFlag &= mask->isStatic();
+ }
+ }
+
+ layer->setStatic(staticFlag && layer->mTransform->isStatic());
+
+ return layer;
+}
+
+void LottieParserImpl::parseMaskProperty(model::Layer *layer)
+{
+ EnterArray();
+ while (NextArrayValue()) {
+ layer->extra()->mMasks.push_back(parseMaskObject());
+ }
+}
+
+model::Mask *LottieParserImpl::parseMaskObject()
+{
+ auto obj = allocator().make<model::Mask>();
+
+ EnterObject();
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "inv")) {
+ obj->mInv = GetBool();
+ } else if (0 == strcmp(key, "mode")) {
+ const char *str = GetString();
+ if (!str) {
+ obj->mMode = model::Mask::Mode::None;
+ continue;
+ }
+ switch (str[0]) {
+ case 'n':
+ obj->mMode = model::Mask::Mode::None;
+ break;
+ case 'a':
+ obj->mMode = model::Mask::Mode::Add;
+ break;
+ case 's':
+ obj->mMode = model::Mask::Mode::Substarct;
+ break;
+ case 'i':
+ obj->mMode = model::Mask::Mode::Intersect;
+ break;
+ case 'f':
+ obj->mMode = model::Mask::Mode::Difference;
+ break;
+ default:
+ obj->mMode = model::Mask::Mode::None;
+ break;
+ }
+ } else if (0 == strcmp(key, "pt")) {
+ parseShapeProperty(obj->mShape);
+ } else if (0 == strcmp(key, "o")) {
+ parseProperty(obj->mOpacity);
+ } else {
+ Skip(key);
+ }
+ }
+ obj->mIsStatic = obj->mShape.isStatic() && obj->mOpacity.isStatic();
+ return obj;
+}
+
+void LottieParserImpl::parseShapesAttr(model::Layer *layer)
+{
+ EnterArray();
+ while (NextArrayValue()) {
+ parseObject(layer);
+ }
+}
+
+model::Object *LottieParserImpl::parseObjectTypeAttr()
+{
+ const char *type = GetString();
+ if (0 == strcmp(type, "gr")) {
+ return parseGroupObject();
+ } else if (0 == strcmp(type, "rc")) {
+ return parseRectObject();
+ } else if (0 == strcmp(type, "rd")) {
+ curLayerRef->mHasRoundedCorner = true;
+ return parseRoundedCorner();
+ } else if (0 == strcmp(type, "el")) {
+ return parseEllipseObject();
+ } else if (0 == strcmp(type, "tr")) {
+ return parseTransformObject();
+ } else if (0 == strcmp(type, "fl")) {
+ return parseFillObject();
+ } else if (0 == strcmp(type, "st")) {
+ return parseStrokeObject();
+ } else if (0 == strcmp(type, "gf")) {
+ curLayerRef->mHasGradient = true;
+ return parseGFillObject();
+ } else if (0 == strcmp(type, "gs")) {
+ curLayerRef->mHasGradient = true;
+ return parseGStrokeObject();
+ } else if (0 == strcmp(type, "sh")) {
+ return parseShapeObject();
+ } else if (0 == strcmp(type, "sr")) {
+ return parsePolystarObject();
+ } else if (0 == strcmp(type, "tm")) {
+ curLayerRef->mHasPathOperator = true;
+ return parseTrimObject();
+ } else if (0 == strcmp(type, "rp")) {
+ curLayerRef->mHasRepeater = true;
+ return parseReapeaterObject();
+ } else if (0 == strcmp(type, "mm")) {
+ vWarning << "Merge Path is not supported yet";
+ return nullptr;
+ } else {
+#ifdef DEBUG_PARSER
+ vDebug << "The Object Type not yet handled = " << type;
+#endif
+ return nullptr;
+ }
+}
+
+void LottieParserImpl::parseObject(model::Group *parent)
+{
+ EnterObject();
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "ty")) {
+ auto child = parseObjectTypeAttr();
+ if (child && !child->hidden()) {
+ if (child->type() == model::Object::Type::RoundedCorner) {
+ updateRoundedCorner(parent, static_cast<model::RoundedCorner *>(child));
+ }
+ parent->mChildren.push_back(child);
+ }
+ } else {
+ Skip(key);
+ }
+ }
+}
+
+void LottieParserImpl::updateRoundedCorner(model::Group *group, model::RoundedCorner *rc)
+{
+ for(auto &e : group->mChildren)
+ {
+ if (e->type() == model::Object::Type::Rect) {
+ static_cast<model::Rect *>(e)->mRoundedCorner = rc;
+ if (!rc->isStatic()) {
+ e->setStatic(false);
+ group->setStatic(false);
+ //@TODO need to propagate.
+ }
+ } else if ( e->type() == model::Object::Type::Group) {
+ updateRoundedCorner(static_cast<model::Group *>(e), rc);
+ }
+ }
+}
+
+model::Object *LottieParserImpl::parseGroupObject()
+{
+ auto group = allocator().make<model::Group>();
+
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "nm")) {
+ group->setName(GetString());
+ } else if (0 == strcmp(key, "it")) {
+ EnterArray();
+ while (NextArrayValue()) {
+ parseObject(group);
+ }
+ if (!group->mChildren.empty()
+ && group->mChildren.back()->type()
+ == model::Object::Type::Transform) {
+ group->mTransform =
+ static_cast<model::Transform *>(group->mChildren.back());
+ group->mChildren.pop_back();
+ }
+ } else {
+ Skip(key);
+ }
+ }
+ bool staticFlag = true;
+ for (const auto &child : group->mChildren) {
+ staticFlag &= child->isStatic();
+ }
+
+ if (group->mTransform) {
+ group->setStatic(staticFlag && group->mTransform->isStatic());
+ }
+
+ return group;
+}
+
+/*
+ * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/rect.json
+ */
+model::Rect *LottieParserImpl::parseRectObject()
+{
+ auto obj = allocator().make<model::Rect>();
+
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "nm")) {
+ obj->setName(GetString());
+ } else if (0 == strcmp(key, "p")) {
+ parseProperty(obj->mPos);
+ } else if (0 == strcmp(key, "s")) {
+ parseProperty(obj->mSize);
+ } else if (0 == strcmp(key, "r")) {
+ parseProperty(obj->mRound);
+ } else if (0 == strcmp(key, "d")) {
+ obj->mDirection = GetInt();
+ } else if (0 == strcmp(key, "hd")) {
+ obj->setHidden(GetBool());
+ } else {
+ Skip(key);
+ }
+ }
+ obj->setStatic(obj->mPos.isStatic() && obj->mSize.isStatic() &&
+ obj->mRound.isStatic());
+ return obj;
+}
+
+/*
+ * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/rect.json
+ */
+model::RoundedCorner *LottieParserImpl::parseRoundedCorner()
+{
+ auto obj = allocator().make<model::RoundedCorner>();
+
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "nm")) {
+ obj->setName(GetString());
+ } else if (0 == strcmp(key, "r")) {
+ parseProperty(obj->mRadius);
+ } else if (0 == strcmp(key, "hd")) {
+ obj->setHidden(GetBool());
+ } else {
+ Skip(key);
+ }
+ }
+ obj->setStatic(obj->mRadius.isStatic());
+ return obj;
+}
+
+/*
+ * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/ellipse.json
+ */
+model::Ellipse *LottieParserImpl::parseEllipseObject()
+{
+ auto obj = allocator().make<model::Ellipse>();
+
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "nm")) {
+ obj->setName(GetString());
+ } else if (0 == strcmp(key, "p")) {
+ parseProperty(obj->mPos);
+ } else if (0 == strcmp(key, "s")) {
+ parseProperty(obj->mSize);
+ } else if (0 == strcmp(key, "d")) {
+ obj->mDirection = GetInt();
+ } else if (0 == strcmp(key, "hd")) {
+ obj->setHidden(GetBool());
+ } else {
+ Skip(key);
+ }
+ }
+ obj->setStatic(obj->mPos.isStatic() && obj->mSize.isStatic());
+ return obj;
+}
+
+/*
+ * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/shape.json
+ */
+model::Path *LottieParserImpl::parseShapeObject()
+{
+ auto obj = allocator().make<model::Path>();
+
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "nm")) {
+ obj->setName(GetString());
+ } else if (0 == strcmp(key, "ks")) {
+ parseShapeProperty(obj->mShape);
+ } else if (0 == strcmp(key, "d")) {
+ obj->mDirection = GetInt();
+ } else if (0 == strcmp(key, "hd")) {
+ obj->setHidden(GetBool());
+ } else {
+#ifdef DEBUG_PARSER
+ vDebug << "Shape property ignored :" << key;
+#endif
+ Skip(key);
+ }
+ }
+ obj->setStatic(obj->mShape.isStatic());
+
+ return obj;
+}
+
+/*
+ * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/star.json
+ */
+model::Polystar *LottieParserImpl::parsePolystarObject()
+{
+ auto obj = allocator().make<model::Polystar>();
+
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "nm")) {
+ obj->setName(GetString());
+ } else if (0 == strcmp(key, "p")) {
+ parseProperty(obj->mPos);
+ } else if (0 == strcmp(key, "pt")) {
+ parseProperty(obj->mPointCount);
+ } else if (0 == strcmp(key, "ir")) {
+ parseProperty(obj->mInnerRadius);
+ } else if (0 == strcmp(key, "is")) {
+ parseProperty(obj->mInnerRoundness);
+ } else if (0 == strcmp(key, "or")) {
+ parseProperty(obj->mOuterRadius);
+ } else if (0 == strcmp(key, "os")) {
+ parseProperty(obj->mOuterRoundness);
+ } else if (0 == strcmp(key, "r")) {
+ parseProperty(obj->mRotation);
+ } else if (0 == strcmp(key, "sy")) {
+ int starType = GetInt();
+ if (starType == 1) obj->mPolyType = model::Polystar::PolyType::Star;
+ if (starType == 2)
+ obj->mPolyType = model::Polystar::PolyType::Polygon;
+ } else if (0 == strcmp(key, "d")) {
+ obj->mDirection = GetInt();
+ } else if (0 == strcmp(key, "hd")) {
+ obj->setHidden(GetBool());
+ } else {
+#ifdef DEBUG_PARSER
+ vDebug << "Polystar property ignored :" << key;
+#endif
+ Skip(key);
+ }
+ }
+ obj->setStatic(
+ obj->mPos.isStatic() && obj->mPointCount.isStatic() &&
+ obj->mInnerRadius.isStatic() && obj->mInnerRoundness.isStatic() &&
+ obj->mOuterRadius.isStatic() && obj->mOuterRoundness.isStatic() &&
+ obj->mRotation.isStatic());
+
+ return obj;
+}
+
+model::Trim::TrimType LottieParserImpl::getTrimType()
+{
+ switch (GetInt()) {
+ case 1:
+ return model::Trim::TrimType::Simultaneously;
+ break;
+ case 2:
+ return model::Trim::TrimType::Individually;
+ break;
+ default:
+ Error();
+ return model::Trim::TrimType::Simultaneously;
+ break;
+ }
+}
+
+/*
+ * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/trim.json
+ */
+model::Trim *LottieParserImpl::parseTrimObject()
+{
+ auto obj = allocator().make<model::Trim>();
+
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "nm")) {
+ obj->setName(GetString());
+ } else if (0 == strcmp(key, "s")) {
+ parseProperty(obj->mStart);
+ } else if (0 == strcmp(key, "e")) {
+ parseProperty(obj->mEnd);
+ } else if (0 == strcmp(key, "o")) {
+ parseProperty(obj->mOffset);
+ } else if (0 == strcmp(key, "m")) {
+ obj->mTrimType = getTrimType();
+ } else if (0 == strcmp(key, "hd")) {
+ obj->setHidden(GetBool());
+ } else {
+#ifdef DEBUG_PARSER
+ vDebug << "Trim property ignored :" << key;
+#endif
+ Skip(key);
+ }
+ }
+ obj->setStatic(obj->mStart.isStatic() && obj->mEnd.isStatic() &&
+ obj->mOffset.isStatic());
+ return obj;
+}
+
+void LottieParserImpl::getValue(model::Repeater::Transform &obj)
+{
+ EnterObject();
+
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "a")) {
+ parseProperty(obj.mAnchor);
+ } else if (0 == strcmp(key, "p")) {
+ parseProperty(obj.mPosition);
+ } else if (0 == strcmp(key, "r")) {
+ parseProperty(obj.mRotation);
+ } else if (0 == strcmp(key, "s")) {
+ parseProperty(obj.mScale);
+ } else if (0 == strcmp(key, "so")) {
+ parseProperty(obj.mStartOpacity);
+ } else if (0 == strcmp(key, "eo")) {
+ parseProperty(obj.mEndOpacity);
+ } else {
+ Skip(key);
+ }
+ }
+}
+
+model::Repeater *LottieParserImpl::parseReapeaterObject()
+{
+ auto obj = allocator().make<model::Repeater>();
+
+ obj->setContent(allocator().make<model::Group>());
+
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "nm")) {
+ obj->setName(GetString());
+ } else if (0 == strcmp(key, "c")) {
+ parseProperty(obj->mCopies);
+ float maxCopy = 0.0;
+ if (!obj->mCopies.isStatic()) {
+ for (auto &keyFrame : obj->mCopies.animation().frames_) {
+ if (maxCopy < keyFrame.value_.start_)
+ maxCopy = keyFrame.value_.start_;
+ if (maxCopy < keyFrame.value_.end_)
+ maxCopy = keyFrame.value_.end_;
+ }
+ } else {
+ maxCopy = obj->mCopies.value();
+ }
+ obj->mMaxCopies = maxCopy;
+ } else if (0 == strcmp(key, "o")) {
+ parseProperty(obj->mOffset);
+ } else if (0 == strcmp(key, "tr")) {
+ getValue(obj->mTransform);
+ } else if (0 == strcmp(key, "hd")) {
+ obj->setHidden(GetBool());
+ } else {
+#ifdef DEBUG_PARSER
+ vDebug << "Repeater property ignored :" << key;
+#endif
+ Skip(key);
+ }
+ }
+ obj->setStatic(obj->mCopies.isStatic() && obj->mOffset.isStatic() &&
+ obj->mTransform.isStatic());
+
+ return obj;
+}
+
+/*
+ * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/transform.json
+ */
+model::Transform *LottieParserImpl::parseTransformObject(bool ddd)
+{
+ auto objT = allocator().make<model::Transform>();
+
+ auto obj = allocator().make<model::Transform::Data>();
+ if (ddd) {
+ obj->createExtraData();
+ obj->mExtra->m3DData = true;
+ }
+
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "nm")) {
+ objT->setName(GetString());
+ } else if (0 == strcmp(key, "a")) {
+ parseProperty(obj->mAnchor);
+ } else if (0 == strcmp(key, "p")) {
+ EnterObject();
+ bool separate = false;
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "k")) {
+ parsePropertyHelper(obj->mPosition);
+ } else if (0 == strcmp(key, "s")) {
+ obj->createExtraData();
+ obj->mExtra->mSeparate = GetBool();
+ separate = true;
+ } else if (separate && (0 == strcmp(key, "x"))) {
+ parseProperty(obj->mExtra->mSeparateX);
+ } else if (separate && (0 == strcmp(key, "y"))) {
+ parseProperty(obj->mExtra->mSeparateY);
+ } else {
+ Skip(key);
+ }
+ }
+ } else if (0 == strcmp(key, "r")) {
+ parseProperty(obj->mRotation);
+ } else if (0 == strcmp(key, "s")) {
+ parseProperty(obj->mScale);
+ } else if (0 == strcmp(key, "o")) {
+ parseProperty(obj->mOpacity);
+ } else if (0 == strcmp(key, "hd")) {
+ objT->setHidden(GetBool());
+ } else if (0 == strcmp(key, "rx")) {
+ if (!obj->mExtra) return nullptr;
+ parseProperty(obj->mExtra->m3DRx);
+ } else if (0 == strcmp(key, "ry")) {
+ if (!obj->mExtra) return nullptr;
+ parseProperty(obj->mExtra->m3DRy);
+ } else if (0 == strcmp(key, "rz")) {
+ if (!obj->mExtra) return nullptr;
+ parseProperty(obj->mExtra->m3DRz);
+ } else {
+ Skip(key);
+ }
+ }
+ bool isStatic = obj->mAnchor.isStatic() && obj->mPosition.isStatic() &&
+ obj->mRotation.isStatic() && obj->mScale.isStatic() &&
+ obj->mOpacity.isStatic();
+ if (obj->mExtra) {
+ isStatic = isStatic && obj->mExtra->m3DRx.isStatic() &&
+ obj->mExtra->m3DRy.isStatic() &&
+ obj->mExtra->m3DRz.isStatic() &&
+ obj->mExtra->mSeparateX.isStatic() &&
+ obj->mExtra->mSeparateY.isStatic();
+ }
+
+ objT->set(obj, isStatic);
+
+ return objT;
+}
+
+/*
+ * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/fill.json
+ */
+model::Fill *LottieParserImpl::parseFillObject()
+{
+ auto obj = allocator().make<model::Fill>();
+
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "nm")) {
+ obj->setName(GetString());
+ } else if (0 == strcmp(key, "c")) {
+ parseProperty(obj->mColor);
+ } else if (0 == strcmp(key, "o")) {
+ parseProperty(obj->mOpacity);
+ } else if (0 == strcmp(key, "fillEnabled")) {
+ obj->mEnabled = GetBool();
+ } else if (0 == strcmp(key, "r")) {
+ obj->mFillRule = getFillRule();
+ } else if (0 == strcmp(key, "hd")) {
+ obj->setHidden(GetBool());
+ } else {
+#ifdef DEBUG_PARSER
+ vWarning << "Fill property skipped = " << key;
+#endif
+ Skip(key);
+ }
+ }
+ obj->setStatic(obj->mColor.isStatic() && obj->mOpacity.isStatic());
+
+ return obj;
+}
+
+/*
+ * https://github.com/airbnb/lottie-web/blob/master/docs/json/helpers/lineCap.json
+ */
+CapStyle LottieParserImpl::getLineCap()
+{
+ switch (GetInt()) {
+ case 1:
+ return CapStyle::Flat;
+ break;
+ case 2:
+ return CapStyle::Round;
+ break;
+ default:
+ return CapStyle::Square;
+ break;
+ }
+}
+
+FillRule LottieParserImpl::getFillRule()
+{
+ switch (GetInt()) {
+ case 1:
+ return FillRule::Winding;
+ break;
+ case 2:
+ return FillRule::EvenOdd;
+ break;
+ default:
+ return FillRule::Winding;
+ break;
+ }
+}
+
+/*
+ * https://github.com/airbnb/lottie-web/blob/master/docs/json/helpers/lineJoin.json
+ */
+JoinStyle LottieParserImpl::getLineJoin()
+{
+ switch (GetInt()) {
+ case 1:
+ return JoinStyle::Miter;
+ break;
+ case 2:
+ return JoinStyle::Round;
+ break;
+ default:
+ return JoinStyle::Bevel;
+ break;
+ }
+}
+
+/*
+ * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/stroke.json
+ */
+model::Stroke *LottieParserImpl::parseStrokeObject()
+{
+ auto obj = allocator().make<model::Stroke>();
+
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "nm")) {
+ obj->setName(GetString());
+ } else if (0 == strcmp(key, "c")) {
+ parseProperty(obj->mColor);
+ } else if (0 == strcmp(key, "o")) {
+ parseProperty(obj->mOpacity);
+ } else if (0 == strcmp(key, "w")) {
+ parseProperty(obj->mWidth);
+ } else if (0 == strcmp(key, "fillEnabled")) {
+ obj->mEnabled = GetBool();
+ } else if (0 == strcmp(key, "lc")) {
+ obj->mCapStyle = getLineCap();
+ } else if (0 == strcmp(key, "lj")) {
+ obj->mJoinStyle = getLineJoin();
+ } else if (0 == strcmp(key, "ml")) {
+ obj->mMiterLimit = GetDouble();
+ } else if (0 == strcmp(key, "d")) {
+ parseDashProperty(obj->mDash);
+ } else if (0 == strcmp(key, "hd")) {
+ obj->setHidden(GetBool());
+ } else {
+#ifdef DEBUG_PARSER
+ vWarning << "Stroke property skipped = " << key;
+#endif
+ Skip(key);
+ }
+ }
+ obj->setStatic(obj->mColor.isStatic() && obj->mOpacity.isStatic() &&
+ obj->mWidth.isStatic() && obj->mDash.isStatic());
+ return obj;
+}
+
+void LottieParserImpl::parseGradientProperty(model::Gradient *obj,
+ const char * key)
+{
+ if (0 == strcmp(key, "t")) {
+ obj->mGradientType = GetInt();
+ } else if (0 == strcmp(key, "o")) {
+ parseProperty(obj->mOpacity);
+ } else if (0 == strcmp(key, "s")) {
+ parseProperty(obj->mStartPoint);
+ } else if (0 == strcmp(key, "e")) {
+ parseProperty(obj->mEndPoint);
+ } else if (0 == strcmp(key, "h")) {
+ parseProperty(obj->mHighlightLength);
+ } else if (0 == strcmp(key, "a")) {
+ parseProperty(obj->mHighlightAngle);
+ } else if (0 == strcmp(key, "g")) {
+ EnterObject();
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "k")) {
+ parseProperty(obj->mGradient);
+ } else if (0 == strcmp(key, "p")) {
+ obj->mColorPoints = GetInt();
+ } else {
+ Skip(nullptr);
+ }
+ }
+ } else if (0 == strcmp(key, "hd")) {
+ obj->setHidden(GetBool());
+ } else {
+#ifdef DEBUG_PARSER
+ vWarning << "Gradient property skipped = " << key;
+#endif
+ Skip(key);
+ }
+ obj->setStatic(
+ obj->mOpacity.isStatic() && obj->mStartPoint.isStatic() &&
+ obj->mEndPoint.isStatic() && obj->mHighlightAngle.isStatic() &&
+ obj->mHighlightLength.isStatic() && obj->mGradient.isStatic());
+}
+
+/*
+ * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/gfill.json
+ */
+model::GradientFill *LottieParserImpl::parseGFillObject()
+{
+ auto obj = allocator().make<model::GradientFill>();
+
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "nm")) {
+ obj->setName(GetString());
+ } else if (0 == strcmp(key, "r")) {
+ obj->mFillRule = getFillRule();
+ } else {
+ parseGradientProperty(obj, key);
+ }
+ }
+ return obj;
+}
+
+void LottieParserImpl::parseDashProperty(model::Dash &dash)
+{
+ EnterArray();
+ while (NextArrayValue()) {
+ EnterObject();
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "v")) {
+ dash.mData.emplace_back();
+ parseProperty(dash.mData.back());
+ } else {
+ Skip(key);
+ }
+ }
+ }
+}
+
+/*
+ * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/gstroke.json
+ */
+model::GradientStroke *LottieParserImpl::parseGStrokeObject()
+{
+ auto obj = allocator().make<model::GradientStroke>();
+
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "nm")) {
+ obj->setName(GetString());
+ } else if (0 == strcmp(key, "w")) {
+ parseProperty(obj->mWidth);
+ } else if (0 == strcmp(key, "lc")) {
+ obj->mCapStyle = getLineCap();
+ } else if (0 == strcmp(key, "lj")) {
+ obj->mJoinStyle = getLineJoin();
+ } else if (0 == strcmp(key, "ml")) {
+ obj->mMiterLimit = GetDouble();
+ } else if (0 == strcmp(key, "d")) {
+ parseDashProperty(obj->mDash);
+ } else {
+ parseGradientProperty(obj, key);
+ }
+ }
+
+ obj->setStatic(obj->isStatic() && obj->mWidth.isStatic() &&
+ obj->mDash.isStatic());
+ return obj;
+}
+
+void LottieParserImpl::getValue(std::vector<VPointF> &v)
+{
+ EnterArray();
+ while (NextArrayValue()) {
+ EnterArray();
+ VPointF pt;
+ getValue(pt);
+ v.push_back(pt);
+ }
+}
+
+void LottieParserImpl::getValue(VPointF &pt)
+{
+ float val[4] = {0.f};
+ int i = 0;
+
+ if (PeekType() == kArrayType) EnterArray();
+
+ while (NextArrayValue()) {
+ const auto value = GetDouble();
+ if (i < 4) {
+ val[i++] = value;
+ }
+ }
+ pt.setX(val[0]);
+ pt.setY(val[1]);
+}
+
+void LottieParserImpl::getValue(float &val)
+{
+ if (PeekType() == kArrayType) {
+ EnterArray();
+ if (NextArrayValue()) val = GetDouble();
+ // discard rest
+ while (NextArrayValue()) {
+ GetDouble();
+ }
+ } else if (PeekType() == kNumberType) {
+ val = GetDouble();
+ } else {
+ Error();
+ }
+}
+
+void LottieParserImpl::getValue(model::Color &color)
+{
+ float val[4] = {0.f};
+ int i = 0;
+ if (PeekType() == kArrayType) EnterArray();
+
+ while (NextArrayValue()) {
+ const auto value = GetDouble();
+ if (i < 4) {
+ val[i++] = value;
+ }
+ }
+
+ if (mColorFilter) mColorFilter(val[0], val[1], val[2]);
+
+ color.r = val[0];
+ color.g = val[1];
+ color.b = val[2];
+}
+
+void LottieParserImpl::getValue(model::Gradient::Data &grad)
+{
+ if (PeekType() == kArrayType) EnterArray();
+
+ while (NextArrayValue()) {
+ grad.mGradient.push_back(GetDouble());
+ }
+}
+
+void LottieParserImpl::getValue(int &val)
+{
+ if (PeekType() == kArrayType) {
+ EnterArray();
+ while (NextArrayValue()) {
+ val = GetInt();
+ }
+ } else if (PeekType() == kNumberType) {
+ val = GetInt();
+ } else {
+ Error();
+ }
+}
+
+void LottieParserImpl::parsePathInfo()
+{
+ mPathInfo.reset();
+
+ /*
+ * The shape object could be wrapped by a array
+ * if its part of the keyframe object
+ */
+ bool arrayWrapper = (PeekType() == kArrayType);
+ if (arrayWrapper) EnterArray();
+
+ EnterObject();
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "i")) {
+ getValue(mPathInfo.mInPoint);
+ } else if (0 == strcmp(key, "o")) {
+ getValue(mPathInfo.mOutPoint);
+ } else if (0 == strcmp(key, "v")) {
+ getValue(mPathInfo.mVertices);
+ } else if (0 == strcmp(key, "c")) {
+ mPathInfo.mClosed = GetBool();
+ } else {
+ Error();
+ Skip(nullptr);
+ }
+ }
+ // exit properly from the array
+ if (arrayWrapper) NextArrayValue();
+
+ mPathInfo.convert();
+}
+
+void LottieParserImpl::getValue(model::PathData &obj)
+{
+ parsePathInfo();
+ obj.mPoints = mPathInfo.mResult;
+ obj.mClosed = mPathInfo.mClosed;
+}
+
+VPointF LottieParserImpl::parseInperpolatorPoint()
+{
+ VPointF cp;
+ EnterObject();
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "x")) {
+ getValue(cp.rx());
+ }
+ if (0 == strcmp(key, "y")) {
+ getValue(cp.ry());
+ }
+ }
+ return cp;
+}
+
+template <typename T>
+bool LottieParserImpl::parseKeyFrameValue(
+ const char *key, model::Value<T, model::Position> &value)
+{
+ if (0 == strcmp(key, "ti")) {
+ value.hasTangent_ = true;
+ getValue(value.inTangent_);
+ } else if (0 == strcmp(key, "to")) {
+ value.hasTangent_ = true;
+ getValue(value.outTangent_);
+ } else {
+ return false;
+ }
+ return true;
+}
+
+VInterpolator *LottieParserImpl::interpolator(VPointF inTangent,
+ VPointF outTangent,
+ std::string key)
+{
+ if (key.empty()) {
+ std::array<char, 20> temp;
+ snprintf(temp.data(), temp.size(), "%.2f_%.2f_%.2f_%.2f", inTangent.x(),
+ inTangent.y(), outTangent.x(), outTangent.y());
+ key = temp.data();
+ }
+
+ auto search = mInterpolatorCache.find(key);
+
+ if (search != mInterpolatorCache.end()) {
+ return search->second;
+ }
+
+ auto obj = allocator().make<VInterpolator>(outTangent, inTangent);
+ mInterpolatorCache[std::move(key)] = obj;
+ return obj;
+}
+
+/*
+ * https://github.com/airbnb/lottie-web/blob/master/docs/json/properties/multiDimensionalKeyframed.json
+ */
+template <typename T, typename Tag>
+void LottieParserImpl::parseKeyFrame(model::KeyFrames<T, Tag> &obj)
+{
+ struct ParsedField {
+ std::string interpolatorKey;
+ bool interpolator{false};
+ bool value{false};
+ bool hold{false};
+ bool noEndValue{true};
+ };
+
+ EnterObject();
+ ParsedField parsed;
+ typename model::KeyFrames<T, Tag>::Frame keyframe;
+ VPointF inTangent;
+ VPointF outTangent;
+
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "i")) {
+ parsed.interpolator = true;
+ inTangent = parseInperpolatorPoint();
+ } else if (0 == strcmp(key, "o")) {
+ outTangent = parseInperpolatorPoint();
+ } else if (0 == strcmp(key, "t")) {
+ keyframe.start_ = GetDouble();
+ } else if (0 == strcmp(key, "s")) {
+ parsed.value = true;
+ getValue(keyframe.value_.start_);
+ continue;
+ } else if (0 == strcmp(key, "e")) {
+ parsed.noEndValue = false;
+ getValue(keyframe.value_.end_);
+ continue;
+ } else if (0 == strcmp(key, "n")) {
+ if (PeekType() == kStringType) {
+ parsed.interpolatorKey = GetStringObject();
+ } else {
+ EnterArray();
+ while (NextArrayValue()) {
+ if (parsed.interpolatorKey.empty()) {
+ parsed.interpolatorKey = GetStringObject();
+ } else {
+ // skip rest of the string
+ Skip(nullptr);
+ }
+ }
+ }
+ continue;
+ } else if (parseKeyFrameValue(key, keyframe.value_)) {
+ continue;
+ } else if (0 == strcmp(key, "h")) {
+ parsed.hold = GetInt();
+ continue;
+ } else {
+#ifdef DEBUG_PARSER
+ vDebug << "key frame property skipped = " << key;
+#endif
+ Skip(key);
+ }
+ }
+
+ auto &list = obj.frames_;
+ if (!list.empty()) {
+ // update the endFrame value of current keyframe
+ list.back().end_ = keyframe.start_;
+ // if no end value provided, copy start value to previous frame
+ if (parsed.value && parsed.noEndValue) {
+ list.back().value_.end_ = keyframe.value_.start_;
+ }
+ }
+
+ if (parsed.hold) {
+ keyframe.value_.end_ = keyframe.value_.start_;
+ keyframe.end_ = keyframe.start_;
+ list.push_back(std::move(keyframe));
+ } else if (parsed.interpolator) {
+ keyframe.interpolator_ = interpolator(
+ inTangent, outTangent, std::move(parsed.interpolatorKey));
+ list.push_back(std::move(keyframe));
+ } else {
+ // its the last frame discard.
+ }
+}
+
+/*
+ * https://github.com/airbnb/lottie-web/blob/master/docs/json/properties/shapeKeyframed.json
+ */
+
+/*
+ * https://github.com/airbnb/lottie-web/blob/master/docs/json/properties/shape.json
+ */
+void LottieParserImpl::parseShapeProperty(model::Property<model::PathData> &obj)
+{
+ EnterObject();
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "k")) {
+ if (PeekType() == kArrayType) {
+ EnterArray();
+ while (NextArrayValue()) {
+ parseKeyFrame(obj.animation());
+ }
+ } else {
+ if (!obj.isStatic()) {
+ st_ = kError;
+ return;
+ }
+ getValue(obj.value());
+ }
+ } else {
+#ifdef DEBUG_PARSER
+ vDebug << "shape property ignored = " << key;
+#endif
+ Skip(nullptr);
+ }
+ }
+ obj.cache();
+}
+
+template <typename T, typename Tag>
+void LottieParserImpl::parsePropertyHelper(model::Property<T, Tag> &obj)
+{
+ if (PeekType() == kNumberType) {
+ if (!obj.isStatic()) {
+ st_ = kError;
+ return;
+ }
+ /*single value property with no animation*/
+ getValue(obj.value());
+ } else {
+ EnterArray();
+ while (NextArrayValue()) {
+ /* property with keyframe info*/
+ if (PeekType() == kObjectType) {
+ parseKeyFrame(obj.animation());
+ } else {
+ /* Read before modifying.
+ * as there is no way of knowing if the
+ * value of the array is either array of numbers
+ * or array of object without entering the array
+ * thats why this hack is there
+ */
+ if (!obj.isStatic()) {
+ st_ = kError;
+ return;
+ }
+ /*multi value property with no animation*/
+ getValue(obj.value());
+ /*break here as we already reached end of array*/
+ break;
+ }
+ }
+ obj.cache();
+ }
+}
+
+/*
+ * https://github.com/airbnb/lottie-web/tree/master/docs/json/properties
+ */
+template <typename T>
+void LottieParserImpl::parseProperty(model::Property<T> &obj)
+{
+ EnterObject();
+ while (const char *key = NextObjectKey()) {
+ if (0 == strcmp(key, "k")) {
+ parsePropertyHelper(obj);
+ } else {
+ Skip(key);
+ }
+ }
+}
+
+#ifdef LOTTIE_DUMP_TREE_SUPPORT
+
+class ObjectInspector {
+public:
+ void visit(model::Composition *obj, std::string level)
+ {
+ vDebug << " { " << level << "Composition:: a: " << !obj->isStatic()
+ << ", v: " << obj->mVersion << ", stFm: " << obj->startFrame()
+ << ", endFm: " << obj->endFrame()
+ << ", W: " << obj->size().width()
+ << ", H: " << obj->size().height() << "\n";
+ level.append("\t");
+ visit(obj->mRootLayer, level);
+ level.erase(level.end() - 1, level.end());
+ vDebug << " } " << level << "Composition End\n";
+ }
+ void visit(model::Layer *obj, std::string level)
+ {
+ vDebug << level << "{ " << layerType(obj->mLayerType)
+ << ", name: " << obj->name() << ", id:" << obj->mId
+ << " Pid:" << obj->mParentId << ", a:" << !obj->isStatic()
+ << ", " << matteType(obj->mMatteType)
+ << ", mask:" << obj->hasMask() << ", inFm:" << obj->mInFrame
+ << ", outFm:" << obj->mOutFrame << ", stFm:" << obj->mStartFrame
+ << ", ts:" << obj->mTimeStreatch << ", ao:" << obj->autoOrient()
+ << ", W:" << obj->layerSize().width()
+ << ", H:" << obj->layerSize().height();
+
+ if (obj->mLayerType == model::Layer::Type::Image)
+ vDebug << level << "\t{ "
+ << "ImageInfo:"
+ << " W :" << obj->extra()->mAsset->mWidth
+ << ", H :" << obj->extra()->mAsset->mHeight << " }"
+ << "\n";
+ else {
+ vDebug << level;
+ }
+ visitChildren(static_cast<model::Group *>(obj), level);
+ vDebug << level << "} " << layerType(obj->mLayerType).c_str()
+ << ", id: " << obj->mId << "\n";
+ }
+ void visitChildren(model::Group *obj, std::string level)
+ {
+ level.append("\t");
+ for (const auto &child : obj->mChildren) visit(child, level);
+ if (obj->mTransform) visit(obj->mTransform, level);
+ }
+
+ void visit(model::Object *obj, std::string level)
+ {
+ switch (obj->type()) {
+ case model::Object::Type::Repeater: {
+ auto r = static_cast<model::Repeater *>(obj);
+ vDebug << level << "{ Repeater: name: " << obj->name()
+ << " , a:" << !obj->isStatic()
+ << ", copies:" << r->maxCopies()
+ << ", offset:" << r->offset(0);
+ visitChildren(r->mContent, level);
+ vDebug << level << "} Repeater";
+ break;
+ }
+ case model::Object::Type::Group: {
+ vDebug << level << "{ Group: name: " << obj->name()
+ << " , a:" << !obj->isStatic();
+ visitChildren(static_cast<model::Group *>(obj), level);
+ vDebug << level << "} Group";
+ break;
+ }
+ case model::Object::Type::Layer: {
+ visit(static_cast<model::Layer *>(obj), level);
+ break;
+ }
+ case model::Object::Type::Trim: {
+ vDebug << level << "{ Trim: name: " << obj->name()
+ << " , a:" << !obj->isStatic() << " }";
+ break;
+ }
+ case model::Object::Type::Rect: {
+ vDebug << level << "{ Rect: name: " << obj->name()
+ << " , a:" << !obj->isStatic() << " }";
+ break;
+ }
+ case model::Object::Type::RoundedCorner: {
+ vDebug << level << "{ RoundedCorner: name: " << obj->name()
+ << " , a:" << !obj->isStatic() << " }";
+ break;
+ }
+ case model::Object::Type::Ellipse: {
+ vDebug << level << "{ Ellipse: name: " << obj->name()
+ << " , a:" << !obj->isStatic() << " }";
+ break;
+ }
+ case model::Object::Type::Path: {
+ vDebug << level << "{ Shape: name: " << obj->name()
+ << " , a:" << !obj->isStatic() << " }";
+ break;
+ }
+ case model::Object::Type::Polystar: {
+ vDebug << level << "{ Polystar: name: " << obj->name()
+ << " , a:" << !obj->isStatic() << " }";
+ break;
+ }
+ case model::Object::Type::Transform: {
+ vDebug << level << "{ Transform: name: " << obj->name()
+ << " , a: " << !obj->isStatic() << " }";
+ break;
+ }
+ case model::Object::Type::Stroke: {
+ vDebug << level << "{ Stroke: name: " << obj->name()
+ << " , a:" << !obj->isStatic() << " }";
+ break;
+ }
+ case model::Object::Type::GStroke: {
+ vDebug << level << "{ GStroke: name: " << obj->name()
+ << " , a:" << !obj->isStatic() << " }";
+ break;
+ }
+ case model::Object::Type::Fill: {
+ vDebug << level << "{ Fill: name: " << obj->name()
+ << " , a:" << !obj->isStatic() << " }";
+ break;
+ }
+ case model::Object::Type::GFill: {
+ auto f = static_cast<model::GradientFill *>(obj);
+ vDebug << level << "{ GFill: name: " << obj->name()
+ << " , a:" << !f->isStatic() << ", ty:" << f->mGradientType
+ << ", s:" << f->mStartPoint.value(0)
+ << ", e:" << f->mEndPoint.value(0) << " }";
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ std::string matteType(model::MatteType type)
+ {
+ switch (type) {
+ case model::MatteType::None:
+ return "Matte::None";
+ break;
+ case model::MatteType::Alpha:
+ return "Matte::Alpha";
+ break;
+ case model::MatteType::AlphaInv:
+ return "Matte::AlphaInv";
+ break;
+ case model::MatteType::Luma:
+ return "Matte::Luma";
+ break;
+ case model::MatteType::LumaInv:
+ return "Matte::LumaInv";
+ break;
+ default:
+ return "Matte::Unknown";
+ break;
+ }
+ }
+ std::string layerType(model::Layer::Type type)
+ {
+ switch (type) {
+ case model::Layer::Type::Precomp:
+ return "Layer::Precomp";
+ break;
+ case model::Layer::Type::Null:
+ return "Layer::Null";
+ break;
+ case model::Layer::Type::Shape:
+ return "Layer::Shape";
+ break;
+ case model::Layer::Type::Solid:
+ return "Layer::Solid";
+ break;
+ case model::Layer::Type::Image:
+ return "Layer::Image";
+ break;
+ case model::Layer::Type::Text:
+ return "Layer::Text";
+ break;
+ default:
+ return "Layer::Unknown";
+ break;
+ }
+ }
+};
+
+#endif
+
+std::shared_ptr<model::Composition> model::parse(char * str,
+ std::string dir_path,
+ model::ColorFilter filter)
+{
+ LottieParserImpl obj(str, std::move(dir_path), std::move(filter));
+
+ if (obj.VerifyType()) {
+ obj.parseComposition();
+ auto composition = obj.composition();
+ if (composition) {
+ composition->processRepeaterObjects();
+ composition->updateStats();
+
+#ifdef LOTTIE_DUMP_TREE_SUPPORT
+ ObjectInspector inspector;
+ inspector.visit(composition.get(), "");
+#endif
+
+ return composition;
+ }
+ }
+
+ vWarning << "Input data is not Lottie format!";
+ return {};
+}
+
+RAPIDJSON_DIAG_POP