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