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