Update rive-cpp to 2.0 version
[platform/core/uifw/rive-tizen.git] / submodule / skia / modules / canvaskit / tests / canvas2d.spec.js
1 describe('Canvas 2D emulation', () => {
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 expectColorCloseTo = (a, b) => {
18         expect(a.length).toEqual(4);
19         expect(b.length).toEqual(4);
20         for (let i=0; i<4; i++) {
21             expect(a[i]).toBeCloseTo(b[i], 3);
22         }
23     }
24
25     describe('color strings', () => {
26         const hex = (s) => {
27             return parseInt(s, 16);
28         }
29
30         it('parses hex color strings', () => {
31             const parseColor = CanvasKit.parseColorString;
32             expectColorCloseTo(parseColor('#FED'),
33                 CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), 1));
34             expectColorCloseTo(parseColor('#FEDC'),
35                 CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), hex('CC')/255));
36             expectColorCloseTo(parseColor('#fed'),
37                 CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), 1));
38             expectColorCloseTo(parseColor('#fedc'),
39                 CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), hex('CC')/255));
40         });
41         it('parses rgba color strings', () => {
42             const parseColor = CanvasKit.parseColorString;
43             expectColorCloseTo(parseColor('rgba(117, 33, 64, 0.75)'),
44                 CanvasKit.Color(117, 33, 64, 0.75));
45             expectColorCloseTo(parseColor('rgb(117, 33, 64, 0.75)'),
46                 CanvasKit.Color(117, 33, 64, 0.75));
47             expectColorCloseTo(parseColor('rgba(117,33,64)'),
48                 CanvasKit.Color(117, 33, 64, 1.0));
49             expectColorCloseTo(parseColor('rgb(117,33, 64)'),
50                 CanvasKit.Color(117, 33, 64, 1.0));
51             expectColorCloseTo(parseColor('rgb(117,33, 64, 32%)'),
52                 CanvasKit.Color(117, 33, 64, 0.32));
53             expectColorCloseTo(parseColor('rgb(117,33, 64, 0.001)'),
54                 CanvasKit.Color(117, 33, 64, 0.001));
55             expectColorCloseTo(parseColor('rgb(117,33,64,0)'),
56                 CanvasKit.Color(117, 33, 64, 0.0));
57         });
58         it('parses named color strings', () => {
59             // Keep this one as the _testing version, because we don't include the large
60             // color map by default.
61             const parseColor = CanvasKit._testing.parseColor;
62             expectColorCloseTo(parseColor('grey'),
63                 CanvasKit.Color(128, 128, 128, 1.0));
64             expectColorCloseTo(parseColor('blanchedalmond'),
65                 CanvasKit.Color(255, 235, 205, 1.0));
66             expectColorCloseTo(parseColor('transparent'),
67                 CanvasKit.Color(0, 0, 0, 0));
68         });
69
70         it('properly produces color strings', () => {
71             const colorToString = CanvasKit._testing.colorToString;
72
73             expect(colorToString(CanvasKit.Color(102, 51, 153, 1.0))).toEqual('#663399');
74
75             expect(colorToString(CanvasKit.Color(255, 235, 205, 0.5))).toEqual(
76                                            'rgba(255, 235, 205, 0.50000000)');
77         });
78
79         it('can multiply colors by alpha', () => {
80             const multiplyByAlpha = CanvasKit.multiplyByAlpha;
81
82             const testCases = [
83                    {
84                     inColor:  CanvasKit.Color(102, 51, 153, 1.0),
85                     inAlpha:  1.0,
86                     outColor: CanvasKit.Color(102, 51, 153, 1.0),
87                 },
88                 {
89                     inColor:  CanvasKit.Color(102, 51, 153, 1.0),
90                     inAlpha:  0.8,
91                     outColor: CanvasKit.Color(102, 51, 153, 0.8),
92                 },
93                 {
94                     inColor:  CanvasKit.Color(102, 51, 153, 0.8),
95                     inAlpha:  0.7,
96                     outColor: CanvasKit.Color(102, 51, 153, 0.56),
97                 },
98                 {
99                     inColor:  CanvasKit.Color(102, 51, 153, 0.8),
100                     inAlpha:  1000,
101                     outColor: CanvasKit.Color(102, 51, 153, 1.0),
102                 },
103             ];
104
105             for (const tc of testCases) {
106                 // Print out the test case if the two don't match.
107                 expect(multiplyByAlpha(tc.inColor, tc.inAlpha))
108                       .toEqual(tc.outColor, JSON.stringify(tc));
109             }
110         });
111     }); // end describe('color string parsing')
112
113     describe('fonts', () => {
114         it('can parse font sizes', () => {
115             const parseFontString = CanvasKit._testing.parseFontString;
116
117             const tests = [{
118                     'input': '10px monospace',
119                     'output': {
120                         'style': '',
121                         'variant': '',
122                         'weight': '',
123                         'sizePx': 10,
124                         'family': 'monospace',
125                     }
126                 },
127                 {
128                     'input': '15pt Arial',
129                     'output': {
130                         'style': '',
131                         'variant': '',
132                         'weight': '',
133                         'sizePx': 20,
134                         'family': 'Arial',
135                     }
136                 },
137                 {
138                     'input': '1.5in Arial, san-serif ',
139                     'output': {
140                         'style': '',
141                         'variant': '',
142                         'weight': '',
143                         'sizePx': 144,
144                         'family': 'Arial, san-serif',
145                     }
146                 },
147                 {
148                     'input': '1.5em SuperFont',
149                     'output': {
150                         'style': '',
151                         'variant': '',
152                         'weight': '',
153                         'sizePx': 24,
154                         'family': 'SuperFont',
155                     }
156                 },
157             ];
158
159             for (let i = 0; i < tests.length; i++) {
160                 expect(parseFontString(tests[i].input)).toEqual(tests[i].output);
161             }
162         });
163
164         it('can parse font attributes', () => {
165             const parseFontString = CanvasKit._testing.parseFontString;
166
167             const tests = [{
168                     'input': 'bold 10px monospace',
169                     'output': {
170                         'style': '',
171                         'variant': '',
172                         'weight': 'bold',
173                         'sizePx': 10,
174                         'family': 'monospace',
175                     }
176                 },
177                 {
178                     'input': 'italic bold 10px monospace',
179                     'output': {
180                         'style': 'italic',
181                         'variant': '',
182                         'weight': 'bold',
183                         'sizePx': 10,
184                         'family': 'monospace',
185                     }
186                 },
187                 {
188                     'input': 'italic small-caps bold 10px monospace',
189                     'output': {
190                         'style': 'italic',
191                         'variant': 'small-caps',
192                         'weight': 'bold',
193                         'sizePx': 10,
194                         'family': 'monospace',
195                     }
196                 },
197                 {
198                     'input': 'small-caps bold 10px monospace',
199                     'output': {
200                         'style': '',
201                         'variant': 'small-caps',
202                         'weight': 'bold',
203                         'sizePx': 10,
204                         'family': 'monospace',
205                     }
206                 },
207                 {
208                     'input': 'italic 10px monospace',
209                     'output': {
210                         'style': 'italic',
211                         'variant': '',
212                         'weight': '',
213                         'sizePx': 10,
214                         'family': 'monospace',
215                     }
216                 },
217                 {
218                     'input': 'small-caps 10px monospace',
219                     'output': {
220                         'style': '',
221                         'variant': 'small-caps',
222                         'weight': '',
223                         'sizePx': 10,
224                         'family': 'monospace',
225                     }
226                 },
227                 {
228                     'input': 'normal bold 10px monospace',
229                     'output': {
230                         'style': 'normal',
231                         'variant': '',
232                         'weight': 'bold',
233                         'sizePx': 10,
234                         'family': 'monospace',
235                     }
236                 },
237             ];
238
239             for (let i = 0; i < tests.length; i++) {
240                 expect(parseFontString(tests[i].input)).toEqual(tests[i].output);
241             }
242         });
243     });
244
245     const multipleCanvasTest = (testname, done, test) => {
246         const skcanvas = CanvasKit.MakeCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
247         skcanvas._config = 'software_canvas';
248         const realCanvas = document.getElementById('test');
249         realCanvas._config = 'html_canvas';
250         realCanvas.width = CANVAS_WIDTH;
251         realCanvas.height = CANVAS_HEIGHT;
252
253         if (!done) {
254             console.log('debugging canvaskit');
255             test(realCanvas);
256             test(skcanvas);
257             const png = skcanvas.toDataURL();
258             const img = document.createElement('img');
259             document.body.appendChild(img);
260             img.src = png;
261             debugger;
262             return;
263         }
264
265         let promises = [];
266
267         for (let canvas of [skcanvas, realCanvas]) {
268             test(canvas);
269             // canvas has .toDataURL (even though skcanvas is not a real Canvas)
270             // so this will work.
271             promises.push(reportCanvas(canvas, testname, canvas._config));
272         }
273         Promise.all(promises).then(() => {
274             skcanvas.dispose();
275             done();
276         }).catch(reportError(done));
277     }
278
279     describe('CanvasContext2D API', () => {
280         multipleCanvasGM('all_line_drawing_operations', (canvas) => {
281             const ctx = canvas.getContext('2d');
282             ctx.scale(3.0, 3.0);
283             ctx.moveTo(20, 5);
284             ctx.lineTo(30, 20);
285             ctx.lineTo(40, 10);
286             ctx.lineTo(50, 20);
287             ctx.lineTo(60, 0);
288             ctx.lineTo(20, 5);
289
290             ctx.moveTo(20, 80);
291             ctx.bezierCurveTo(90, 10, 160, 150, 190, 10);
292
293             ctx.moveTo(36, 148);
294             ctx.quadraticCurveTo(66, 188, 120, 136);
295             ctx.lineTo(36, 148);
296
297             ctx.rect(5, 170, 20, 25);
298
299             ctx.moveTo(150, 180);
300             ctx.arcTo(150, 100, 50, 200, 20);
301             ctx.lineTo(160, 160);
302
303             ctx.moveTo(20, 120);
304             ctx.arc(20, 120, 18, 0, 1.75 * Math.PI);
305             ctx.lineTo(20, 120);
306
307             ctx.moveTo(150, 5);
308             ctx.ellipse(130, 25, 30, 10, -1*Math.PI/8, Math.PI/6, 1.5*Math.PI)
309
310             ctx.lineWidth = 2;
311             ctx.stroke();
312
313             // Test edgecases and draw direction
314             ctx.beginPath();
315             ctx.arc(50, 100, 10, Math.PI, -Math.PI/2);
316             ctx.stroke();
317             ctx.beginPath();
318             ctx.arc(75, 100, 10, Math.PI, -Math.PI/2, true);
319             ctx.stroke();
320             ctx.beginPath();
321             ctx.arc(100, 100, 10, Math.PI, 100.1 * Math.PI, true);
322             ctx.stroke();
323             ctx.beginPath();
324             ctx.arc(125, 100, 10, Math.PI, 100.1 * Math.PI, false);
325             ctx.stroke();
326             ctx.beginPath();
327             ctx.ellipse(155, 100, 10, 15, Math.PI/8, 100.1 * Math.PI, Math.PI, true);
328             ctx.stroke();
329             ctx.beginPath();
330             ctx.ellipse(180, 100, 10, 15, Math.PI/8, Math.PI, 100.1 * Math.PI, true);
331             ctx.stroke();
332         });
333
334         multipleCanvasGM('all_matrix_operations', (canvas) => {
335             const ctx = canvas.getContext('2d');
336             ctx.rect(10, 10, 20, 20);
337
338             ctx.scale(2.0, 4.0);
339             ctx.rect(30, 10, 20, 20);
340             ctx.resetTransform();
341
342             ctx.rotate(Math.PI / 3);
343             ctx.rect(50, 10, 20, 20);
344             ctx.resetTransform();
345
346             ctx.translate(30, -2);
347             ctx.rect(70, 10, 20, 20);
348             ctx.resetTransform();
349
350             ctx.translate(60, 0);
351             ctx.rotate(Math.PI / 6);
352             ctx.transform(1.5, 0, 0, 0.5, 0, 0); // effectively scale
353             ctx.rect(90, 10, 20, 20);
354             ctx.resetTransform();
355
356             ctx.save();
357             ctx.setTransform(2, 0, -.5, 2.5, -40, 120);
358             ctx.rect(110, 10, 20, 20);
359             ctx.lineTo(110, 0);
360             ctx.restore();
361             ctx.lineTo(220, 120);
362
363             ctx.scale(3.0, 3.0);
364             ctx.font = '6pt Noto Mono';
365             ctx.fillText('This text should be huge', 10, 80);
366             ctx.resetTransform();
367
368             ctx.strokeStyle = 'black';
369             ctx.lineWidth = 2;
370             ctx.stroke();
371
372             ctx.beginPath();
373             ctx.moveTo(250, 30);
374             ctx.lineTo(250, 80);
375             ctx.scale(3.0, 3.0);
376             ctx.lineTo(280/3, 90/3);
377             ctx.closePath();
378             ctx.strokeStyle = 'black';
379             ctx.lineWidth = 5;
380             ctx.stroke();
381         });
382
383         multipleCanvasGM('shadows_and_save_restore', (canvas) => {
384             const ctx = canvas.getContext('2d');
385             ctx.strokeStyle = '#000';
386             ctx.fillStyle = '#CCC';
387             ctx.shadowColor = 'rebeccapurple';
388             ctx.shadowBlur = 1;
389             ctx.shadowOffsetX = 3;
390             ctx.shadowOffsetY = -8;
391             ctx.rect(10, 10, 30, 30);
392
393             ctx.save();
394             ctx.strokeStyle = '#C00';
395             ctx.fillStyle = '#00C';
396             ctx.shadowBlur = 0;
397             ctx.shadowColor = 'transparent';
398
399             ctx.stroke();
400
401             ctx.restore();
402             ctx.fill();
403
404             ctx.beginPath();
405             ctx.moveTo(36, 148);
406             ctx.quadraticCurveTo(66, 188, 120, 136);
407             ctx.closePath();
408             ctx.stroke();
409
410             ctx.beginPath();
411             ctx.shadowColor = '#993366AA';
412             ctx.shadowOffsetX = 8;
413             ctx.shadowBlur = 5;
414             ctx.setTransform(2, 0, -.5, 2.5, -40, 120);
415             ctx.rect(110, 10, 20, 20);
416             ctx.lineTo(110, 0);
417             ctx.resetTransform();
418             ctx.lineTo(220, 120);
419             ctx.stroke();
420
421             ctx.fillStyle = 'green';
422             ctx.font = '16pt Noto Mono';
423             ctx.fillText('This should be shadowed', 20, 80);
424
425             ctx.beginPath();
426             ctx.lineWidth = 6;
427             ctx.ellipse(10, 290, 30, 30, 0, 0, Math.PI * 2);
428             ctx.scale(2, 1);
429             ctx.moveTo(10, 290)
430             ctx.ellipse(10, 290, 30, 60, 0, 0, Math.PI * 2);
431             ctx.resetTransform();
432             ctx.shadowColor = '#993366AA';
433             ctx.scale(3, 1);
434             ctx.moveTo(10, 290)
435             ctx.ellipse(10, 290, 30, 90, 0, 0, Math.PI * 2);
436             ctx.stroke();
437         });
438
439         multipleCanvasGM('global_dashed_rects', (canvas) => {
440             const ctx = canvas.getContext('2d');
441             ctx.scale(1.1, 1.1);
442             ctx.translate(10, 10);
443             // Shouldn't impact the fillRect calls
444             ctx.setLineDash([5, 3]);
445
446             ctx.fillStyle = 'rgba(200, 0, 100, 0.81)';
447             ctx.fillRect(20, 30, 100, 100);
448
449             ctx.globalAlpha = 0.81;
450             ctx.fillStyle = 'rgba(200, 0, 100, 1.0)';
451             ctx.fillRect(120, 30, 100, 100);
452             // This shouldn't do anything
453             ctx.globalAlpha = 0.1;
454
455             ctx.fillStyle = 'rgba(200, 0, 100, 0.9)';
456             ctx.globalAlpha = 0.9;
457             // Intentional no-op to check ordering
458             ctx.clearRect(220, 30, 100, 100);
459             ctx.fillRect(220, 30, 100, 100);
460
461             ctx.fillRect(320, 30, 100, 100);
462             ctx.clearRect(330, 40, 80, 80);
463
464             ctx.strokeStyle = 'blue';
465             ctx.lineWidth = 3;
466             ctx.setLineDash([5, 3]);
467             ctx.strokeRect(20, 150, 100, 100);
468             ctx.setLineDash([50, 30]);
469             ctx.strokeRect(125, 150, 100, 100);
470             ctx.lineDashOffset = 25;
471             ctx.strokeRect(230, 150, 100, 100);
472             ctx.setLineDash([2, 5, 9]);
473             ctx.strokeRect(335, 150, 100, 100);
474
475             ctx.setLineDash([5, 2]);
476             ctx.moveTo(336, 400);
477             ctx.quadraticCurveTo(366, 488, 120, 450);
478             ctx.lineTo(300, 400);
479             ctx.stroke();
480
481             ctx.font = '36pt Noto Mono';
482             ctx.strokeText('Dashed', 20, 350);
483             ctx.fillText('Not Dashed', 20, 400);
484         });
485
486         multipleCanvasGM('gradients_clip', (canvas) => {
487             const ctx = canvas.getContext('2d');
488
489             const rgradient = ctx.createRadialGradient(200, 300, 10, 100, 100, 300);
490
491             rgradient.addColorStop(0, 'red');
492             rgradient.addColorStop(.7, 'white');
493             rgradient.addColorStop(1, 'blue');
494
495             ctx.fillStyle = rgradient;
496             ctx.globalAlpha = 0.7;
497             ctx.fillRect(0,0,600,600);
498             ctx.globalAlpha = 0.95;
499
500             ctx.beginPath();
501             ctx.arc(300, 100, 90, 0, Math.PI*1.66);
502             ctx.closePath();
503             ctx.strokeStyle = 'yellow';
504             ctx.lineWidth = 5;
505             ctx.stroke();
506             ctx.save();
507             ctx.clip();
508
509             const lgradient = ctx.createLinearGradient(200, 20, 420, 40);
510
511             lgradient.addColorStop(0, 'green');
512             lgradient.addColorStop(.5, 'cyan');
513             lgradient.addColorStop(1, 'orange');
514
515             ctx.fillStyle = lgradient;
516
517             ctx.fillRect(200, 30, 200, 300);
518
519             ctx.restore();
520             ctx.fillRect(550, 550, 40, 40);
521         });
522
523         multipleCanvasGM('get_put_imagedata', (canvas) => {
524             const ctx = canvas.getContext('2d');
525             // Make a gradient so we see if the pixels copying worked
526             const grad = ctx.createLinearGradient(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
527             grad.addColorStop(0, 'yellow');
528             grad.addColorStop(1, 'red');
529             ctx.fillStyle = grad;
530             ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
531
532             const iData = ctx.getImageData(400, 100, 200, 150);
533             expect(iData.width).toBe(200);
534             expect(iData.height).toBe(150);
535             expect(iData.data.byteLength).toBe(200*150*4);
536             ctx.putImageData(iData, 10, 10);
537             ctx.putImageData(iData, 350, 350, 100, 75, 45, 40);
538             ctx.strokeRect(350, 350, 200, 150);
539
540             const box = ctx.createImageData(20, 40);
541             ctx.putImageData(box, 10, 300);
542             const biggerBox = ctx.createImageData(iData);
543             ctx.putImageData(biggerBox, 10, 350);
544             expect(biggerBox.width).toBe(iData.width);
545             expect(biggerBox.height).toBe(iData.height);
546         });
547
548         multipleCanvasGM('shadows_with_rotate_skbug_9947', (canvas) => {
549             const ctx = canvas.getContext('2d');
550             const angle = 240;
551             ctx.fillStyle = 'white';
552             ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
553             ctx.save();
554             ctx.translate(80, 80);
555             ctx.rotate((angle * Math.PI) / 180);
556             ctx.shadowOffsetX = 10;
557             ctx.shadowOffsetY = 10;
558             ctx.shadowColor = 'rgba(100,100,100,0.5)';
559             ctx.shadowBlur = 1;
560             ctx.fillStyle = 'black';
561             ctx.strokeStyle = 'red';
562             ctx.beginPath();
563             ctx.rect(-20, -20, 40, 40);
564             ctx.fill();
565             ctx.fillRect(30, 30, 40, 40);
566             ctx.strokeRect(30, -20, 40, 40);
567             ctx.fillText('text', -20, -30);
568             ctx.restore();
569         });
570
571         describe('using images', () => {
572             let skImageData = null;
573             let htmlImage = null;
574             const skPromise = fetch('/assets/mandrill_512.png')
575                 .then((response) => response.arrayBuffer())
576                 .then((buffer) => {
577                     skImageData = buffer;
578
579                 });
580             const realPromise = fetch('/assets/mandrill_512.png')
581                 .then((response) => response.blob())
582                 .then((blob) => createImageBitmap(blob))
583                 .then((bitmap) => {
584                     htmlImage = bitmap;
585                 });
586
587             beforeEach(async () => {
588                 await skPromise;
589                 await realPromise;
590             });
591
592             multipleCanvasGM('draw_patterns', (canvas) => {
593                 const ctx = canvas.getContext('2d');
594                 let img = htmlImage;
595                 if (canvas._config === 'software_canvas') {
596                     img = canvas.decodeImage(skImageData);
597                 }
598                 ctx.fillStyle = '#EEE';
599                 ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
600                 ctx.lineWidth = 20;
601                 ctx.scale(0.2, 0.4);
602
603                 let pattern = ctx.createPattern(img, 'repeat');
604                 ctx.fillStyle = pattern;
605                 ctx.fillRect(0, 0, 1500, 750);
606
607                 pattern = ctx.createPattern(img, 'repeat-x');
608                 ctx.fillStyle = pattern;
609                 ctx.fillRect(1500, 0, 3000, 750);
610
611                 ctx.globalAlpha = 0.7
612                 pattern = ctx.createPattern(img, 'repeat-y');
613                 ctx.fillStyle = pattern;
614                 ctx.fillRect(0, 750, 1500, 1500);
615                 ctx.strokeRect(0, 750, 1500, 1500);
616
617                 pattern = ctx.createPattern(img, 'no-repeat');
618                 ctx.fillStyle = pattern;
619                 pattern.setTransform({a: 1, b: -.1, c:.1, d: 0.5, e: 1800, f:800});
620                 ctx.fillRect(0, 0, 3000, 1500);
621             });
622
623             multipleCanvasGM('draw_image', (canvas) => {
624                 let ctx = canvas.getContext('2d');
625                 let img = htmlImage;
626                 if (canvas._config === 'software_canvas') {
627                     img = canvas.decodeImage(skImageData);
628                 }
629                 ctx.drawImage(img, 30, -200);
630
631                 ctx.globalAlpha = 0.7
632                 ctx.rotate(.1);
633                 ctx.imageSmoothingQuality = 'medium';
634                 ctx.drawImage(img, 200, 350, 150, 100);
635                 ctx.rotate(-.2);
636                 ctx.imageSmoothingEnabled = false;
637                 ctx.drawImage(img, 100, 150, 400, 350, 10, 400, 150, 100);
638             });
639         }); // end describe('using images')
640
641         {
642             const drawPoint = (ctx, x, y, color) => {
643                 ctx.fillStyle = color;
644                 ctx.fillRect(x, y, 1, 1);
645             }
646             const IN = 'purple';
647             const OUT = 'orange';
648             const SCALE = 8;
649
650             // Check to see if these points are in or out on each of the
651             // test configurations.
652             const pts = [[3, 3], [4, 4], [5, 5], [10, 10], [8, 10], [6, 10],
653                          [6.5, 9], [15, 10], [17, 10], [17, 11], [24, 24],
654                          [25, 25], [26, 26], [27, 27]];
655             const tests = [
656                 {
657                     xOffset: 0,
658                     yOffset: 0,
659                     fillType: 'nonzero',
660                     strokeWidth: 0,
661                     testFn: (ctx, x, y) => ctx.isPointInPath(x * SCALE, y * SCALE, 'nonzero'),
662                 },
663                 {
664                     xOffset: 30,
665                     yOffset: 0,
666                     fillType: 'evenodd',
667                     strokeWidth: 0,
668                     testFn: (ctx, x, y) => ctx.isPointInPath(x * SCALE, y * SCALE, 'evenodd'),
669                 },
670                 {
671                     xOffset: 0,
672                     yOffset: 30,
673                     fillType: null,
674                     strokeWidth: 1,
675                     testFn: (ctx, x, y) => ctx.isPointInStroke(x * SCALE, y * SCALE),
676                 },
677                 {
678                     xOffset: 30,
679                     yOffset: 30,
680                     fillType: null,
681                     strokeWidth: 2,
682                     testFn: (ctx, x, y) => ctx.isPointInStroke(x * SCALE, y * SCALE),
683                 },
684             ];
685             multipleCanvasGM('points_in_path_stroke', (canvas) => {
686                 const ctx = canvas.getContext('2d');
687                 ctx.font = '20px Noto Mono';
688                 // Draw some visual aids
689                 ctx.fillText('path-nonzero', 60, 30);
690                 ctx.fillText('path-evenodd', 300, 30);
691                 ctx.fillText('stroke-1px-wide', 60, 260);
692                 ctx.fillText('stroke-2px-wide', 300, 260);
693                 ctx.fillText('purple is IN, orange is OUT', 20, 560);
694
695                 // Scale up to make single pixels easier to see
696                 ctx.scale(SCALE, SCALE);
697                 for (const test of tests) {
698                     ctx.beginPath();
699                     const xOffset = test.xOffset;
700                     const yOffset = test.yOffset;
701
702                     ctx.fillStyle = '#AAA';
703                     ctx.lineWidth = test.strokeWidth;
704                     ctx.rect(5+xOffset, 5+yOffset, 20, 20);
705                     ctx.arc(15+xOffset, 15+yOffset, 8, 0, Math.PI*2, false);
706                     if (test.fillType) {
707                         ctx.fill(test.fillType);
708                     } else {
709                         ctx.stroke();
710                     }
711
712                     for (const pt of pts) {
713                         let [x, y] = pt;
714                         x += xOffset;
715                         y += yOffset;
716                         // naively apply transform when querying because the points queried
717                         // ignore the CTM.
718                         if (test.testFn(ctx, x, y)) {
719                           drawPoint(ctx, x, y, IN);
720                         } else {
721                           drawPoint(ctx, x, y, OUT);
722                         }
723                     }
724                 }
725             });
726         }
727
728         describe('loading custom fonts', () => {
729             const realFontLoaded = new FontFace('BungeeNonSystem', 'url(/assets/Bungee-Regular.ttf)', {
730                 'family': 'BungeeNonSystem', // Make sure the canvas does not use the system font
731                 'style': 'normal',
732                 'weight': '400',
733             }).load().then((font) => {
734                 document.fonts.add(font);
735             });
736
737             let fontBuffer = null;
738             const skFontLoaded = fetch('/assets/Bungee-Regular.ttf').then(
739                 (response) => response.arrayBuffer()).then(
740                 (buffer) => {
741                     fontBuffer = buffer;
742                 });
743
744             beforeEach(async () => {
745                 await realFontLoaded;
746                 await skFontLoaded;
747             });
748
749             multipleCanvasGM('custom_font', (canvas) => {
750                 if (canvas.loadFont) {
751                     canvas.loadFont(fontBuffer, {
752                         'family': 'BungeeNonSystem',
753                         'style': 'normal',
754                         'weight': '400',
755                     });
756                 }
757                 const ctx = canvas.getContext('2d');
758
759                 ctx.font = '20px monospace';
760                 ctx.fillText('20 px monospace', 10, 30);
761
762                 ctx.font = '2.0em BungeeNonSystem';
763                 ctx.fillText('2.0em Bungee filled', 10, 80);
764                 ctx.strokeText('2.0em Bungee stroked', 10, 130);
765
766                 const m = ctx.measureText('A phrase in English');
767                 expect(m).toBeTruthy();
768                 expect(m['width']).toBeTruthy();
769
770                 ctx.font = '40pt monospace';
771                 ctx.strokeText('40pt monospace', 10, 200);
772
773                 // bold wasn't defined, so should fallback to just the 400 weight
774                 ctx.font = 'bold 45px BungeeNonSystem';
775                 ctx.fillText('45px Bungee filled', 10, 260);
776             });
777         }); // describe('loading custom fonts')
778
779         it('can read default properties', () => {
780             const skcanvas = CanvasKit.MakeCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
781             const realCanvas = document.getElementById('test');
782             realCanvas.width = CANVAS_WIDTH;
783             realCanvas.height = CANVAS_HEIGHT;
784
785             const skcontext = skcanvas.getContext('2d');
786             const realContext = realCanvas.getContext('2d');
787             // The skia canvas only comes with a monospace font by default
788             // Set the html canvas to be monospace too.
789             realContext.font = '10px monospace';
790
791             const toTest = ['font', 'lineWidth', 'strokeStyle', 'lineCap',
792                             'lineJoin', 'miterLimit', 'shadowOffsetY',
793                             'shadowBlur', 'shadowColor', 'shadowOffsetX',
794                             'globalAlpha', 'globalCompositeOperation',
795                             'lineDashOffset', 'imageSmoothingEnabled',
796                             'imageFilterQuality'];
797
798             // Compare all the default values of the properties of skcanvas
799             // to the default values on the properties of a real canvas.
800             for(let attr of toTest) {
801                 expect(skcontext[attr]).toBe(realContext[attr], attr);
802             }
803
804             skcanvas.dispose();
805         });
806     }); // end describe('CanvasContext2D API')
807
808     describe('Path2D API', () => {
809         multipleCanvasGM('path2d_line_drawing_operations', (canvas) => {
810             const ctx = canvas.getContext('2d');
811             let clock;
812             let path;
813             if (canvas.makePath2D) {
814                 clock = canvas.makePath2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z');
815                 path = canvas.makePath2D();
816             } else {
817                 clock = new Path2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z')
818                 path = new Path2D();
819             }
820             path.moveTo(20, 5);
821             path.lineTo(30, 20);
822             path.lineTo(40, 10);
823             path.lineTo(50, 20);
824             path.lineTo(60, 0);
825             path.lineTo(20, 5);
826
827             path.moveTo(20, 80);
828             path.bezierCurveTo(90, 10, 160, 150, 190, 10);
829
830             path.moveTo(36, 148);
831             path.quadraticCurveTo(66, 188, 120, 136);
832             path.lineTo(36, 148);
833
834             path.rect(5, 170, 20, 25);
835
836             path.moveTo(150, 180);
837             path.arcTo(150, 100, 50, 200, 20);
838             path.lineTo(160, 160);
839
840             path.moveTo(20, 120);
841             path.arc(20, 120, 18, 0, 1.75 * Math.PI);
842             path.lineTo(20, 120);
843
844             path.moveTo(150, 5);
845             path.ellipse(130, 25, 30, 10, -1*Math.PI/8, Math.PI/6, 1.5*Math.PI)
846
847             ctx.lineWidth = 2;
848             ctx.scale(3.0, 3.0);
849             ctx.stroke(path);
850             ctx.stroke(clock);
851         });
852     }); // end describe('Path2D API')
853 });