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