• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1describe('Skottie behavior', () => {
2    let container;
3
4    beforeEach(async () => {
5        await EverythingLoaded;
6        container = document.createElement('div');
7        container.innerHTML = `
8            <canvas width=600 height=600 id=test></canvas>
9            <canvas width=600 height=600 id=report></canvas>`;
10        document.body.appendChild(container);
11    });
12
13    afterEach(() => {
14        document.body.removeChild(container);
15    });
16
17    const expectArrayCloseTo = (a, b, precision) => {
18        precision = precision || 14; // digits of precision in base 10
19        expect(a.length).toEqual(b.length);
20        for (let i=0; i<a.length; i++) {
21          expect(a[i]).toBeCloseTo(b[i], precision);
22        }
23    };
24
25    const imgPromise = fetch('/assets/flightAnim.gif')
26        .then((response) => response.arrayBuffer());
27    const jsonPromise = fetch('/assets/animated_gif.json')
28        .then((response) => response.text());
29    const washPromise = fetch('/assets/map-shield.json')
30        .then((response) => response.text());
31
32    gm('skottie_animgif', (canvas, promises) => {
33        if (!CanvasKit.skottie || !CanvasKit.managed_skottie) {
34            console.warn('Skipping test because not compiled with skottie');
35            return;
36        }
37        expect(promises[1]).not.toBe('NOT FOUND');
38        const animation = CanvasKit.MakeManagedAnimation(promises[1], {
39            'flightAnim.gif': promises[0],
40        });
41        expect(animation).toBeTruthy();
42        const bounds = CanvasKit.LTRBRect(0, 0, 500, 500);
43
44        const size = animation.size();
45        expectArrayCloseTo(size, Float32Array.of(800, 600), 4);
46
47        animation.render(canvas, bounds);
48
49        // We intentionally make the length of this array 5 and add a sentinel value
50        // of 999 so we can make sure the bounds are copied into this rect and a new
51        // one is not allocated.
52        const damageRect = Float32Array.of(0, 0, 0, 0, 999);
53
54        // There was a bug, fixed in https://skia-review.googlesource.com/c/skia/+/241757
55        // that seeking again and drawing again revealed.
56        animation.seek(0.5, damageRect);
57        expectArrayCloseTo(damageRect, Float32Array.of(0, 0, 800, 600, 999), 4);
58
59        canvas.clear(CanvasKit.WHITE);
60        animation.render(canvas, bounds);
61        animation.delete();
62    }, imgPromise, jsonPromise);
63
64    gm('skottie_setcolor', (canvas, promises) => {
65        if (!CanvasKit.skottie || !CanvasKit.managed_skottie) {
66            console.warn('Skipping test because not compiled with skottie');
67            return;
68        }
69        expect(promises[0]).not.toBe('NOT FOUND');
70        const bounds = CanvasKit.LTRBRect(0, 0, 500, 500);
71
72        const animation = CanvasKit.MakeManagedAnimation(promises[0]);
73        expect(animation).toBeTruthy();
74        animation.setColor('$Icon Fill', CanvasKit.RED);
75        animation.seek(0.5);
76        animation.render(canvas, bounds);
77        animation.delete();
78    }, washPromise);
79
80    it('can load audio assets', (done) => {
81        if (!CanvasKit.skottie || !CanvasKit.managed_skottie) {
82            console.warn('Skipping test because not compiled with skottie');
83            return;
84        }
85        const mockSoundMap = {
86            map : new Map(),
87            getPlayer : function(name) {return this.map.get(name)},
88            setPlayer : function(name, player) {this.map.set(name, player)},
89        };
90        function mockPlayer(name) {
91            this.name = name;
92            this.wasPlayed = false,
93            this.seek = function(t) {
94                this.wasPlayed = true;
95            }
96        }
97        for (let i = 0; i < 20; i++) {
98            var name = 'audio_' + i;
99            mockSoundMap.setPlayer(name, new mockPlayer(name));
100        }
101        fetch('/assets/audio_external.json')
102        .then((response) => response.text())
103        .then((lottie) => {
104            const animation = CanvasKit.MakeManagedAnimation(lottie, null, null, mockSoundMap);
105            expect(animation).toBeTruthy();
106            // 190 frames in sample lottie
107            for (let t = 0; t < 190; t++) {
108                animation.seekFrame(t);
109            }
110            animation.delete();
111            for(const player of mockSoundMap.map.values()) {
112                expect(player.wasPlayed).toBeTrue(player.name + " was not played");
113            }
114            done();
115        });
116    });
117
118    it('can get logs', (done) => {
119        if (!CanvasKit.skottie || !CanvasKit.managed_skottie) {
120            console.warn('Skipping test because not compiled with skottie');
121            return;
122        }
123
124        const logger = {
125           errors:   [],
126           warnings: [],
127
128           reset: function() { this.errors = []; this.warnings = []; },
129
130           // Logger API
131           onError:   function(err) { this.errors.push(err)   },
132           onWarning: function(wrn) { this.warnings.push(wrn) }
133        };
134
135        {
136            const json = `{
137                "v": "5.2.1",
138                "w": 100,
139                "h": 100,
140                "fr": 10,
141                "ip": 0,
142                "op": 100,
143                "layers": [{
144                    "ty": 3,
145                    "nm": "null",
146                    "ind": 0,
147                    "ip": 0
148                }]
149            }`;
150            const animation = CanvasKit.MakeManagedAnimation(json, null, null, null, logger);
151            expect(animation).toBeTruthy();
152            expect(logger.errors.length).toEqual(0);
153            expect(logger.warnings.length).toEqual(0);
154        }
155
156        {
157            const json = `{
158                "v": "5.2.1",
159                "w": 100,
160                "h": 100,
161                "fr": 10,
162                "ip": 0,
163                "op": 100,
164                "layers": [{
165                    "ty": 2,
166                    "nm": "image",
167                    "ind": 0,
168                    "ip": 0
169                }]
170            }`;
171            const animation = CanvasKit.MakeManagedAnimation(json, null, null, null, logger);
172            expect(animation).toBeTruthy();
173            expect(logger.errors.length).toEqual(1);
174            expect(logger.warnings.length).toEqual(0);
175
176            // Image layer missing refID
177            expect(logger.errors[0].includes('missing ref'));
178            logger.reset();
179        }
180
181        {
182            const json = `{
183                "v": "5.2.1",
184                "w": 100,
185                "h": 100,
186                "fr": 10,
187                "ip": 0,
188                "op": 100,
189                "layers": [{
190                    "ty": 1,
191                    "nm": "solid",
192                    "sw": 100,
193                    "sh": 100,
194                    "sc": "#aabbcc",
195                    "ind": 0,
196                    "ip": 0,
197                    "ef": [{
198                      "mn": "FOO"
199                    }]
200                }]
201            }`;
202            const animation = CanvasKit.MakeManagedAnimation(json, null, null, null, logger);
203            expect(animation).toBeTruthy();
204            expect(logger.errors.length).toEqual(0);
205            expect(logger.warnings.length).toEqual(1);
206
207            // Unsupported effect FOO
208            expect(logger.warnings[0].includes('FOO'));
209            logger.reset();
210        }
211
212        done();
213    });
214
215    it('can access dynamic props', () => {
216        if (!CanvasKit.skottie || !CanvasKit.managed_skottie) {
217            console.warn('Skipping test because not compiled with skottie');
218            return;
219        }
220
221        const json = `{
222            "v": "5.2.1",
223            "w": 100,
224            "h": 100,
225            "fr": 10,
226            "ip": 0,
227            "op": 100,
228            "fonts": {
229              "list": [{
230                "fName": "test_font",
231                "fFamily": "test-family",
232                "fStyle": "TestFontStyle"
233              }]
234            },
235            "layers": [
236              {
237                "ty": 4,
238                "nm": "__shape_layer",
239                "ind": 0,
240                "ip": 0,
241                "shapes": [
242                  {
243                    "ty": "el",
244                    "p": { "a": 0, "k": [ 50, 50 ] },
245                    "s": { "a": 0, "k": [ 50, 50 ] }
246                  },{
247                    "ty": "fl",
248                    "nm": "__shape_fill",
249                    "c": { "a": 0, "k": [ 1, 0, 0] }
250                  },{
251                    "ty": "tr",
252                    "nm": "__shape_opacity",
253                    "o": { "a": 0, "k": 50 }
254                  }
255                ]
256              },{
257                "ty": 5,
258                "nm": "__text_layer",
259                "ip": 0,
260                "t": {
261                  "d": {
262                    "k": [{
263                      "t": 0,
264                      "s": {
265                        "f": "test_font",
266                        "s": 100,
267                        "t": "Foo Bar Baz",
268                        "lh": 120,
269                        "ls": 12
270                      }
271                    }]
272                  }
273                }
274              }
275            ]
276        }`;
277
278        const animation = CanvasKit.MakeManagedAnimation(json, null, '__');
279        expect(animation).toBeTruthy();
280
281        {
282            const colors = animation.getColorProps();
283            expect(colors.length).toEqual(1);
284            expect(colors[0].key).toEqual('__shape_fill');
285            expect(colors[0].value).toEqual(CanvasKit.ColorAsInt(255,0,0,255));
286
287            const opacities = animation.getOpacityProps();
288            expect(opacities.length).toEqual(1);
289            expect(opacities[0].key).toEqual('__shape_opacity');
290            expect(opacities[0].value).toEqual(50);
291
292            const texts = animation.getTextProps();
293            expect(texts.length).toEqual(1);
294            expect(texts[0].key).toEqual('__text_layer');
295            expect(texts[0].value.text).toEqual('Foo Bar Baz');
296            expect(texts[0].value.size).toEqual(100);
297        }
298
299        expect(animation.setColor('__shape_fill', [0,1,0,1])).toEqual(true);
300        expect(animation.setOpacity('__shape_opacity', 100)).toEqual(true);
301        expect(animation.setText('__text_layer', 'baz bar foo', 10)).toEqual(true);
302
303        {
304            const colors = animation.getColorProps();
305            expect(colors.length).toEqual(1);
306            expect(colors[0].key).toEqual('__shape_fill');
307            expect(colors[0].value).toEqual(CanvasKit.ColorAsInt(0,255,0,255));
308
309            const opacities = animation.getOpacityProps();
310            expect(opacities.length).toEqual(1);
311            expect(opacities[0].key).toEqual('__shape_opacity');
312            expect(opacities[0].value).toEqual(100);
313
314            const texts = animation.getTextProps();
315            expect(texts.length).toEqual(1);
316            expect(texts[0].key).toEqual('__text_layer');
317            expect(texts[0].value.text).toEqual('baz bar foo');
318            expect(texts[0].value.size).toEqual(10);
319        }
320
321        expect(animation.setColor('INVALID_KEY', [0,1,0,1])).toEqual(false);
322        expect(animation.setOpacity('INVALID_KEY', 100)).toEqual(false);
323        expect(animation.setText('INVALID KEY', '', 10)).toEqual(false);
324    });
325});
326