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 #ifndef _TVG_LOTTIE_PROPERTY_H_
27 #define _TVG_LOTTIE_PROPERTY_H_
28 
29 #include <algorithm>
30 #include "tvgMath.h"
31 #include "tvgLottieCommon.h"
32 #include "tvgLottieInterpolator.h"
33 #include "tvgLottieExpressions.h"
34 #include "tvgLottieModifier.h"
35 
36 
37 struct LottieFont;
38 struct LottieLayer;
39 struct LottieObject;
40 
41 
42 template<typename T>
43 struct LottieScalarFrame
44 {
45     T value;                    //keyframe value
46     float no;                   //frame number
47     LottieInterpolator* interpolator;
48     bool hold = false;           //do not interpolate.
49 
interpolateLottieScalarFrame50     T interpolate(LottieScalarFrame<T>* next, float frameNo)
51     {
52         auto t = (frameNo - no) / (next->no - no);
53         if (interpolator) t = interpolator->progress(t);
54 
55         if (hold) {
56             if (t < 1.0f) return value;
57             else return next->value;
58         }
59         return lerp(value, next->value, t);
60     }
61 };
62 
63 
64 template<typename T>
65 struct LottieVectorFrame
66 {
67     T value;                    //keyframe value
68     float no;                   //frame number
69     LottieInterpolator* interpolator;
70     T outTangent, inTangent;
71     float length;
72     bool hasTangent = false;
73     bool hold = false;
74 
interpolateLottieVectorFrame75     T interpolate(LottieVectorFrame* next, float frameNo)
76     {
77         auto t = (frameNo - no) / (next->no - no);
78         if (interpolator) t = interpolator->progress(t);
79 
80         if (hold) {
81             if (t < 1.0f) return value;
82             else return next->value;
83         }
84 
85         if (hasTangent) {
86             Bezier bz = {value, value + outTangent, next->value + inTangent, next->value};
87             return bz.at(bz.atApprox(t * length, length));
88         } else {
89             return lerp(value, next->value, t);
90         }
91     }
92 
angleLottieVectorFrame93     float angle(LottieVectorFrame* next, float frameNo)
94     {
95         if (!hasTangent) {
96             Point dp = next->value - value;
97             return rad2deg(tvg::atan2(dp.y, dp.x));
98         }
99 
100         auto t = (frameNo - no) / (next->no - no);
101         if (interpolator) t = interpolator->progress(t);
102         Bezier bz = {value, value + outTangent, next->value + inTangent, next->value};
103         t = bz.atApprox(t * length, length);
104         return bz.angle(t >= 1.0f ? 0.99f : (t <= 0.0f ? 0.01f : t));
105     }
106 
prepareLottieVectorFrame107     void prepare(LottieVectorFrame* next)
108     {
109         Bezier bz = {value, value + outTangent, next->value + inTangent, next->value};
110         length = bz.lengthApprox();
111     }
112 };
113 
114 
115 //Property would have an either keyframes or single value.
116 struct LottieProperty
117 {
118     enum class Type : uint8_t { Point = 0, Float, Opacity, Color, PathSet, ColorStop, Position, TextDoc, Invalid };
119 
120     LottieExpression* exp = nullptr;
121     Type type;
122     uint8_t ix;  //property index
123 
124     //TODO: Apply common bodies?
~LottiePropertyLottieProperty125     virtual ~LottieProperty() {}
126     virtual uint32_t frameCnt() = 0;
127     virtual uint32_t nearest(float time) = 0;
128     virtual float frameNo(int32_t key) = 0;
129 };
130 
131 
132 struct LottieExpression
133 {
134     enum LoopMode : uint8_t { None = 0, InCycle = 1, InPingPong, InOffset, InContinue, OutCycle, OutPingPong, OutOffset, OutContinue };
135 
136     char* code;
137     LottieComposition* comp;
138     LottieLayer* layer;
139     LottieObject* object;
140     LottieProperty* property;
141     bool disabled = false;
142 
143     struct {
144         uint32_t key = 0;      //the keyframe number repeating to
145         float in = FLT_MAX;    //looping duration in frame number
146         LoopMode mode = None;
147     } loop;
148 
~LottieExpressionLottieExpression149     ~LottieExpression()
150     {
151         free(code);
152     }
153 };
154 
155 
_copy(PathSet * pathset,Array<Point> & outPts,Matrix * transform)156 static void _copy(PathSet* pathset, Array<Point>& outPts, Matrix* transform)
157 {
158     Array<Point> inPts;
159 
160     if (transform) {
161         for (int i = 0; i < pathset->ptsCnt; ++i) {
162             Point pt = pathset->pts[i];
163             pt *= *transform;
164             outPts.push(pt);
165         }
166     } else {
167         inPts.data = pathset->pts;
168         inPts.count = pathset->ptsCnt;
169         outPts.push(inPts);
170         inPts.data = nullptr;
171     }
172 }
173 
174 
_copy(PathSet * pathset,Array<PathCommand> & outCmds)175 static void _copy(PathSet* pathset, Array<PathCommand>& outCmds)
176 {
177     Array<PathCommand> inCmds;
178     inCmds.data = pathset->cmds;
179     inCmds.count = pathset->cmdsCnt;
180     outCmds.push(inCmds);
181     inCmds.data = nullptr;
182 }
183 
184 
185 template<typename T>
_bsearch(T * frames,float frameNo)186 uint32_t _bsearch(T* frames, float frameNo)
187 {
188     int32_t low = 0;
189     int32_t high = int32_t(frames->count) - 1;
190 
191     while (low <= high) {
192         auto mid = low + (high - low) / 2;
193         auto frame = frames->data + mid;
194         if (frameNo < frame->no) high = mid - 1;
195         else low = mid + 1;
196     }
197     if (high < low) low = high;
198     if (low < 0) low = 0;
199     return low;
200 }
201 
202 
203 template<typename T>
_nearest(T * frames,float frameNo)204 uint32_t _nearest(T* frames, float frameNo)
205 {
206     if (frames) {
207         auto key = _bsearch(frames, frameNo);
208         if (key == frames->count - 1) return key;
209         return (fabsf(frames->data[key].no - frameNo) < fabsf(frames->data[key + 1].no - frameNo)) ? key : (key + 1);
210     }
211     return 0;
212 }
213 
214 
215 template<typename T>
_frameNo(T * frames,int32_t key)216 float _frameNo(T* frames, int32_t key)
217 {
218     if (!frames) return 0.0f;
219     if (key < 0) key = 0;
220     if (key >= (int32_t) frames->count) key = (int32_t)(frames->count - 1);
221     return (*frames)[key].no;
222 }
223 
224 
225 template<typename T>
_loop(T * frames,float frameNo,LottieExpression * exp)226 float _loop(T* frames, float frameNo, LottieExpression* exp)
227 {
228     if (frameNo >= exp->loop.in || frameNo < frames->first().no || frameNo < frames->last().no) return frameNo;
229 
230     frameNo -= frames->first().no;
231 
232     switch (exp->loop.mode) {
233         case LottieExpression::LoopMode::InCycle: {
234             return fmodf(frameNo, frames->last().no - frames->first().no) + (*frames)[exp->loop.key].no;
235         }
236         case LottieExpression::LoopMode::InPingPong: {
237             auto range = frames->last().no - (*frames)[exp->loop.key].no;
238             auto forward = (static_cast<int>(frameNo / range) % 2) == 0 ? true : false;
239             frameNo = fmodf(frameNo, range);
240             return (forward ? frameNo : (range - frameNo)) + (*frames)[exp->loop.key].no;
241         }
242         case LottieExpression::LoopMode::OutCycle: {
243             return fmodf(frameNo, (*frames)[frames->count - 1 - exp->loop.key].no - frames->first().no) + frames->first().no;
244         }
245         case LottieExpression::LoopMode::OutPingPong: {
246             auto range = (*frames)[frames->count - 1 - exp->loop.key].no - frames->first().no;
247             auto forward = (static_cast<int>(frameNo / range) % 2) == 0 ? true : false;
248             frameNo = fmodf(frameNo, range);
249             return (forward ? frameNo : (range - frameNo)) + frames->first().no;
250         }
251         default: break;
252     }
253     return frameNo;
254 }
255 
256 
257 template<typename T>
258 struct LottieGenericProperty : LottieProperty
259 {
260     //Property has an either keyframes or single value.
261     Array<LottieScalarFrame<T>>* frames = nullptr;
262     T value;
263 
LottieGenericPropertyLottieGenericProperty264     LottieGenericProperty(T v) : value(v) {}
LottieGenericPropertyLottieGenericProperty265     LottieGenericProperty() {}
266 
~LottieGenericPropertyLottieGenericProperty267     ~LottieGenericProperty()
268     {
269         release();
270     }
271 
releaseLottieGenericProperty272     void release()
273     {
274         delete(frames);
275         frames = nullptr;
276         if (exp) {
277             delete(exp);
278             exp = nullptr;
279         }
280     }
281 
nearestLottieGenericProperty282     uint32_t nearest(float frameNo) override
283     {
284         return _nearest(frames, frameNo);
285     }
286 
frameCntLottieGenericProperty287     uint32_t frameCnt() override
288     {
289         return frames ? frames->count : 1;
290     }
291 
frameNoLottieGenericProperty292     float frameNo(int32_t key) override
293     {
294         return _frameNo(frames, key);
295     }
296 
newFrameLottieGenericProperty297     LottieScalarFrame<T>& newFrame()
298     {
299         if (!frames) frames = new Array<LottieScalarFrame<T>>;
300         if (frames->count + 1 >= frames->reserved) {
301             auto old = frames->reserved;
302             frames->grow(frames->count + 2);
303             memset((void*)(frames->data + old), 0x00, sizeof(LottieScalarFrame<T>) * (frames->reserved - old));
304         }
305         ++frames->count;
306         return frames->last();
307     }
308 
nextFrameLottieGenericProperty309     LottieScalarFrame<T>& nextFrame()
310     {
311         return (*frames)[frames->count];
312     }
313 
operatorLottieGenericProperty314     T operator()(float frameNo)
315     {
316         if (!frames) return value;
317         if (frames->count == 1 || frameNo <= frames->first().no) return frames->first().value;
318         if (frameNo >= frames->last().no) return frames->last().value;
319 
320         auto frame = frames->data + _bsearch(frames, frameNo);
321         if (tvg::equal(frame->no, frameNo)) return frame->value;
322         return frame->interpolate(frame + 1, frameNo);
323     }
324 
operatorLottieGenericProperty325     T operator()(float frameNo, LottieExpressions* exps)
326     {
327         if (exps && exp) {
328             T out{};
329             if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp);
330             if (exps->result<LottieGenericProperty<T>>(frameNo, out, exp)) return out;
331         }
332         return operator()(frameNo);
333     }
334 
335     LottieGenericProperty<T>& operator=(const LottieGenericProperty<T>& other)
336     {
337         //shallow copy, used for slot overriding
338         if (other.frames) {
339             frames = other.frames;
340             const_cast<LottieGenericProperty<T>&>(other).frames = nullptr;
341         } else value = other.value;
342         return *this;
343     }
344 
angleLottieGenericProperty345     float angle(float frameNo) { return 0; }
prepareLottieGenericProperty346     void prepare() {}
347 };
348 
349 
350 struct LottiePathSet : LottieProperty
351 {
352     Array<LottieScalarFrame<PathSet>>* frames = nullptr;
353     PathSet value;
354 
~LottiePathSetLottiePathSet355     ~LottiePathSet()
356     {
357         release();
358     }
359 
releaseLottiePathSet360     void release()
361     {
362         if (exp) {
363             delete(exp);
364             exp = nullptr;
365         }
366 
367         free(value.cmds);
368         free(value.pts);
369 
370         if (!frames) return;
371 
372         for (auto p = frames->begin(); p < frames->end(); ++p) {
373             free((*p).value.cmds);
374             free((*p).value.pts);
375         }
376         free(frames->data);
377         free(frames);
378     }
379 
nearestLottiePathSet380     uint32_t nearest(float frameNo) override
381     {
382         return _nearest(frames, frameNo);
383     }
384 
frameCntLottiePathSet385     uint32_t frameCnt() override
386     {
387         return frames ? frames->count : 1;
388     }
389 
frameNoLottiePathSet390     float frameNo(int32_t key) override
391     {
392         return _frameNo(frames, key);
393     }
394 
newFrameLottiePathSet395     LottieScalarFrame<PathSet>& newFrame()
396     {
397         if (!frames) {
398             frames = static_cast<Array<LottieScalarFrame<PathSet>>*>(calloc(1, sizeof(Array<LottieScalarFrame<PathSet>>)));
399         }
400         if (frames->count + 1 >= frames->reserved) {
401             auto old = frames->reserved;
402             frames->grow(frames->count + 2);
403             memset((void*)(frames->data + old), 0x00, sizeof(LottieScalarFrame<PathSet>) * (frames->reserved - old));
404         }
405         ++frames->count;
406         return frames->last();
407     }
408 
nextFrameLottiePathSet409     LottieScalarFrame<PathSet>& nextFrame()
410     {
411         return (*frames)[frames->count];
412     }
413 
operatorLottiePathSet414     bool operator()(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, const LottieRoundnessModifier* roundness, const LottieOffsetModifier* offsetPath)
415     {
416         PathSet* path = nullptr;
417         LottieScalarFrame<PathSet>* frame = nullptr;
418         float t;
419         bool interpolate = false;
420 
421         if (!frames) path = &value;
422         else if (frames->count == 1 || frameNo <= frames->first().no) path = &frames->first().value;
423         else if (frameNo >= frames->last().no) path = &frames->last().value;
424         else {
425             frame = frames->data + _bsearch(frames, frameNo);
426             if (tvg::equal(frame->no, frameNo)) path = &frame->value;
427             else if (frame->value.ptsCnt != (frame + 1)->value.ptsCnt) {
428                 path = &frame->value;
429                 TVGLOG("LOTTIE", "Different numbers of points in consecutive frames - interpolation omitted.");
430             } else {
431                 t = (frameNo - frame->no) / ((frame + 1)->no - frame->no);
432                 if (frame->interpolator) t = frame->interpolator->progress(t);
433                 if (frame->hold) path = &(frame + ((t < 1.0f) ? 0 : 1))->value;
434                 else interpolate = true;
435             }
436         }
437 
438         if (!interpolate) {
439             if (roundness) {
440                 if (offsetPath) {
441                     Array<PathCommand> cmds1(path->cmdsCnt);
442                     Array<Point> pts1(path->ptsCnt);
443                     roundness->modifyPath(path->cmds, path->cmdsCnt, path->pts, path->ptsCnt, cmds1, pts1, transform);
444                     return offsetPath->modifyPath(cmds1.data, cmds1.count, pts1.data, pts1.count, cmds, pts);
445                 }
446                 return roundness->modifyPath(path->cmds, path->cmdsCnt, path->pts, path->ptsCnt, cmds, pts, transform);
447             }
448             if (offsetPath) return offsetPath->modifyPath(path->cmds, path->cmdsCnt, path->pts, path->ptsCnt, cmds, pts);
449 
450             _copy(path, cmds);
451             _copy(path, pts, transform);
452             return true;
453         }
454 
455         auto s = frame->value.pts;
456         auto e = (frame + 1)->value.pts;
457 
458         if (!roundness && !offsetPath) {
459             for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e) {
460                 auto pt = lerp(*s, *e, t);
461                 if (transform) pt *= *transform;
462                 pts.push(pt);
463             }
464             _copy(&frame->value, cmds);
465             return true;
466         }
467 
468         auto interpPts = (Point*)malloc(frame->value.ptsCnt * sizeof(Point));
469         auto p = interpPts;
470         for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e, ++p) {
471             *p = lerp(*s, *e, t);
472             if (transform) *p *= *transform;
473         }
474 
475         if (roundness) {
476             if (offsetPath) {
477                 Array<PathCommand> cmds1;
478                 Array<Point> pts1;
479                 roundness->modifyPath(frame->value.cmds, frame->value.cmdsCnt, interpPts, frame->value.ptsCnt, cmds1, pts1, nullptr);
480                 offsetPath->modifyPath(cmds1.data, cmds1.count, pts1.data, pts1.count, cmds, pts);
481             } else roundness->modifyPath(frame->value.cmds, frame->value.cmdsCnt, interpPts, frame->value.ptsCnt, cmds, pts, nullptr);
482         } else if (offsetPath) offsetPath->modifyPath(frame->value.cmds, frame->value.cmdsCnt, interpPts, frame->value.ptsCnt, cmds, pts);
483 
484         free(interpPts);
485 
486         return true;
487     }
488 
489 
operatorLottiePathSet490     bool operator()(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, const LottieRoundnessModifier* roundness, const LottieOffsetModifier* offsetPath, LottieExpressions* exps)
491     {
492         if (exps && exp) {
493             if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp);
494             if (exps->result<LottiePathSet>(frameNo, cmds, pts, transform, roundness, offsetPath, exp)) return true;
495         }
496         return operator()(frameNo, cmds, pts, transform, roundness, offsetPath);
497     }
498 
prepareLottiePathSet499     void prepare() {}
500 };
501 
502 
503 struct LottieColorStop : LottieProperty
504 {
505     Array<LottieScalarFrame<ColorStop>>* frames = nullptr;
506     ColorStop value;
507     uint16_t count = 0;     //colorstop count
508     bool populated = false;
509 
~LottieColorStopLottieColorStop510     ~LottieColorStop()
511     {
512         release();
513     }
514 
releaseLottieColorStop515     void release()
516     {
517         if (exp) {
518             delete(exp);
519             exp = nullptr;
520         }
521 
522         if (value.data) {
523             free(value.data);
524             value.data = nullptr;
525         }
526 
527         if (!frames) return;
528 
529         for (auto p = frames->begin(); p < frames->end(); ++p) {
530             free((*p).value.data);
531         }
532         free(frames->data);
533         free(frames);
534         frames = nullptr;
535     }
536 
nearestLottieColorStop537     uint32_t nearest(float frameNo) override
538     {
539         return _nearest(frames, frameNo);
540     }
541 
frameCntLottieColorStop542     uint32_t frameCnt() override
543     {
544         return frames ? frames->count : 1;
545     }
546 
frameNoLottieColorStop547     float frameNo(int32_t key) override
548     {
549         return _frameNo(frames, key);
550     }
551 
newFrameLottieColorStop552     LottieScalarFrame<ColorStop>& newFrame()
553     {
554         if (!frames) {
555             frames = static_cast<Array<LottieScalarFrame<ColorStop>>*>(calloc(1, sizeof(Array<LottieScalarFrame<ColorStop>>)));
556         }
557         if (frames->count + 1 >= frames->reserved) {
558             auto old = frames->reserved;
559             frames->grow(frames->count + 2);
560             memset((void*)(frames->data + old), 0x00, sizeof(LottieScalarFrame<ColorStop>) * (frames->reserved - old));
561         }
562         ++frames->count;
563         return frames->last();
564     }
565 
nextFrameLottieColorStop566     LottieScalarFrame<ColorStop>& nextFrame()
567     {
568         return (*frames)[frames->count];
569     }
570 
operatorLottieColorStop571     Result operator()(float frameNo, Fill* fill, LottieExpressions* exps)
572     {
573         if (exps && exp) {
574             if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp);
575             if (exps->result<LottieColorStop>(frameNo, fill, exp)) return Result::Success;
576         }
577 
578         if (!frames) return fill->colorStops(value.data, count);
579 
580         if (frames->count == 1 || frameNo <= frames->first().no) {
581             return fill->colorStops(frames->first().value.data, count);
582         }
583 
584         if (frameNo >= frames->last().no) {
585             return fill->colorStops(frames->last().value.data, count);
586         }
587 
588         auto frame = frames->data + _bsearch(frames, frameNo);
589         if (tvg::equal(frame->no, frameNo)) return fill->colorStops(frame->value.data, count);
590 
591         //interpolate
592         auto t = (frameNo - frame->no) / ((frame + 1)->no - frame->no);
593         if (frame->interpolator) t = frame->interpolator->progress(t);
594 
595         if (frame->hold) {
596             if (t < 1.0f) fill->colorStops(frame->value.data, count);
597             else fill->colorStops((frame + 1)->value.data, count);
598         }
599 
600         auto s = frame->value.data;
601         auto e = (frame + 1)->value.data;
602 
603         Array<Fill::ColorStop> result;
604 
605         for (auto i = 0; i < count; ++i, ++s, ++e) {
606             auto offset = lerp(s->offset, e->offset, t);
607             auto r = lerp(s->r, e->r, t);
608             auto g = lerp(s->g, e->g, t);
609             auto b = lerp(s->b, e->b, t);
610             auto a = lerp(s->a, e->a, t);
611             result.push({offset, r, g, b, a});
612         }
613         return fill->colorStops(result.data, count);
614     }
615 
616     LottieColorStop& operator=(const LottieColorStop& other)
617     {
618         //shallow copy, used for slot overriding
619         if (other.frames) {
620             frames = other.frames;
621             const_cast<LottieColorStop&>(other).frames = nullptr;
622         } else {
623             value = other.value;
624             const_cast<LottieColorStop&>(other).value = {nullptr, nullptr};
625         }
626         populated = other.populated;
627         count = other.count;
628 
629         return *this;
630     }
631 
prepareLottieColorStop632     void prepare() {}
633 };
634 
635 
636 struct LottiePosition : LottieProperty
637 {
638     Array<LottieVectorFrame<Point>>* frames = nullptr;
639     Point value;
640 
LottiePositionLottiePosition641     LottiePosition(Point v) : value(v)
642     {
643     }
644 
~LottiePositionLottiePosition645     ~LottiePosition()
646     {
647         release();
648     }
649 
releaseLottiePosition650     void release()
651     {
652         delete(frames);
653         frames = nullptr;
654 
655         if (exp) {
656             delete(exp);
657             exp = nullptr;
658         }
659     }
660 
nearestLottiePosition661     uint32_t nearest(float frameNo) override
662     {
663         return _nearest(frames, frameNo);
664     }
665 
frameCntLottiePosition666     uint32_t frameCnt() override
667     {
668         return frames ? frames->count : 1;
669     }
670 
frameNoLottiePosition671     float frameNo(int32_t key) override
672     {
673         return _frameNo(frames, key);
674     }
675 
newFrameLottiePosition676     LottieVectorFrame<Point>& newFrame()
677     {
678         if (!frames) frames = new Array<LottieVectorFrame<Point>>;
679         if (frames->count + 1 >= frames->reserved) {
680             auto old = frames->reserved;
681             frames->grow(frames->count + 2);
682             memset((void*)(frames->data + old), 0x00, sizeof(LottieVectorFrame<Point>) * (frames->reserved - old));
683         }
684         ++frames->count;
685         return frames->last();
686     }
687 
nextFrameLottiePosition688     LottieVectorFrame<Point>& nextFrame()
689     {
690         return (*frames)[frames->count];
691     }
692 
operatorLottiePosition693     Point operator()(float frameNo)
694     {
695         if (!frames) return value;
696         if (frames->count == 1 || frameNo <= frames->first().no) return frames->first().value;
697         if (frameNo >= frames->last().no) return frames->last().value;
698 
699         auto frame = frames->data + _bsearch(frames, frameNo);
700         if (tvg::equal(frame->no, frameNo)) return frame->value;
701         return frame->interpolate(frame + 1, frameNo);
702     }
703 
operatorLottiePosition704     Point operator()(float frameNo, LottieExpressions* exps)
705     {
706         Point out{};
707         if (exps && exp) {
708             if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp);
709             if (exps->result<LottiePosition>(frameNo, out, exp)) return out;
710         }
711         return operator()(frameNo);
712     }
713 
angleLottiePosition714     float angle(float frameNo)
715     {
716         if (!frames || frames->count == 1) return 0;
717 
718         if (frameNo <= frames->first().no) return frames->first().angle(frames->data + 1, frames->first().no);
719         if (frameNo >= frames->last().no) {
720             auto frame = frames->data + frames->count - 2;
721             return frame->angle(frame + 1, frames->last().no);
722         }
723 
724         auto frame = frames->data + _bsearch(frames, frameNo);
725         return frame->angle(frame + 1, frameNo);
726     }
727 
prepareLottiePosition728     void prepare()
729     {
730         if (!frames || frames->count < 2) return;
731         for (auto frame = frames->begin() + 1; frame < frames->end(); ++frame) {
732             (frame - 1)->prepare(frame);
733         }
734     }
735 };
736 
737 
738 struct LottieTextDoc : LottieProperty
739 {
740     Array<LottieScalarFrame<TextDocument>>* frames = nullptr;
741     TextDocument value;
742 
~LottieTextDocLottieTextDoc743     ~LottieTextDoc()
744     {
745         release();
746     }
747 
releaseLottieTextDoc748     void release()
749     {
750         if (exp) {
751             delete(exp);
752             exp = nullptr;
753         }
754 
755         if (value.text) {
756             free(value.text);
757             value.text = nullptr;
758         }
759         if (value.name) {
760             free(value.name);
761             value.name = nullptr;
762         }
763 
764         if (!frames) return;
765 
766         for (auto p = frames->begin(); p < frames->end(); ++p) {
767             free((*p).value.text);
768             free((*p).value.name);
769         }
770         delete(frames);
771         frames = nullptr;
772     }
773 
nearestLottieTextDoc774     uint32_t nearest(float frameNo) override
775     {
776         return _nearest(frames, frameNo);
777     }
778 
frameCntLottieTextDoc779     uint32_t frameCnt() override
780     {
781         return frames ? frames->count : 1;
782     }
783 
frameNoLottieTextDoc784     float frameNo(int32_t key) override
785     {
786         return _frameNo(frames, key);
787     }
788 
newFrameLottieTextDoc789     LottieScalarFrame<TextDocument>& newFrame()
790     {
791         if (!frames) frames = new Array<LottieScalarFrame<TextDocument>>;
792         if (frames->count + 1 >= frames->reserved) {
793             auto old = frames->reserved;
794             frames->grow(frames->count + 2);
795             memset((void*)(frames->data + old), 0x00, sizeof(LottieScalarFrame<TextDocument>) * (frames->reserved - old));
796         }
797         ++frames->count;
798         return frames->last();
799     }
800 
nextFrameLottieTextDoc801     LottieScalarFrame<TextDocument>& nextFrame()
802     {
803         return (*frames)[frames->count];
804     }
805 
operatorLottieTextDoc806     TextDocument& operator()(float frameNo)
807     {
808         if (!frames) return value;
809         if (frames->count == 1 || frameNo <= frames->first().no) return frames->first().value;
810         if (frameNo >= frames->last().no) return frames->last().value;
811 
812         auto frame = frames->data + _bsearch(frames, frameNo);
813         return frame->value;
814     }
815 
816     LottieTextDoc& operator=(const LottieTextDoc& other)
817     {
818         //shallow copy, used for slot overriding
819         if (other.frames) {
820             frames = other.frames;
821             const_cast<LottieTextDoc&>(other).frames = nullptr;
822         } else {
823             value = other.value;
824             const_cast<LottieTextDoc&>(other).value.text = nullptr;
825             const_cast<LottieTextDoc&>(other).value.name = nullptr;
826         }
827         return *this;
828     }
829 
prepareLottieTextDoc830     void prepare() {}
831 };
832 
833 
834 using LottiePoint = LottieGenericProperty<Point>;
835 using LottieFloat = LottieGenericProperty<float>;
836 using LottieOpacity = LottieGenericProperty<uint8_t>;
837 using LottieColor = LottieGenericProperty<RGB24>;
838 using LottieSlider = LottieFloat;
839 using LottieCheckbox = LottieGenericProperty<int8_t>;
840 
841 #endif //_TVG_LOTTIE_PROPERTY_H_
842 
843 #endif /* LV_USE_THORVG_INTERNAL */
844 
845