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