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