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