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