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