Home | History | Annotate | Download | only in tests
      1 // The increased timeout is especially needed with larger binaries
      2 // like in the debug/gpu build
      3 jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
      4 
      5 describe('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