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