1 /*
2  * Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved.
3 
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10 
11  * The above copyright notice and this permission notice shall be included in all
12  * copies or substantial portions of the Software.
13 
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20  * SOFTWARE.
21  */
22 
23 #include "../../lv_conf_internal.h"
24 #if LV_USE_THORVG_INTERNAL
25 
26 #include "tvgStr.h"
27 #include "tvgCompressor.h"
28 #include "tvgLottieModel.h"
29 #include "tvgLottieParser.h"
30 #include "tvgLottieExpressions.h"
31 
32 
33 /************************************************************************/
34 /* Internal Class Implementation                                        */
35 /************************************************************************/
36 
37 #define KEY_AS(name) !strcmp(key, name)
38 
39 
_expression(char * code,LottieComposition * comp,LottieLayer * layer,LottieObject * object,LottieProperty * property)40 static LottieExpression* _expression(char* code, LottieComposition* comp, LottieLayer* layer, LottieObject* object, LottieProperty* property)
41 {
42     if (!comp->expressions) comp->expressions = true;
43 
44     auto inst = new LottieExpression;
45     inst->code = code;
46     inst->comp = comp;
47     inst->layer = layer;
48     inst->object = object;
49     inst->property = property;
50 
51     return inst;
52 }
53 
54 
_int2str(int num)55 static unsigned long _int2str(int num)
56 {
57     char str[20];
58     snprintf(str, 20, "%d", num);
59     return djb2Encode(str);
60 }
61 
62 
getEffect(int type)63 LottieEffect* LottieParser::getEffect(int type)
64 {
65     switch (type) {
66         case 29: return new LottieGaussianBlur;
67         default: return nullptr;
68     }
69 }
70 
71 
getMaskMethod(bool inversed)72 CompositeMethod LottieParser::getMaskMethod(bool inversed)
73 {
74     auto mode = getString();
75     if (!mode) return CompositeMethod::None;
76 
77     switch (mode[0]) {
78         case 'a': {
79             if (inversed) return CompositeMethod::InvAlphaMask;
80             else return CompositeMethod::AddMask;
81         }
82         case 's': return CompositeMethod::SubtractMask;
83         case 'i': return CompositeMethod::IntersectMask;
84         case 'f': return CompositeMethod::DifferenceMask;
85         case 'l': return CompositeMethod::LightenMask;
86         case 'd': return CompositeMethod::DarkenMask;
87         default: return CompositeMethod::None;
88     }
89 }
90 
91 
getColor(const char * str)92 RGB24 LottieParser::getColor(const char *str)
93 {
94     RGB24 color = {0, 0, 0};
95 
96     if (!str) return color;
97 
98     auto len = strlen(str);
99 
100     // some resource has empty color string, return a default color for those cases.
101     if (len != 7 || str[0] != '#') return color;
102 
103     char tmp[3] = {'\0', '\0', '\0'};
104     tmp[0] = str[1];
105     tmp[1] = str[2];
106     color.rgb[0] = uint8_t(strtol(tmp, nullptr, 16));
107 
108     tmp[0] = str[3];
109     tmp[1] = str[4];
110     color.rgb[1] = uint8_t(strtol(tmp, nullptr, 16));
111 
112     tmp[0] = str[5];
113     tmp[1] = str[6];
114     color.rgb[2] = uint8_t(strtol(tmp, nullptr, 16));
115 
116     return color;
117 }
118 
119 
getFillRule()120 FillRule LottieParser::getFillRule()
121 {
122     switch (getInt()) {
123         case 1: return FillRule::Winding;
124         default: return FillRule::EvenOdd;
125     }
126 }
127 
128 
getMatteType()129 CompositeMethod LottieParser::getMatteType()
130 {
131     switch (getInt()) {
132         case 1: return CompositeMethod::AlphaMask;
133         case 2: return CompositeMethod::InvAlphaMask;
134         case 3: return CompositeMethod::LumaMask;
135         case 4: return CompositeMethod::InvLumaMask;
136         default: return CompositeMethod::None;
137     }
138 }
139 
140 
getStrokeCap()141 StrokeCap LottieParser::getStrokeCap()
142 {
143     switch (getInt()) {
144         case 1: return StrokeCap::Butt;
145         case 2: return StrokeCap::Round;
146         default: return StrokeCap::Square;
147     }
148 }
149 
150 
getStrokeJoin()151 StrokeJoin LottieParser::getStrokeJoin()
152 {
153     switch (getInt()) {
154         case 1: return StrokeJoin::Miter;
155         case 2: return StrokeJoin::Round;
156         default: return StrokeJoin::Bevel;
157     }
158 }
159 
160 
getValue(TextDocument & doc)161 void LottieParser::getValue(TextDocument& doc)
162 {
163     enterObject();
164     while (auto key = nextObjectKey()) {
165         if (KEY_AS("s")) doc.size = getFloat() * 0.01f;
166         else if (KEY_AS("f")) doc.name = getStringCopy();
167         else if (KEY_AS("t")) doc.text = getStringCopy();
168         else if (KEY_AS("j")) doc.justify = getInt();
169         else if (KEY_AS("tr")) doc.tracking = getFloat() * 0.1f;
170         else if (KEY_AS("lh")) doc.height = getFloat();
171         else if (KEY_AS("ls")) doc.shift = getFloat();
172         else if (KEY_AS("fc")) getValue(doc.color);
173         else if (KEY_AS("ps")) getValue(doc.bbox.pos);
174         else if (KEY_AS("sz")) getValue(doc.bbox.size);
175         else if (KEY_AS("sc")) getValue(doc.stroke.color);
176         else if (KEY_AS("sw")) doc.stroke.width = getFloat();
177         else if (KEY_AS("of")) doc.stroke.render = getBool();
178         else skip(key);
179     }
180 }
181 
182 
getValue(PathSet & path)183 void LottieParser::getValue(PathSet& path)
184 {
185     Array<Point> outs, ins, pts;
186     bool closed = false;
187 
188     /* The shape object could be wrapped by a array
189        if its part of the keyframe object */
190     auto arrayWrapper = (peekType() == kArrayType) ? true : false;
191     if (arrayWrapper) enterArray();
192 
193     enterObject();
194     while (auto key = nextObjectKey()) {
195         if (KEY_AS("i")) getValue(ins);
196         else if (KEY_AS("o")) getValue(outs);
197         else if (KEY_AS("v")) getValue(pts);
198         else if (KEY_AS("c")) closed = getBool();
199         else skip(key);
200     }
201 
202     //exit properly from the array
203     if (arrayWrapper) nextArrayValue();
204 
205     //valid path data?
206     if (ins.empty() || outs.empty() || pts.empty()) return;
207     if (ins.count != outs.count || outs.count != pts.count) return;
208 
209     //convert path
210     auto out = outs.begin();
211     auto in = ins.begin();
212     auto pt = pts.begin();
213 
214     //Store manipulated results
215     Array<Point> outPts;
216     Array<PathCommand> outCmds;
217 
218     //Reuse the buffers
219     outPts.data = path.pts;
220     outPts.reserved = path.ptsCnt;
221     outCmds.data = path.cmds;
222     outCmds.reserved = path.cmdsCnt;
223 
224     size_t extra = closed ? 3 : 0;
225     outPts.reserve(pts.count * 3 + 1 + extra);
226     outCmds.reserve(pts.count + 2);
227 
228     outCmds.push(PathCommand::MoveTo);
229     outPts.push(*pt);
230 
231     for (++pt, ++out, ++in; pt < pts.end(); ++pt, ++out, ++in) {
232         outCmds.push(PathCommand::CubicTo);
233         outPts.push(*(pt - 1) + *(out - 1));
234         outPts.push(*pt + *in);
235         outPts.push(*pt);
236     }
237 
238     if (closed) {
239         outPts.push(pts.last() + outs.last());
240         outPts.push(pts.first() + ins.first());
241         outPts.push(pts.first());
242         outCmds.push(PathCommand::CubicTo);
243         outCmds.push(PathCommand::Close);
244     }
245 
246     path.pts = outPts.data;
247     path.cmds = outCmds.data;
248     path.ptsCnt = outPts.count;
249     path.cmdsCnt = outCmds.count;
250 
251     outPts.data = nullptr;
252     outCmds.data = nullptr;
253 }
254 
255 
getValue(ColorStop & color)256 void LottieParser::getValue(ColorStop& color)
257 {
258     if (peekType() == kArrayType) enterArray();
259 
260     if (!color.input) color.input = new Array<float>(static_cast<LottieGradient*>(context.parent)->colorStops.count * 6);
261     else color.input->clear();
262 
263     while (nextArrayValue()) color.input->push(getFloat());
264 }
265 
266 
getValue(Array<Point> & pts)267 void LottieParser::getValue(Array<Point>& pts)
268 {
269     enterArray();
270     while (nextArrayValue()) {
271         enterArray();
272         Point pt;
273         getValue(pt);
274         pts.push(pt);
275     }
276 }
277 
278 
getValue(int8_t & val)279 void LottieParser::getValue(int8_t& val)
280 {
281     if (peekType() == kArrayType) {
282         enterArray();
283         if (nextArrayValue()) val = getInt();
284         //discard rest
285         while (nextArrayValue()) getInt();
286     } else {
287         val = getFloat();
288     }
289 }
290 
291 
getValue(uint8_t & val)292 void LottieParser::getValue(uint8_t& val)
293 {
294     if (peekType() == kArrayType) {
295         enterArray();
296         if (nextArrayValue()) val = (uint8_t)(getFloat() * 2.55f);
297         //discard rest
298         while (nextArrayValue()) getFloat();
299     } else {
300         val = (uint8_t)(getFloat() * 2.55f);
301     }
302 }
303 
304 
getValue(float & val)305 void LottieParser::getValue(float& val)
306 {
307     if (peekType() == kArrayType) {
308         enterArray();
309         if (nextArrayValue()) val = getFloat();
310         //discard rest
311         while (nextArrayValue()) getFloat();
312     } else {
313         val = getFloat();
314     }
315 }
316 
317 
getValue(Point & pt)318 bool LottieParser::getValue(Point& pt)
319 {
320     auto type = peekType();
321     if (type == kNullType) return false;
322 
323     int i = 0;
324     auto ptr = (float*)(&pt);
325 
326     if (type == kArrayType) enterArray();
327 
328     while (nextArrayValue()) {
329         auto val = getFloat();
330         if (i < 2) ptr[i++] = val;
331     }
332 
333     return true;
334 }
335 
336 
getValue(RGB24 & color)337 void LottieParser::getValue(RGB24& color)
338 {
339     int i = 0;
340 
341     if (peekType() == kArrayType) enterArray();
342 
343     while (nextArrayValue()) {
344         auto val = getFloat();
345         if (i < 3) color.rgb[i++] = REMAP255(val);
346     }
347 
348     //TODO: color filter?
349 }
350 
351 
getInterpolatorPoint(Point & pt)352 void LottieParser::getInterpolatorPoint(Point& pt)
353 {
354     enterObject();
355     while (auto key = nextObjectKey()) {
356         if (KEY_AS("x")) getValue(pt.x);
357         else if (KEY_AS("y")) getValue(pt.y);
358     }
359 }
360 
361 
362 template<LottieProperty::Type type, typename T>
parseSlotProperty(T & prop)363 void LottieParser::parseSlotProperty(T& prop)
364 {
365     while (auto key = nextObjectKey()) {
366         if (KEY_AS("p")) parseProperty<type>(prop);
367         else skip(key);
368     }
369 }
370 
371 
372 template<typename T>
parseTangent(const char * key,LottieVectorFrame<T> & value)373 bool LottieParser::parseTangent(const char *key, LottieVectorFrame<T>& value)
374 {
375     if (KEY_AS("ti") && getValue(value.inTangent)) ;
376     else if (KEY_AS("to") && getValue(value.outTangent)) ;
377     else return false;
378 
379     value.hasTangent = true;
380     return true;
381 }
382 
383 
384 template<typename T>
parseTangent(const char * key,LottieScalarFrame<T> & value)385 bool LottieParser::parseTangent(const char *key, LottieScalarFrame<T>& value)
386 {
387     return false;
388 }
389 
390 
getInterpolator(const char * key,Point & in,Point & out)391 LottieInterpolator* LottieParser::getInterpolator(const char* key, Point& in, Point& out)
392 {
393     char buf[20];
394 
395     if (!key) {
396         snprintf(buf, sizeof(buf), "%.2f_%.2f_%.2f_%.2f", in.x, in.y, out.x, out.y);
397         key = buf;
398     }
399 
400     LottieInterpolator* interpolator = nullptr;
401 
402     //get a cached interpolator if it has any.
403     for (auto i = comp->interpolators.begin(); i < comp->interpolators.end(); ++i) {
404         if (!strncmp((*i)->key, key, sizeof(buf))) interpolator = *i;
405     }
406 
407     //new interpolator
408     if (!interpolator) {
409         interpolator = static_cast<LottieInterpolator*>(malloc(sizeof(LottieInterpolator)));
410         interpolator->set(key, in, out);
411         comp->interpolators.push(interpolator);
412     }
413 
414     return interpolator;
415 }
416 
417 
418 template<typename T>
parseKeyFrame(T & prop)419 void LottieParser::parseKeyFrame(T& prop)
420 {
421     Point inTangent, outTangent;
422     const char* interpolatorKey = nullptr;
423     auto& frame = prop.newFrame();
424     auto interpolator = false;
425 
426     enterObject();
427 
428     while (auto key = nextObjectKey()) {
429         if (KEY_AS("i")) {
430             interpolator = true;
431             getInterpolatorPoint(inTangent);
432         } else if (KEY_AS("o")) {
433             getInterpolatorPoint(outTangent);
434         } else if (KEY_AS("n")) {
435             if (peekType() == kStringType) {
436                 interpolatorKey = getString();
437             } else {
438                 enterArray();
439                 while (nextArrayValue()) {
440                     if (!interpolatorKey) interpolatorKey = getString();
441                     else skip(nullptr);
442                 }
443             }
444         } else if (KEY_AS("t")) {
445             frame.no = getFloat();
446         } else if (KEY_AS("s")) {
447             getValue(frame.value);
448         } else if (KEY_AS("e")) {
449             //current end frame and the next start frame is duplicated,
450             //We propagate the end value to the next frame to avoid having duplicated values.
451             auto& frame2 = prop.nextFrame();
452             getValue(frame2.value);
453         } else if (parseTangent(key, frame)) {
454             continue;
455         } else if (KEY_AS("h")) {
456             frame.hold = getInt();
457         } else skip(key);
458     }
459 
460     if (interpolator) {
461         frame.interpolator = getInterpolator(interpolatorKey, inTangent, outTangent);
462     }
463 }
464 
465 template<typename T>
parsePropertyInternal(T & prop)466 void LottieParser::parsePropertyInternal(T& prop)
467 {
468     //single value property
469     if (peekType() == kNumberType) {
470         getValue(prop.value);
471     //multi value property
472     } else {
473         //TODO: Here might be a single frame.
474         //Can we figure out the frame number in advance?
475         enterArray();
476         while (nextArrayValue()) {
477             //keyframes value
478             if (peekType() == kObjectType) {
479                 parseKeyFrame(prop);
480             //multi value property with no keyframes
481             } else {
482                 getValue(prop.value);
483                 break;
484             }
485         }
486         prop.prepare();
487     }
488 }
489 
490 
491 template<LottieProperty::Type type, typename T>
parseProperty(T & prop,LottieObject * obj)492 void LottieParser::parseProperty(T& prop, LottieObject* obj)
493 {
494     enterObject();
495     while (auto key = nextObjectKey()) {
496         if (KEY_AS("k")) parsePropertyInternal(prop);
497         else if (obj && KEY_AS("sid")) {
498             auto sid = getStringCopy();
499             //append object if the slot already exists.
500             for (auto slot = comp->slots.begin(); slot < comp->slots.end(); ++slot) {
501                 if (strcmp((*slot)->sid, sid)) continue;
502                 (*slot)->pairs.push({obj, 0});
503                 break;
504             }
505             comp->slots.push(new LottieSlot(sid, obj, type));
506         } else if (KEY_AS("x")) {
507             prop.exp = _expression(getStringCopy(), comp, context.layer, context.parent, &prop);
508         } else if (KEY_AS("ix")) {
509             prop.ix = getInt();
510         } else skip(key);
511     }
512     prop.type = type;
513 }
514 
515 
parseCommon(LottieObject * obj,const char * key)516 bool LottieParser::parseCommon(LottieObject* obj, const char* key)
517 {
518     if (KEY_AS("nm")) {
519         obj->id = djb2Encode(getString());
520         return true;
521     } else if (KEY_AS("hd")) {
522         obj->hidden = getBool();
523         return true;
524     } else return false;
525 }
526 
527 
parseDirection(LottieShape * shape,const char * key)528 bool LottieParser::parseDirection(LottieShape* shape, const char* key)
529 {
530     if (KEY_AS("d")) {
531         if (getInt() == 3) {
532             shape->clockwise = false;       //default is true
533         }
534         return true;
535     }
536     return false;
537 }
538 
539 
parseRect()540 LottieRect* LottieParser::parseRect()
541 {
542     auto rect = new LottieRect;
543 
544     context.parent = rect;
545 
546     while (auto key = nextObjectKey()) {
547         if (parseCommon(rect, key)) continue;
548         else if (KEY_AS("s")) parseProperty<LottieProperty::Type::Point>(rect->size);
549         else if (KEY_AS("p")) parseProperty<LottieProperty::Type::Position>(rect->position);
550         else if (KEY_AS("r")) parseProperty<LottieProperty::Type::Float>(rect->radius);
551         else if (parseDirection(rect, key)) continue;
552         else skip(key);
553     }
554     rect->prepare();
555     return rect;
556 }
557 
558 
parseEllipse()559 LottieEllipse* LottieParser::parseEllipse()
560 {
561     auto ellipse = new LottieEllipse;
562 
563     context.parent = ellipse;
564 
565     while (auto key = nextObjectKey()) {
566         if (parseCommon(ellipse, key)) continue;
567         else if (KEY_AS("p")) parseProperty<LottieProperty::Type::Position>(ellipse->position);
568         else if (KEY_AS("s")) parseProperty<LottieProperty::Type::Point>(ellipse->size);
569         else if (parseDirection(ellipse, key)) continue;
570         else skip(key);
571     }
572     ellipse->prepare();
573     return ellipse;
574 }
575 
576 
parseTransform(bool ddd)577 LottieTransform* LottieParser::parseTransform(bool ddd)
578 {
579     auto transform = new LottieTransform;
580 
581     context.parent = transform;
582 
583     if (ddd) {
584         transform->rotationEx = new LottieTransform::RotationEx;
585         TVGLOG("LOTTIE", "3D transform(ddd) is not totally compatible.");
586     }
587 
588     while (auto key = nextObjectKey()) {
589         if (parseCommon(transform, key)) continue;
590         else if (KEY_AS("p"))
591         {
592             enterObject();
593             while (auto key = nextObjectKey()) {
594                 if (KEY_AS("k")) parsePropertyInternal(transform->position);
595                 else if (KEY_AS("s") && getBool()) transform->coords = new LottieTransform::SeparateCoord;
596                 //check separateCoord to figure out whether "x(expression)" / "x(coord)"
597                 else if (transform->coords && KEY_AS("x")) parseProperty<LottieProperty::Type::Float>(transform->coords->x);
598                 else if (transform->coords && KEY_AS("y")) parseProperty<LottieProperty::Type::Float>(transform->coords->y);
599                 else if (KEY_AS("x")) transform->position.exp = _expression(getStringCopy(), comp, context.layer, context.parent, &transform->position);
600                 else skip(key);
601             }
602             transform->position.type = LottieProperty::Type::Position;
603         }
604         else if (KEY_AS("a")) parseProperty<LottieProperty::Type::Point>(transform->anchor);
605         else if (KEY_AS("s")) parseProperty<LottieProperty::Type::Point>(transform->scale);
606         else if (KEY_AS("r")) parseProperty<LottieProperty::Type::Float>(transform->rotation);
607         else if (KEY_AS("o")) parseProperty<LottieProperty::Type::Opacity>(transform->opacity);
608         else if (transform->rotationEx && KEY_AS("rx")) parseProperty<LottieProperty::Type::Float>(transform->rotationEx->x);
609         else if (transform->rotationEx && KEY_AS("ry")) parseProperty<LottieProperty::Type::Float>(transform->rotationEx->y);
610         else if (transform->rotationEx && KEY_AS("rz")) parseProperty<LottieProperty::Type::Float>(transform->rotation);
611         else if (KEY_AS("sk")) parseProperty<LottieProperty::Type::Float>(transform->skewAngle);
612         else if (KEY_AS("sa")) parseProperty<LottieProperty::Type::Float>(transform->skewAxis);
613         else skip(key);
614     }
615     transform->prepare();
616     return transform;
617 }
618 
619 
parseSolidFill()620 LottieSolidFill* LottieParser::parseSolidFill()
621 {
622     auto fill = new LottieSolidFill;
623 
624     context.parent = fill;
625 
626     while (auto key = nextObjectKey()) {
627         if (parseCommon(fill, key)) continue;
628         else if (KEY_AS("c")) parseProperty<LottieProperty::Type::Color>(fill->color, fill);
629         else if (KEY_AS("o")) parseProperty<LottieProperty::Type::Opacity>(fill->opacity, fill);
630         else if (KEY_AS("fillEnabled")) fill->hidden |= !getBool();
631         else if (KEY_AS("r")) fill->rule = getFillRule();
632         else skip(key);
633     }
634     fill->prepare();
635     return fill;
636 }
637 
638 
parseStrokeDash(LottieStroke * stroke)639 void LottieParser::parseStrokeDash(LottieStroke* stroke)
640 {
641     enterArray();
642     while (nextArrayValue()) {
643         enterObject();
644         int idx = 0;
645         while (auto key = nextObjectKey()) {
646             if (KEY_AS("n")) {
647                 auto style = getString();
648                 if (!strcmp("o", style)) idx = 0;           //offset
649                 else if (!strcmp("d", style)) idx = 1;      //dash
650                 else if (!strcmp("g", style)) idx = 2;      //gap
651             } else if (KEY_AS("v")) {
652                 parseProperty<LottieProperty::Type::Float>(stroke->dash(idx));
653             } else skip(key);
654         }
655     }
656 }
657 
658 
parseSolidStroke()659 LottieSolidStroke* LottieParser::parseSolidStroke()
660 {
661     auto stroke = new LottieSolidStroke;
662 
663     context.parent = stroke;
664 
665     while (auto key = nextObjectKey()) {
666         if (parseCommon(stroke, key)) continue;
667         else if (KEY_AS("c")) parseProperty<LottieProperty::Type::Color>(stroke->color, stroke);
668         else if (KEY_AS("o")) parseProperty<LottieProperty::Type::Opacity>(stroke->opacity, stroke);
669         else if (KEY_AS("w")) parseProperty<LottieProperty::Type::Float>(stroke->width, stroke);
670         else if (KEY_AS("lc")) stroke->cap = getStrokeCap();
671         else if (KEY_AS("lj")) stroke->join = getStrokeJoin();
672         else if (KEY_AS("ml")) stroke->miterLimit = getFloat();
673         else if (KEY_AS("fillEnabled")) stroke->hidden |= !getBool();
674         else if (KEY_AS("d")) parseStrokeDash(stroke);
675         else skip(key);
676     }
677     stroke->prepare();
678     return stroke;
679 }
680 
681 
getPathSet(LottiePathSet & path)682 void LottieParser::getPathSet(LottiePathSet& path)
683 {
684     enterObject();
685     while (auto key = nextObjectKey()) {
686         if (KEY_AS("k")) {
687             if (peekType() == kArrayType) {
688                 enterArray();
689                 while (nextArrayValue()) parseKeyFrame(path);
690             } else {
691                 getValue(path.value);
692             }
693         } else if (KEY_AS("x")) {
694             path.exp = _expression(getStringCopy(), comp, context.layer, context.parent, &path);
695         } else skip(key);
696     }
697     path.type = LottieProperty::Type::PathSet;
698 }
699 
700 
parsePath()701 LottiePath* LottieParser::parsePath()
702 {
703     auto path = new LottiePath;
704 
705     while (auto key = nextObjectKey()) {
706         if (parseCommon(path, key)) continue;
707         else if (KEY_AS("ks")) getPathSet(path->pathset);
708         else if (parseDirection(path, key)) continue;
709         else skip(key);
710     }
711     path->prepare();
712     return path;
713 }
714 
715 
parsePolyStar()716 LottiePolyStar* LottieParser::parsePolyStar()
717 {
718     auto star = new LottiePolyStar;
719 
720     context.parent = star;
721 
722     while (auto key = nextObjectKey()) {
723         if (parseCommon(star, key)) continue;
724         else if (KEY_AS("p")) parseProperty<LottieProperty::Type::Position>(star->position);
725         else if (KEY_AS("pt")) parseProperty<LottieProperty::Type::Float>(star->ptsCnt);
726         else if (KEY_AS("ir")) parseProperty<LottieProperty::Type::Float>(star->innerRadius);
727         else if (KEY_AS("is")) parseProperty<LottieProperty::Type::Float>(star->innerRoundness);
728         else if (KEY_AS("or")) parseProperty<LottieProperty::Type::Float>(star->outerRadius);
729         else if (KEY_AS("os")) parseProperty<LottieProperty::Type::Float>(star->outerRoundness);
730         else if (KEY_AS("r")) parseProperty<LottieProperty::Type::Float>(star->rotation);
731         else if (KEY_AS("sy")) star->type = (LottiePolyStar::Type) getInt();
732         else if (parseDirection(star, key)) continue;
733         else skip(key);
734     }
735     star->prepare();
736     return star;
737 }
738 
739 
parseRoundedCorner()740 LottieRoundedCorner* LottieParser::parseRoundedCorner()
741 {
742     auto corner = new LottieRoundedCorner;
743 
744     context.parent = corner;
745 
746     while (auto key = nextObjectKey()) {
747         if (parseCommon(corner, key)) continue;
748         else if (KEY_AS("r")) parseProperty<LottieProperty::Type::Float>(corner->radius);
749         else skip(key);
750     }
751     corner->prepare();
752     return corner;
753 }
754 
755 
parseGradient(LottieGradient * gradient,const char * key)756 void LottieParser::parseGradient(LottieGradient* gradient, const char* key)
757 {
758     if (KEY_AS("t")) gradient->id = getInt();
759     else if (KEY_AS("o")) parseProperty<LottieProperty::Type::Opacity>(gradient->opacity, gradient);
760     else if (KEY_AS("g"))
761     {
762         enterObject();
763         while (auto key = nextObjectKey()) {
764             if (KEY_AS("p")) gradient->colorStops.count = getInt();
765             else if (KEY_AS("k")) parseProperty<LottieProperty::Type::ColorStop>(gradient->colorStops, gradient);
766             else skip(key);
767         }
768     }
769     else if (KEY_AS("s")) parseProperty<LottieProperty::Type::Point>(gradient->start, gradient);
770     else if (KEY_AS("e")) parseProperty<LottieProperty::Type::Point>(gradient->end, gradient);
771     else if (KEY_AS("h")) parseProperty<LottieProperty::Type::Float>(gradient->height, gradient);
772     else if (KEY_AS("a")) parseProperty<LottieProperty::Type::Float>(gradient->angle, gradient);
773     else skip(key);
774 }
775 
776 
parseGradientFill()777 LottieGradientFill* LottieParser::parseGradientFill()
778 {
779     auto fill = new LottieGradientFill;
780 
781     context.parent = fill;
782 
783     while (auto key = nextObjectKey()) {
784         if (parseCommon(fill, key)) continue;
785         else if (KEY_AS("r")) fill->rule = getFillRule();
786         else parseGradient(fill, key);
787     }
788 
789     fill->prepare();
790 
791     return fill;
792 }
793 
794 
parseGradientStroke()795 LottieGradientStroke* LottieParser::parseGradientStroke()
796 {
797     auto stroke = new LottieGradientStroke;
798 
799     context.parent = stroke;
800 
801     while (auto key = nextObjectKey()) {
802         if (parseCommon(stroke, key)) continue;
803         else if (KEY_AS("lc")) stroke->cap = getStrokeCap();
804         else if (KEY_AS("lj")) stroke->join = getStrokeJoin();
805         else if (KEY_AS("ml")) stroke->miterLimit = getFloat();
806         else if (KEY_AS("w")) parseProperty<LottieProperty::Type::Float>(stroke->width);
807         else if (KEY_AS("d")) parseStrokeDash(stroke);
808         else parseGradient(stroke, key);
809     }
810     stroke->prepare();
811 
812     return stroke;
813 }
814 
815 
parseTrimpath()816 LottieTrimpath* LottieParser::parseTrimpath()
817 {
818     auto trim = new LottieTrimpath;
819 
820     context.parent = trim;
821 
822     while (auto key = nextObjectKey()) {
823         if (parseCommon(trim, key)) continue;
824         else if (KEY_AS("s")) parseProperty<LottieProperty::Type::Float>(trim->start);
825         else if (KEY_AS("e")) parseProperty<LottieProperty::Type::Float>(trim->end);
826         else if (KEY_AS("o")) parseProperty<LottieProperty::Type::Float>(trim->offset);
827         else if (KEY_AS("m")) trim->type = static_cast<LottieTrimpath::Type>(getInt());
828         else skip(key);
829     }
830     trim->prepare();
831 
832     return trim;
833 }
834 
835 
parseRepeater()836 LottieRepeater* LottieParser::parseRepeater()
837 {
838     auto repeater = new LottieRepeater;
839 
840     context.parent = repeater;
841 
842     while (auto key = nextObjectKey()) {
843         if (parseCommon(repeater, key)) continue;
844         else if (KEY_AS("c")) parseProperty<LottieProperty::Type::Float>(repeater->copies);
845         else if (KEY_AS("o")) parseProperty<LottieProperty::Type::Float>(repeater->offset);
846         else if (KEY_AS("m")) repeater->inorder = getInt() == 2;
847         else if (KEY_AS("tr"))
848         {
849             enterObject();
850             while (auto key = nextObjectKey()) {
851                 if (KEY_AS("a")) parseProperty<LottieProperty::Type::Point>(repeater->anchor);
852                 else if (KEY_AS("p")) parseProperty<LottieProperty::Type::Position>(repeater->position);
853                 else if (KEY_AS("r")) parseProperty<LottieProperty::Type::Float>(repeater->rotation);
854                 else if (KEY_AS("s")) parseProperty<LottieProperty::Type::Point>(repeater->scale);
855                 else if (KEY_AS("so")) parseProperty<LottieProperty::Type::Opacity>(repeater->startOpacity);
856                 else if (KEY_AS("eo")) parseProperty<LottieProperty::Type::Opacity>(repeater->endOpacity);
857                 else skip(key);
858             }
859         }
860         else skip(key);
861     }
862     repeater->prepare();
863 
864     return repeater;
865 }
866 
867 
parseOffsetPath()868 LottieOffsetPath* LottieParser::parseOffsetPath()
869 {
870     auto offsetPath = new LottieOffsetPath;
871 
872     context.parent = offsetPath;
873 
874     while (auto key = nextObjectKey()) {
875         if (parseCommon(offsetPath, key)) continue;
876         else if (KEY_AS("a")) parseProperty<LottieProperty::Type::Float>(offsetPath->offset);
877         else if (KEY_AS("lj")) offsetPath->join = getStrokeJoin();
878         else if (KEY_AS("ml")) parseProperty<LottieProperty::Type::Float>(offsetPath->miterLimit);
879         else skip(key);
880     }
881     offsetPath->prepare();
882 
883     return offsetPath;
884 }
885 
886 
parseObject()887 LottieObject* LottieParser::parseObject()
888 {
889     auto type = getString();
890     if (!type) return nullptr;
891 
892     if (!strcmp(type, "gr")) return parseGroup();
893     else if (!strcmp(type, "rc")) return parseRect();
894     else if (!strcmp(type, "el")) return parseEllipse();
895     else if (!strcmp(type, "tr")) return parseTransform();
896     else if (!strcmp(type, "fl")) return parseSolidFill();
897     else if (!strcmp(type, "st")) return parseSolidStroke();
898     else if (!strcmp(type, "sh")) return parsePath();
899     else if (!strcmp(type, "sr")) return parsePolyStar();
900     else if (!strcmp(type, "rd")) return parseRoundedCorner();
901     else if (!strcmp(type, "gf")) return parseGradientFill();
902     else if (!strcmp(type, "gs")) return parseGradientStroke();
903     else if (!strcmp(type, "tm")) return parseTrimpath();
904     else if (!strcmp(type, "rp")) return parseRepeater();
905     else if (!strcmp(type, "mm")) TVGLOG("LOTTIE", "MergePath(mm) is not supported yet");
906     else if (!strcmp(type, "pb")) TVGLOG("LOTTIE", "Puker/Bloat(pb) is not supported yet");
907     else if (!strcmp(type, "tw")) TVGLOG("LOTTIE", "Twist(tw) is not supported yet");
908     else if (!strcmp(type, "op")) return parseOffsetPath();
909     else if (!strcmp(type, "zz")) TVGLOG("LOTTIE", "ZigZag(zz) is not supported yet");
910     return nullptr;
911 }
912 
913 
parseObject(Array<LottieObject * > & parent)914 void LottieParser::parseObject(Array<LottieObject*>& parent)
915 {
916     enterObject();
917     while (auto key = nextObjectKey()) {
918         if (KEY_AS("ty")) {
919             if (auto child = parseObject()) {
920                 if (child->hidden) delete(child);
921                 else parent.push(child);
922             }
923         } else skip(key);
924     }
925 }
926 
927 
parseImage(const char * data,const char * subPath,bool embedded,float width,float height)928 LottieImage* LottieParser::parseImage(const char* data, const char* subPath, bool embedded, float width, float height)
929 {
930     //Used for Image Asset
931     auto image = new LottieImage;
932 
933     //embedded image resource. should start with "data:"
934     //header look like "data:image/png;base64," so need to skip till ','.
935     if (embedded && !strncmp(data, "data:", 5)) {
936         //figure out the mimetype
937         auto mimeType = data + 11;
938         auto needle = strstr(mimeType, ";");
939         image->mimeType = strDuplicate(mimeType, needle - mimeType);
940         //b64 data
941         auto b64Data = strstr(data, ",") + 1;
942         size_t length = strlen(data) - (b64Data - data);
943         image->size = b64Decode(b64Data, length, &image->b64Data);
944     //external image resource
945     } else {
946         auto len = strlen(dirName) + strlen(subPath) + strlen(data) + 1;
947         image->path = static_cast<char*>(malloc(len));
948         snprintf(image->path, len, "%s%s%s", dirName, subPath, data);
949     }
950 
951     image->width = width;
952     image->height = height;
953     image->prepare();
954 
955     return image;
956 }
957 
958 
parseAsset()959 LottieObject* LottieParser::parseAsset()
960 {
961     enterObject();
962 
963     LottieObject* obj = nullptr;
964     unsigned long id = 0;
965 
966     //Used for Image Asset
967     const char* data = nullptr;
968     const char* subPath = nullptr;
969     float width = 0.0f;
970     float height = 0.0f;
971     auto embedded = false;
972 
973     while (auto key = nextObjectKey()) {
974         if (KEY_AS("id"))
975         {
976             if (peekType() == kStringType) {
977                 id = djb2Encode(getString());
978             } else {
979                 id = _int2str(getInt());
980             }
981         }
982         else if (KEY_AS("layers")) obj = parseLayers(comp->root);
983         else if (KEY_AS("u")) subPath = getString();
984         else if (KEY_AS("p")) data = getString();
985         else if (KEY_AS("w")) width = getFloat();
986         else if (KEY_AS("h")) height = getFloat();
987         else if (KEY_AS("e")) embedded = getInt();
988         else skip(key);
989     }
990     if (data) obj = parseImage(data, subPath, embedded, width, height);
991     if (obj) obj->id = id;
992     return obj;
993 }
994 
995 
parseFont()996 LottieFont* LottieParser::parseFont()
997 {
998     enterObject();
999 
1000     auto font = new LottieFont;
1001 
1002     while (auto key = nextObjectKey()) {
1003         if (KEY_AS("fName")) font->name = getStringCopy();
1004         else if (KEY_AS("fFamily")) font->family = getStringCopy();
1005         else if (KEY_AS("fStyle")) font->style = getStringCopy();
1006         else if (KEY_AS("ascent")) font->ascent = getFloat();
1007         else if (KEY_AS("origin")) font->origin = (LottieFont::Origin) getInt();
1008         else skip(key);
1009     }
1010     return font;
1011 }
1012 
1013 
parseAssets()1014 void LottieParser::parseAssets()
1015 {
1016     enterArray();
1017     while (nextArrayValue()) {
1018         auto asset = parseAsset();
1019         if (asset) comp->assets.push(asset);
1020         else TVGERR("LOTTIE", "Invalid Asset!");
1021     }
1022 }
1023 
parseMarker()1024 LottieMarker* LottieParser::parseMarker()
1025 {
1026     enterObject();
1027 
1028     auto marker = new LottieMarker;
1029 
1030     while (auto key = nextObjectKey()) {
1031         if (KEY_AS("cm")) marker->name = getStringCopy();
1032         else if (KEY_AS("tm")) marker->time = getFloat();
1033         else if (KEY_AS("dr")) marker->duration = getFloat();
1034         else skip(key);
1035     }
1036 
1037     return marker;
1038 }
1039 
parseMarkers()1040 void LottieParser::parseMarkers()
1041 {
1042     enterArray();
1043     while (nextArrayValue()) {
1044         comp->markers.push(parseMarker());
1045     }
1046 }
1047 
parseChars(Array<LottieGlyph * > & glyphs)1048 void LottieParser::parseChars(Array<LottieGlyph*>& glyphs)
1049 {
1050     enterArray();
1051     while (nextArrayValue()) {
1052         enterObject();
1053         //a new glyph
1054         auto glyph = new LottieGlyph;
1055         while (auto key = nextObjectKey()) {
1056             if (KEY_AS("ch")) glyph->code = getStringCopy();
1057             else if (KEY_AS("size")) glyph->size = static_cast<uint16_t>(getFloat());
1058             else if (KEY_AS("style")) glyph->style = getStringCopy();
1059             else if (KEY_AS("w")) glyph->width = getFloat();
1060             else if (KEY_AS("fFamily")) glyph->family = getStringCopy();
1061             else if (KEY_AS("data"))
1062             {   //glyph shapes
1063                 enterObject();
1064                 while (auto key = nextObjectKey()) {
1065                     if (KEY_AS("shapes")) parseShapes(glyph->children);
1066                 }
1067             } else skip(key);
1068         }
1069         glyph->prepare();
1070         glyphs.push(glyph);
1071     }
1072 }
1073 
parseFonts()1074 void LottieParser::parseFonts()
1075 {
1076     enterObject();
1077     while (auto key = nextObjectKey()) {
1078         if (KEY_AS("list")) {
1079             enterArray();
1080             while (nextArrayValue()) {
1081                 comp->fonts.push(parseFont());
1082             }
1083         } else skip(key);
1084     }
1085 }
1086 
1087 
parseGroup()1088 LottieObject* LottieParser::parseGroup()
1089 {
1090     auto group = new LottieGroup;
1091 
1092     while (auto key = nextObjectKey()) {
1093         if (parseCommon(group, key)) continue;
1094         else if (KEY_AS("it")) {
1095             enterArray();
1096             while (nextArrayValue()) parseObject(group->children);
1097         } else skip(key);
1098     }
1099     if (group->children.empty()) {
1100         delete(group);
1101         return nullptr;
1102     }
1103     group->prepare();
1104 
1105     return group;
1106 }
1107 
1108 
parseTimeRemap(LottieLayer * layer)1109 void LottieParser::parseTimeRemap(LottieLayer* layer)
1110 {
1111     parseProperty<LottieProperty::Type::Float>(layer->timeRemap);
1112 }
1113 
1114 
parseShapes(Array<LottieObject * > & parent)1115 void LottieParser::parseShapes(Array<LottieObject*>& parent)
1116 {
1117     enterArray();
1118     while (nextArrayValue()) {
1119         enterObject();
1120         while (auto key = nextObjectKey()) {
1121             if (KEY_AS("it")) {
1122                 enterArray();
1123                 while (nextArrayValue()) parseObject(parent);
1124             } else if (KEY_AS("ty")) {
1125                 if (auto child = parseObject()) {
1126                     if (child->hidden) delete(child);
1127                     else parent.push(child);
1128                 }
1129             } else skip(key);
1130         }
1131      }
1132 }
1133 
1134 
parseTextRange(LottieText * text)1135 void LottieParser::parseTextRange(LottieText* text)
1136 {
1137     enterArray();
1138     while (nextArrayValue()) {
1139         enterObject();
1140 
1141         auto selector = new LottieTextRange;
1142 
1143         while (auto key = nextObjectKey()) {
1144             if (KEY_AS("s")) { // text range selector
1145                 enterObject();
1146                 while (auto key = nextObjectKey()) {
1147                     if (KEY_AS("t")) selector->expressible = (bool) getInt();
1148                     else if (KEY_AS("xe")) parseProperty<LottieProperty::Type::Float>(selector->maxEase);
1149                     else if (KEY_AS("ne")) parseProperty<LottieProperty::Type::Float>(selector->minEase);
1150                     else if (KEY_AS("a")) parseProperty<LottieProperty::Type::Float>(selector->maxAmount);
1151                     else if (KEY_AS("b")) selector->based = (LottieTextRange::Based) getInt();
1152                     else if (KEY_AS("rn")) selector->random = getInt() ? rand() : 0;
1153                     else if (KEY_AS("sh")) selector->shape = (LottieTextRange::Shape) getInt();
1154                     else if (KEY_AS("o")) parseProperty<LottieProperty::Type::Float>(selector->offset);
1155                     else if (KEY_AS("r")) selector->rangeUnit = (LottieTextRange::Unit) getInt();
1156                     else if (KEY_AS("sm")) parseProperty<LottieProperty::Type::Float>(selector->smoothness);
1157                     else if (KEY_AS("s")) parseProperty<LottieProperty::Type::Float>(selector->start);
1158                     else if (KEY_AS("e")) parseProperty<LottieProperty::Type::Float>(selector->end);
1159                     else skip(key);
1160                 }
1161             } else if (KEY_AS("a")) { // text style
1162                 enterObject();
1163                 while (auto key = nextObjectKey()) {
1164                     if (KEY_AS("t")) parseProperty<LottieProperty::Type::Float>(selector->style.letterSpacing);
1165                     else if (KEY_AS("ls")) parseProperty<LottieProperty::Type::Color>(selector->style.lineSpacing);
1166                     else if (KEY_AS("fc")) parseProperty<LottieProperty::Type::Color>(selector->style.fillColor);
1167                     else if (KEY_AS("fo")) parseProperty<LottieProperty::Type::Color>(selector->style.fillOpacity);
1168                     else if (KEY_AS("sw")) parseProperty<LottieProperty::Type::Float>(selector->style.strokeWidth);
1169                     else if (KEY_AS("sc")) parseProperty<LottieProperty::Type::Color>(selector->style.strokeColor);
1170                     else if (KEY_AS("so")) parseProperty<LottieProperty::Type::Opacity>(selector->style.strokeOpacity);
1171                     else if (KEY_AS("o")) parseProperty<LottieProperty::Type::Opacity>(selector->style.opacity);
1172                     else if (KEY_AS("p")) parseProperty<LottieProperty::Type::Position>(selector->style.position);
1173                     else if (KEY_AS("s")) parseProperty<LottieProperty::Type::Position>(selector->style.scale);
1174                     else if (KEY_AS("r")) parseProperty<LottieProperty::Type::Float>(selector->style.rotation);
1175                     else skip(key);
1176                 }
1177             } else skip(key);
1178         }
1179 
1180         text->ranges.push(selector);
1181     }
1182 }
1183 
1184 
parseText(Array<LottieObject * > & parent)1185 void LottieParser::parseText(Array<LottieObject*>& parent)
1186 {
1187     enterObject();
1188 
1189     auto text = new LottieText;
1190 
1191     while (auto key = nextObjectKey()) {
1192         if (KEY_AS("d")) parseProperty<LottieProperty::Type::TextDoc>(text->doc, text);
1193         else if (KEY_AS("a")) parseTextRange(text);
1194         else if (KEY_AS("p"))
1195         {
1196             TVGLOG("LOTTIE", "Text Follow Path (p) is not supported");
1197             skip(key);
1198         }
1199         else if (KEY_AS("m"))
1200         {
1201             TVGLOG("LOTTIE", "Text Alignment Option (m) is not supported");
1202             skip(key);
1203         }
1204         else skip(key);
1205     }
1206 
1207     text->prepare();
1208     parent.push(text);
1209 }
1210 
1211 
getLayerSize(float & val)1212 void LottieParser::getLayerSize(float& val)
1213 {
1214     if (val == 0.0f) {
1215         val = getFloat();
1216     } else {
1217         //layer might have both w(width) & sw(solid color width)
1218         //override one if the a new size is smaller.
1219         auto w = getFloat();
1220         if (w < val) val = w;
1221     }
1222 }
1223 
parseMask()1224 LottieMask* LottieParser::parseMask()
1225 {
1226     auto mask = new LottieMask;
1227     auto valid = true;  //skip if the mask mode is none.
1228 
1229     enterObject();
1230     while (auto key = nextObjectKey()) {
1231         if (KEY_AS("inv")) mask->inverse = getBool();
1232         else if (KEY_AS("mode"))
1233         {
1234             mask->method = getMaskMethod(mask->inverse);
1235             if (mask->method == CompositeMethod::None) valid = false;
1236         }
1237         else if (valid && KEY_AS("pt")) getPathSet(mask->pathset);
1238         else if (valid && KEY_AS("o")) parseProperty<LottieProperty::Type::Opacity>(mask->opacity);
1239         else if (valid && KEY_AS("x")) parseProperty<LottieProperty::Type::Float>(mask->expand);
1240         else skip(key);
1241     }
1242 
1243     if (!valid) {
1244         delete(mask);
1245         return nullptr;
1246     }
1247 
1248     return mask;
1249 }
1250 
1251 
parseMasks(LottieLayer * layer)1252 void LottieParser::parseMasks(LottieLayer* layer)
1253 {
1254     enterArray();
1255     while (nextArrayValue()) {
1256         if (auto mask = parseMask()) {
1257             layer->masks.push(mask);
1258         }
1259     }
1260 }
1261 
1262 
parseGaussianBlur(LottieGaussianBlur * effect)1263 void LottieParser::parseGaussianBlur(LottieGaussianBlur* effect)
1264 {
1265     int idx = 0;  //blurness -> direction -> wrap
1266     enterArray();
1267     while (nextArrayValue()) {
1268         enterObject();
1269         while (auto key = nextObjectKey()) {
1270             if (KEY_AS("v")) {
1271                 enterObject();
1272                 while (auto key = nextObjectKey()) {
1273                     if (KEY_AS("k")) {
1274                         if (idx == 0) parsePropertyInternal(effect->blurness);
1275                         else if (idx == 1) parsePropertyInternal(effect->direction);
1276                         else if (idx == 2) parsePropertyInternal(effect->wrap);
1277                         else skip(key);
1278                         ++idx;
1279                     } else skip(key);
1280                 }
1281             } else skip(key);
1282         }
1283     }
1284 }
1285 
1286 
parseEffect(LottieEffect * effect)1287 void LottieParser::parseEffect(LottieEffect* effect)
1288 {
1289     switch (effect->type) {
1290         case LottieEffect::GaussianBlur: {
1291             parseGaussianBlur(static_cast<LottieGaussianBlur*>(effect));
1292             break;
1293         }
1294         default: break;
1295     }
1296 }
1297 
1298 
parseEffects(LottieLayer * layer)1299 void LottieParser::parseEffects(LottieLayer* layer)
1300 {
1301     auto invalid = true;
1302 
1303     enterArray();
1304     while (nextArrayValue()) {
1305         LottieEffect* effect = nullptr;
1306         enterObject();
1307         while (auto key = nextObjectKey()) {
1308             //type must be priortized.
1309             if (KEY_AS("ty"))
1310             {
1311                 effect = getEffect(getInt());
1312                 if (!effect) break;
1313                 else invalid = false;
1314             }
1315             else if (effect && KEY_AS("en")) effect->enable = getInt();
1316             else if (effect && KEY_AS("ef")) parseEffect(effect);
1317             else skip(key);
1318         }
1319         //TODO: remove when all effects were guaranteed.
1320         if (invalid) {
1321             TVGLOG("LOTTIE", "Not supported Layer Effect = %d", effect ? (int)effect->type : -1);
1322             while (auto key = nextObjectKey()) skip(key);
1323         } else layer->effects.push(effect);
1324     }
1325 }
1326 
1327 
parseLayer(LottieLayer * precomp)1328 LottieLayer* LottieParser::parseLayer(LottieLayer* precomp)
1329 {
1330     auto layer = new LottieLayer;
1331 
1332     layer->comp = precomp;
1333     context.layer = layer;
1334 
1335     auto ddd = false;
1336     RGB24 color;
1337 
1338     enterObject();
1339 
1340     while (auto key = nextObjectKey()) {
1341         if (KEY_AS("nm"))
1342         {
1343             layer->name = getStringCopy();
1344             layer->id = djb2Encode(layer->name);
1345         }
1346         else if (KEY_AS("ddd")) ddd = getInt();  //3d layer
1347         else if (KEY_AS("ind")) layer->idx = getInt();
1348         else if (KEY_AS("ty")) layer->type = (LottieLayer::Type) getInt();
1349         else if (KEY_AS("sr")) layer->timeStretch = getFloat();
1350         else if (KEY_AS("ks"))
1351         {
1352             enterObject();
1353             layer->transform = parseTransform(ddd);
1354         }
1355         else if (KEY_AS("ao")) layer->autoOrient = getInt();
1356         else if (KEY_AS("shapes")) parseShapes(layer->children);
1357         else if (KEY_AS("ip")) layer->inFrame = getFloat();
1358         else if (KEY_AS("op")) layer->outFrame = getFloat();
1359         else if (KEY_AS("st")) layer->startFrame = getFloat();
1360         else if (KEY_AS("bm")) layer->blendMethod = (BlendMethod) getInt();
1361         else if (KEY_AS("parent")) layer->pidx = getInt();
1362         else if (KEY_AS("tm")) parseTimeRemap(layer);
1363         else if (KEY_AS("w") || KEY_AS("sw")) getLayerSize(layer->w);
1364         else if (KEY_AS("h") || KEY_AS("sh")) getLayerSize(layer->h);
1365         else if (KEY_AS("sc")) color = getColor(getString());
1366         else if (KEY_AS("tt")) layer->matteType = getMatteType();
1367         else if (KEY_AS("tp")) layer->mid = getInt();
1368         else if (KEY_AS("masksProperties")) parseMasks(layer);
1369         else if (KEY_AS("hd")) layer->hidden = getBool();
1370         else if (KEY_AS("refId")) layer->rid = djb2Encode(getString());
1371         else if (KEY_AS("td")) layer->matteSrc = getInt();      //used for matte layer
1372         else if (KEY_AS("t")) parseText(layer->children);
1373         else if (KEY_AS("ef")) parseEffects(layer);
1374         else skip(key);
1375     }
1376 
1377     layer->prepare(&color);
1378 
1379     return layer;
1380 }
1381 
1382 
parseLayers(LottieLayer * root)1383 LottieLayer* LottieParser::parseLayers(LottieLayer* root)
1384 {
1385     auto precomp = new LottieLayer;
1386 
1387     precomp->type = LottieLayer::Precomp;
1388     precomp->comp = root;
1389 
1390     enterArray();
1391     while (nextArrayValue()) {
1392         precomp->children.push(parseLayer(precomp));
1393     }
1394 
1395     precomp->prepare();
1396     return precomp;
1397 }
1398 
1399 
postProcess(Array<LottieGlyph * > & glyphs)1400 void LottieParser::postProcess(Array<LottieGlyph*>& glyphs)
1401 {
1402     //aggregate font characters
1403     for (uint32_t g = 0; g < glyphs.count; ++g) {
1404         auto glyph = glyphs[g];
1405         for (uint32_t i = 0; i < comp->fonts.count; ++i) {
1406             auto& font = comp->fonts[i];
1407             if (!strcmp(font->family, glyph->family) && !strcmp(font->style, glyph->style)) {
1408                 font->chars.push(glyph);
1409                 free(glyph->family);
1410                 free(glyph->style);
1411                 break;
1412             }
1413         }
1414     }
1415 }
1416 
1417 
1418 /************************************************************************/
1419 /* External Class Implementation                                        */
1420 /************************************************************************/
1421 
sid(bool first)1422 const char* LottieParser::sid(bool first)
1423 {
1424     if (first) {
1425         //verify json
1426         if (!parseNext()) return nullptr;
1427         enterObject();
1428     }
1429     return nextObjectKey();
1430 }
1431 
1432 
apply(LottieSlot * slot)1433 bool LottieParser::apply(LottieSlot* slot)
1434 {
1435     enterObject();
1436 
1437     //OPTIMIZE: we can create the property directly, without object
1438     LottieObject* obj = nullptr;  //slot object
1439 
1440     switch (slot->type) {
1441         case LottieProperty::Type::ColorStop: {
1442             obj = new LottieGradient;
1443             context.parent = obj;
1444             parseSlotProperty<LottieProperty::Type::ColorStop>(static_cast<LottieGradient*>(obj)->colorStops);
1445             break;
1446         }
1447         case LottieProperty::Type::Color: {
1448             obj = new LottieSolid;
1449             context.parent = obj;
1450             parseSlotProperty<LottieProperty::Type::Color>(static_cast<LottieSolid*>(obj)->color);
1451             break;
1452         }
1453         case LottieProperty::Type::TextDoc: {
1454             obj = new LottieText;
1455             context.parent = obj;
1456             parseSlotProperty<LottieProperty::Type::TextDoc>(static_cast<LottieText*>(obj)->doc);
1457             break;
1458         }
1459         default: break;
1460     }
1461 
1462     if (!obj || Invalid()) return false;
1463 
1464     slot->assign(obj);
1465 
1466     delete(obj);
1467 
1468     return true;
1469 }
1470 
1471 
parse()1472 bool LottieParser::parse()
1473 {
1474     //verify json.
1475     if (!parseNext()) return false;
1476 
1477     enterObject();
1478 
1479     if (comp) delete(comp);
1480     comp = new LottieComposition;
1481 
1482     Array<LottieGlyph*> glyphs;
1483 
1484     auto startFrame = 0.0f;
1485     auto endFrame = 0.0f;
1486 
1487     while (auto key = nextObjectKey()) {
1488         if (KEY_AS("v")) comp->version = getStringCopy();
1489         else if (KEY_AS("fr")) comp->frameRate = getFloat();
1490         else if (KEY_AS("ip")) startFrame = getFloat();
1491         else if (KEY_AS("op")) endFrame = getFloat();
1492         else if (KEY_AS("w")) comp->w = getFloat();
1493         else if (KEY_AS("h")) comp->h = getFloat();
1494         else if (KEY_AS("nm")) comp->name = getStringCopy();
1495         else if (KEY_AS("assets")) parseAssets();
1496         else if (KEY_AS("layers")) comp->root = parseLayers(comp->root);
1497         else if (KEY_AS("fonts")) parseFonts();
1498         else if (KEY_AS("chars")) parseChars(glyphs);
1499         else if (KEY_AS("markers")) parseMarkers();
1500         else skip(key);
1501     }
1502 
1503     if (Invalid() || !comp->root) {
1504         delete(comp);
1505         return false;
1506     }
1507 
1508     comp->root->inFrame = startFrame;
1509     comp->root->outFrame = endFrame;
1510 
1511     postProcess(glyphs);
1512 
1513     return true;
1514 }
1515 
1516 #endif /* LV_USE_THORVG_INTERNAL */
1517 
1518