1describe('Path Behavior', () => { 2 let container; 3 4 beforeEach(async () => { 5 await LoadCanvasKit; 6 container = document.createElement('div'); 7 container.innerHTML = ` 8 <canvas width=600 height=600 id=test></canvas> 9 <canvas width=600 height=600 id=report></canvas>`; 10 document.body.appendChild(container); 11 }); 12 13 afterEach(() => { 14 document.body.removeChild(container); 15 }); 16 17 gm('path_api_example', (canvas) => { 18 const paint = new CanvasKit.Paint(); 19 paint.setStrokeWidth(1.0); 20 paint.setAntiAlias(true); 21 paint.setColor(CanvasKit.Color(0, 0, 0, 1.0)); 22 paint.setStyle(CanvasKit.PaintStyle.Stroke); 23 24 const path = new CanvasKit.Path(); 25 path.moveTo(20, 5); 26 path.lineTo(30, 20); 27 path.lineTo(40, 10); 28 path.lineTo(50, 20); 29 path.lineTo(60, 0); 30 path.lineTo(20, 5); 31 32 path.moveTo(20, 80); 33 path.cubicTo(90, 10, 160, 150, 190, 10); 34 35 path.moveTo(36, 148); 36 path.quadTo(66, 188, 120, 136); 37 path.lineTo(36, 148); 38 39 path.moveTo(150, 180); 40 path.arcToTangent(150, 100, 50, 200, 20); 41 path.lineTo(160, 160); 42 43 path.moveTo(20, 120); 44 path.lineTo(20, 120); 45 46 path.transform([2, 0, 0, 47 0, 2, 0, 48 0, 0, 1 ]); 49 50 canvas.drawPath(path, paint); 51 52 const rrect = CanvasKit.RRectXY([100, 10, 140, 62], 10, 4); 53 54 const rrectPath = new CanvasKit.Path().addRRect(rrect, true); 55 56 canvas.drawPath(rrectPath, paint); 57 58 rrectPath.delete(); 59 path.delete(); 60 paint.delete(); 61 // See PathKit for more tests, since they share implementation 62 }); 63 64 it('can create a path from an SVG string', () => { 65 //.This is a parallelogram from 66 // https://upload.wikimedia.org/wikipedia/commons/e/e7/Simple_parallelogram.svg 67 const path = CanvasKit.Path.MakeFromSVGString( 68 'M 205,5 L 795,5 L 595,295 L 5,295 L 205,5 z'); 69 70 const cmds = path.toCmds(); 71 expect(cmds).toBeTruthy(); 72 // 1 move, 4 lines, 1 close 73 // each element in cmds is an array, with index 0 being the verb, and the rest being args 74 expect(cmds).toEqual(Float32Array.of( 75 CanvasKit.MOVE_VERB, 205, 5, 76 CanvasKit.LINE_VERB, 795, 5, 77 CanvasKit.LINE_VERB, 595, 295, 78 CanvasKit.LINE_VERB, 5, 295, 79 CanvasKit.LINE_VERB, 205, 5, 80 CanvasKit.CLOSE_VERB)); 81 path.delete(); 82 }); 83 84 it('can create a path by combining two other paths', () => { 85 // Get the intersection of two overlapping squares and verify that it is the smaller square. 86 const pathOne = new CanvasKit.Path(); 87 pathOne.addRect([10, 10, 20, 20]); 88 89 const pathTwo = new CanvasKit.Path(); 90 pathTwo.addRect([15, 15, 30, 30]); 91 92 const path = CanvasKit.Path.MakeFromOp(pathOne, pathTwo, CanvasKit.PathOp.Intersect); 93 const cmds = path.toCmds(); 94 expect(cmds).toBeTruthy(); 95 expect(cmds).toEqual(Float32Array.of( 96 CanvasKit.MOVE_VERB, 15, 15, 97 CanvasKit.LINE_VERB, 20, 15, 98 CanvasKit.LINE_VERB, 20, 20, 99 CanvasKit.LINE_VERB, 15, 20, 100 CanvasKit.CLOSE_VERB)); 101 path.delete(); 102 pathOne.delete(); 103 pathTwo.delete(); 104 }); 105 106 it('can create an SVG string from a path', () => { 107 const cmds = [CanvasKit.MOVE_VERB, 205, 5, 108 CanvasKit.LINE_VERB, 795, 5, 109 CanvasKit.LINE_VERB, 595, 295, 110 CanvasKit.LINE_VERB, 5, 295, 111 CanvasKit.LINE_VERB, 205, 5, 112 CanvasKit.CLOSE_VERB]; 113 const path = CanvasKit.Path.MakeFromCmds(cmds); 114 115 const svgStr = path.toSVGString(); 116 // We output it in terse form, which is different than Wikipedia's version 117 expect(svgStr).toEqual('M205 5L795 5L595 295L5 295L205 5Z'); 118 path.delete(); 119 }); 120 121 it('can create a path with malloced verbs, points, weights', () => { 122 const mVerbs = CanvasKit.Malloc(Uint8Array, 6); 123 const mPoints = CanvasKit.Malloc(Float32Array, 18); 124 const mWeights = CanvasKit.Malloc(Float32Array, 1); 125 mVerbs.toTypedArray().set([CanvasKit.MOVE_VERB, CanvasKit.LINE_VERB, 126 CanvasKit.QUAD_VERB, CanvasKit.CONIC_VERB, CanvasKit.CUBIC_VERB, CanvasKit.CLOSE_VERB 127 ]); 128 129 mPoints.toTypedArray().set([ 130 1,2, // moveTo 131 3,4, // lineTo 132 5,6,7,8, // quadTo 133 9,10,11,12, // conicTo 134 13,14,15,16,17,18, // cubicTo 135 ]); 136 137 mWeights.toTypedArray().set([117]); 138 139 let path = CanvasKit.Path.MakeFromVerbsPointsWeights(mVerbs, mPoints, mWeights); 140 141 let cmds = path.toCmds(); 142 expect(cmds).toEqual(Float32Array.of( 143 CanvasKit.MOVE_VERB, 1, 2, 144 CanvasKit.LINE_VERB, 3, 4, 145 CanvasKit.QUAD_VERB, 5, 6, 7, 8, 146 CanvasKit.CONIC_VERB, 9, 10, 11, 12, 117, 147 CanvasKit.CUBIC_VERB, 13, 14, 15, 16, 17, 18, 148 CanvasKit.CLOSE_VERB, 149 )); 150 path.delete(); 151 152 // If given insufficient points, it stops early (but doesn't read out of bounds). 153 path = CanvasKit.Path.MakeFromVerbsPointsWeights(mVerbs, mPoints.subarray(0, 10), mWeights); 154 155 cmds = path.toCmds(); 156 expect(cmds).toEqual(Float32Array.of( 157 CanvasKit.MOVE_VERB, 1, 2, 158 CanvasKit.LINE_VERB, 3, 4, 159 CanvasKit.QUAD_VERB, 5, 6, 7, 8, 160 )); 161 path.delete(); 162 CanvasKit.Free(mVerbs); 163 CanvasKit.Free(mPoints); 164 CanvasKit.Free(mWeights); 165 }); 166 167 it('can create and update a path with verbs and points (no weights)', () => { 168 const path = CanvasKit.Path.MakeFromVerbsPointsWeights( 169 [CanvasKit.MOVE_VERB, CanvasKit.LINE_VERB], 170 [1,2, 3,4]); 171 let cmds = path.toCmds(); 172 expect(cmds).toEqual(Float32Array.of( 173 CanvasKit.MOVE_VERB, 1, 2, 174 CanvasKit.LINE_VERB, 3, 4 175 )); 176 177 path.addVerbsPointsWeights( 178 [CanvasKit.QUAD_VERB, CanvasKit.CLOSE_VERB], 179 [5,6,7,8], 180 ); 181 182 cmds = path.toCmds(); 183 expect(cmds).toEqual(Float32Array.of( 184 CanvasKit.MOVE_VERB, 1, 2, 185 CanvasKit.LINE_VERB, 3, 4, 186 CanvasKit.QUAD_VERB, 5, 6, 7, 8, 187 CanvasKit.CLOSE_VERB 188 )); 189 path.delete(); 190 }); 191 192 193 it('can add points to a path in bulk', () => { 194 const mVerbs = CanvasKit.Malloc(Uint8Array, 6); 195 const mPoints = CanvasKit.Malloc(Float32Array, 18); 196 const mWeights = CanvasKit.Malloc(Float32Array, 1); 197 mVerbs.toTypedArray().set([CanvasKit.MOVE_VERB, CanvasKit.LINE_VERB, 198 CanvasKit.QUAD_VERB, CanvasKit.CONIC_VERB, CanvasKit.CUBIC_VERB, CanvasKit.CLOSE_VERB 199 ]); 200 201 mPoints.toTypedArray().set([ 202 1,2, // moveTo 203 3,4, // lineTo 204 5,6,7,8, // quadTo 205 9,10,11,12, // conicTo 206 13,14,15,16,17,18, // cubicTo 207 ]); 208 209 mWeights.toTypedArray().set([117]); 210 211 const path = new CanvasKit.Path(); 212 path.lineTo(77, 88); 213 path.addVerbsPointsWeights(mVerbs, mPoints, mWeights); 214 215 let cmds = path.toCmds(); 216 expect(cmds).toEqual(Float32Array.of( 217 CanvasKit.MOVE_VERB, 0, 0, 218 CanvasKit.LINE_VERB, 77, 88, 219 CanvasKit.MOVE_VERB, 1, 2, 220 CanvasKit.LINE_VERB, 3, 4, 221 CanvasKit.QUAD_VERB, 5, 6, 7, 8, 222 CanvasKit.CONIC_VERB, 9, 10, 11, 12, 117, 223 CanvasKit.CUBIC_VERB, 13, 14, 15, 16, 17, 18, 224 CanvasKit.CLOSE_VERB, 225 )); 226 227 path.rewind(); 228 cmds = path.toCmds(); 229 expect(cmds).toEqual(new Float32Array(0)); 230 231 path.delete(); 232 CanvasKit.Free(mVerbs); 233 CanvasKit.Free(mPoints); 234 CanvasKit.Free(mWeights); 235 }); 236 237 it('can retrieve points from a path', () => { 238 const path = new CanvasKit.Path(); 239 path.addRect([10, 15, 20, 25]); 240 241 let pt = path.getPoint(0); 242 expect(pt[0]).toEqual(10); 243 expect(pt[1]).toEqual(15); 244 245 path.getPoint(2, pt); 246 expect(pt[0]).toEqual(20); 247 expect(pt[1]).toEqual(25); 248 249 path.getPoint(1000, pt); // off the end returns (0, 0) as per the docs. 250 expect(pt[0]).toEqual(0); 251 expect(pt[1]).toEqual(0); 252 253 path.delete(); 254 }); 255 256 gm('offset_path', (canvas) => { 257 const path = starPath(CanvasKit); 258 259 const paint = new CanvasKit.Paint(); 260 paint.setStyle(CanvasKit.PaintStyle.Stroke); 261 paint.setStrokeWidth(5.0); 262 paint.setAntiAlias(true); 263 paint.setColor(CanvasKit.BLACK); 264 265 canvas.clear(CanvasKit.WHITE); 266 267 canvas.drawPath(path, paint); 268 path.offset(80, 40); 269 canvas.drawPath(path, paint); 270 271 path.delete(); 272 paint.delete(); 273 }); 274 275 gm('oval_path', (canvas) => { 276 const paint = new CanvasKit.Paint(); 277 278 paint.setStyle(CanvasKit.PaintStyle.Stroke); 279 paint.setStrokeWidth(5.0); 280 paint.setAntiAlias(true); 281 paint.setColor(CanvasKit.BLACK); 282 283 canvas.clear(CanvasKit.WHITE); 284 285 const path = new CanvasKit.Path(); 286 path.moveTo(5, 5); 287 path.lineTo(10, 120); 288 path.addOval(CanvasKit.LTRBRect(10, 20, 100, 200), false, 3); 289 path.lineTo(300, 300); 290 291 canvas.drawPath(path, paint); 292 293 path.delete(); 294 paint.delete(); 295 }); 296 297 gm('bounds_path', (canvas) => { 298 const paint = new CanvasKit.Paint(); 299 300 paint.setStyle(CanvasKit.PaintStyle.Stroke); 301 paint.setStrokeWidth(5.0); 302 paint.setAntiAlias(true); 303 paint.setColor(CanvasKit.BLACK); 304 305 canvas.clear(CanvasKit.WHITE); 306 307 const path = new CanvasKit.Path(); 308 // Arbitrary points to make an interesting curve. 309 path.moveTo(97, 225); 310 path.cubicTo(20, 400, 404, 75, 243, 271); 311 312 canvas.drawPath(path, paint); 313 314 const bounds = new Float32Array(4); 315 path.getBounds(bounds); 316 317 paint.setColor(CanvasKit.BLUE); 318 paint.setStrokeWidth(3.0); 319 canvas.drawRect(bounds, paint); 320 321 path.computeTightBounds(bounds); 322 paint.setColor(CanvasKit.RED); 323 paint.setStrokeWidth(3.0); 324 canvas.drawRect(bounds, paint); 325 326 path.delete(); 327 paint.delete(); 328 }); 329 330 gm('arcto_path', (canvas) => { 331 const paint = new CanvasKit.Paint(); 332 333 paint.setStyle(CanvasKit.PaintStyle.Stroke); 334 paint.setStrokeWidth(5.0); 335 paint.setAntiAlias(true); 336 paint.setColor(CanvasKit.BLACK); 337 338 canvas.clear(CanvasKit.WHITE); 339 340 const path = new CanvasKit.Path(); 341 342 // - x1, y1, x2, y2, radius 343 path.arcToTangent(40, 0, 40, 40, 40); 344 // - oval (as Rect), startAngle, sweepAngle, forceMoveTo 345 path.arcToOval(CanvasKit.LTRBRect(90, 10, 120, 200), 30, 300, true); 346 // - rx, ry, xAxisRotate, useSmallArc, isCCW, x, y 347 path.moveTo(5, 105); 348 path.arcToRotated(24, 24, 45, true, false, 82, 156); 349 350 canvas.drawPath(path, paint); 351 352 path.delete(); 353 paint.delete(); 354 }); 355 356 gm('path_relative', (canvas) => { 357 const paint = new CanvasKit.Paint(); 358 paint.setStrokeWidth(1.0); 359 paint.setAntiAlias(true); 360 paint.setColor(CanvasKit.Color(0, 0, 0, 1.0)); 361 paint.setStyle(CanvasKit.PaintStyle.Stroke); 362 363 const path = new CanvasKit.Path(); 364 path.rMoveTo(20, 5) 365 .rLineTo(10, 15) // 30, 20 366 .rLineTo(10, -5); // 40, 10 367 path.rLineTo(10, 10); // 50, 20 368 path.rLineTo(10, -20); // 60, 0 369 path.rLineTo(-40, 5); // 20, 5 370 371 path.moveTo(20, 80) 372 .rCubicTo(70, -70, 140, 70, 170, -70); // 90, 10, 160, 150, 190, 10 373 374 path.moveTo(36, 148) 375 .rQuadTo(30, 40, 84, -12) // 66, 188, 120, 136 376 .lineTo(36, 148); 377 378 path.moveTo(150, 180) 379 .rArcTo(24, 24, 45, true, false, -68, -24); // 82, 156 380 path.lineTo(160, 160); 381 382 canvas.drawPath(path, paint); 383 384 path.delete(); 385 paint.delete(); 386 }); 387 388 it('can measure the contours of a path', () => { 389 const path = new CanvasKit.Path(); 390 path.moveTo(10, 10) 391 .lineTo(40, 50); // should be length 50 because of the 3/4/5 triangle rule 392 393 path.moveTo(80, 0) 394 .lineTo(80, 10) 395 .lineTo(100, 5) 396 .lineTo(80, 0); 397 398 const meas = new CanvasKit.ContourMeasureIter(path, false, 1); 399 let cont = meas.next(); 400 expect(cont).toBeTruthy(); 401 402 expect(cont.length()).toBeCloseTo(50.0, 3); 403 const pt = cont.getPosTan(28.7); // arbitrary point 404 expect(pt[0]).toBeCloseTo(27.22, 3); // x 405 expect(pt[1]).toBeCloseTo(32.96, 3); // y 406 expect(pt[2]).toBeCloseTo(0.6, 3); // dy 407 expect(pt[3]).toBeCloseTo(0.8, 3); // dy 408 409 pt.set([-1, -1, -1, -1]); // fill with sentinel values. 410 cont.getPosTan(28.7, pt); // arbitrary point again, passing in an array to copy into. 411 expect(pt[0]).toBeCloseTo(27.22, 3); // x 412 expect(pt[1]).toBeCloseTo(32.96, 3); // y 413 expect(pt[2]).toBeCloseTo(0.6, 3); // dy 414 expect(pt[3]).toBeCloseTo(0.8, 3); // dy 415 416 const subpath = cont.getSegment(20, 40, true); // make sure this doesn't crash 417 418 cont.delete(); 419 cont = meas.next(); 420 expect(cont).toBeTruthy(); 421 expect(cont.length()).toBeCloseTo(51.231, 3); 422 423 cont.delete(); 424 expect(meas.next()).toBeFalsy(); 425 426 meas.delete(); 427 path.delete(); 428 }); 429 430 gm('drawpoly_path', (canvas) => { 431 const paint = new CanvasKit.Paint(); 432 paint.setStrokeWidth(1.0); 433 paint.setAntiAlias(true); 434 paint.setColor(CanvasKit.Color(0, 0, 0, 1.0)); 435 paint.setStyle(CanvasKit.PaintStyle.Stroke); 436 437 const points = [5, 5, 30, 20, 55, 5, 55, 50, 30, 30, 5, 50]; 438 439 const pointsObj = CanvasKit.Malloc(Float32Array, 6 * 2); 440 const mPoints = pointsObj.toTypedArray(); 441 mPoints.set([105, 105, 130, 120, 155, 105, 155, 150, 130, 130, 105, 150]); 442 443 const path = new CanvasKit.Path(); 444 path.addPoly(points, true) 445 .moveTo(100, 0) 446 .addPoly(mPoints, true); 447 448 canvas.drawPath(path, paint); 449 CanvasKit.Free(pointsObj); 450 451 path.delete(); 452 paint.delete(); 453 }); 454 455 // Test trim, adding paths to paths, and a bunch of other path methods. 456 gm('trim_path', (canvas) => { 457 canvas.clear(CanvasKit.WHITE); 458 459 const paint = new CanvasKit.Paint(); 460 paint.setStrokeWidth(1.0); 461 paint.setAntiAlias(true); 462 paint.setColor(CanvasKit.Color(0, 0, 0, 1.0)); 463 paint.setStyle(CanvasKit.PaintStyle.Stroke); 464 465 const arcpath = new CanvasKit.Path(); 466 arcpath.arc(400, 400, 100, 0, -90, false) // x, y, radius, startAngle, endAngle, ccw 467 .dash(3, 1, 0) 468 .conicTo(10, 20, 30, 40, 5) 469 .rConicTo(60, 70, 80, 90, 5) 470 .trim(0.2, 1, false); 471 472 const path = new CanvasKit.Path(); 473 path.addArc(CanvasKit.LTRBRect(10, 20, 100, 200), 30, 300) 474 .addRect(CanvasKit.LTRBRect(200, 200, 300, 300)) // test single arg, default cw 475 .addRect(CanvasKit.LTRBRect(240, 240, 260, 260), true) // test two arg, true means ccw 476 .addRect([260, 260, 290, 290], true) // test five arg, true means ccw 477 .addRRect([300, 10, 500, 290, // Rect in LTRB order 478 60, 60, 60, 60, 60, 60, 60, 60], // all radii are the same 479 false) // ccw 480 .addRRect(CanvasKit.RRectXY([350, 60, 450, 240], 20, 80), true) // Rect, rx, ry, ccw 481 .addPath(arcpath) 482 .transform(0.54, -0.84, 390.35, 483 0.84, 0.54, -114.53, 484 0, 0, 1); 485 486 canvas.drawPath(path, paint); 487 488 path.delete(); 489 paint.delete(); 490 }); 491 492 gm('winding_example', (canvas) => { 493 // Inspired by https://fiddle.skia.org/c/@Path_FillType_a 494 const path = new CanvasKit.Path(); 495 // Draw overlapping rects on top 496 path.addRect(CanvasKit.LTRBRect(10, 10, 30, 30), false); 497 path.addRect(CanvasKit.LTRBRect(20, 20, 40, 40), false); 498 // Draw overlapping rects on bottom, with different direction lines. 499 path.addRect(CanvasKit.LTRBRect(10, 60, 30, 80), false); 500 path.addRect(CanvasKit.LTRBRect(20, 70, 40, 90), true); 501 502 expect(path.getFillType()).toEqual(CanvasKit.FillType.Winding); 503 504 // Draw the two rectangles on the left side. 505 const paint = new CanvasKit.Paint(); 506 paint.setStyle(CanvasKit.PaintStyle.Stroke); 507 canvas.drawPath(path, paint); 508 509 const clipRect = CanvasKit.LTRBRect(0, 0, 51, 100); 510 paint.setStyle(CanvasKit.PaintStyle.Fill); 511 512 for (const fillType of [CanvasKit.FillType.Winding, CanvasKit.FillType.EvenOdd]) { 513 canvas.translate(51, 0); 514 canvas.save(); 515 canvas.clipRect(clipRect, CanvasKit.ClipOp.Intersect, false); 516 path.setFillType(fillType); 517 canvas.drawPath(path, paint); 518 canvas.restore(); 519 } 520 521 path.delete(); 522 paint.delete(); 523 }); 524 525 gm('as_winding', (canvas) => { 526 const evenOddPath = new CanvasKit.Path(); 527 // Draw overlapping rects 528 evenOddPath.addRect(CanvasKit.LTRBRect(10, 10, 70, 70), false); 529 evenOddPath.addRect(CanvasKit.LTRBRect(30, 30, 50, 50), false); 530 evenOddPath.setFillType(CanvasKit.FillType.EvenOdd); 531 532 const evenOddCmds = evenOddPath.toCmds(); 533 expect(evenOddCmds).toEqual(Float32Array.of( 534 CanvasKit.MOVE_VERB, 10, 10, 535 CanvasKit.LINE_VERB, 70, 10, 536 CanvasKit.LINE_VERB, 70, 70, 537 CanvasKit.LINE_VERB, 10, 70, 538 CanvasKit.CLOSE_VERB, 539 CanvasKit.MOVE_VERB, 30, 30, // This contour is drawn 540 CanvasKit.LINE_VERB, 50, 30, // clockwise, as specified. 541 CanvasKit.LINE_VERB, 50, 50, 542 CanvasKit.LINE_VERB, 30, 50, 543 CanvasKit.CLOSE_VERB 544 )); 545 546 const windingPath = evenOddPath.makeAsWinding(); 547 548 expect(windingPath.getFillType()).toBe(CanvasKit.FillType.Winding); 549 const windingCmds = windingPath.toCmds(); 550 expect(windingCmds).toEqual(Float32Array.of( 551 CanvasKit.MOVE_VERB, 10, 10, 552 CanvasKit.LINE_VERB, 70, 10, 553 CanvasKit.LINE_VERB, 70, 70, 554 CanvasKit.LINE_VERB, 10, 70, 555 CanvasKit.CLOSE_VERB, 556 CanvasKit.MOVE_VERB, 30, 50, // This contour has been 557 CanvasKit.LINE_VERB, 50, 50, // re-drawn counter-clockwise 558 CanvasKit.LINE_VERB, 50, 30, // so that it covers the same 559 CanvasKit.LINE_VERB, 30, 30, // area, but with the winding fill type. 560 CanvasKit.CLOSE_VERB 561 )); 562 563 const paint = new CanvasKit.Paint(); 564 paint.setStyle(CanvasKit.PaintStyle.Fill); 565 const font = new CanvasKit.Font(null, 20); 566 567 canvas.drawText('Original path (even odd)', 5, 20, paint, font); 568 canvas.translate(0, 50); 569 canvas.drawPath(evenOddPath, paint); 570 571 canvas.translate(300, 0); 572 canvas.drawPath(windingPath, paint); 573 574 canvas.translate(0, -50); 575 canvas.drawText('makeAsWinding path', 5, 20, paint, font); 576 577 evenOddPath.delete(); 578 windingPath.delete(); 579 }); 580}); 581