1describe('Path Behavior', () => { 2 let container; 3 4 beforeEach(async () => { 5 await EverythingLoaded; 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.drawPath(path, paint); 266 path.offset(80, 40); 267 canvas.drawPath(path, paint); 268 269 path.delete(); 270 paint.delete(); 271 }); 272 273 gm('oval_path', (canvas) => { 274 const paint = new CanvasKit.Paint(); 275 276 paint.setStyle(CanvasKit.PaintStyle.Stroke); 277 paint.setStrokeWidth(5.0); 278 paint.setAntiAlias(true); 279 paint.setColor(CanvasKit.BLACK); 280 281 const path = new CanvasKit.Path(); 282 path.moveTo(5, 5); 283 path.lineTo(10, 120); 284 path.addOval(CanvasKit.LTRBRect(10, 20, 100, 200), false, 3); 285 path.lineTo(300, 300); 286 287 canvas.drawPath(path, paint); 288 289 path.delete(); 290 paint.delete(); 291 }); 292 293 gm('bounds_path', (canvas) => { 294 const paint = new CanvasKit.Paint(); 295 296 paint.setStyle(CanvasKit.PaintStyle.Stroke); 297 paint.setStrokeWidth(5.0); 298 paint.setAntiAlias(true); 299 paint.setColor(CanvasKit.BLACK); 300 301 const path = new CanvasKit.Path(); 302 // Arbitrary points to make an interesting curve. 303 path.moveTo(97, 225); 304 path.cubicTo(20, 400, 404, 75, 243, 271); 305 306 canvas.drawPath(path, paint); 307 308 const bounds = new Float32Array(4); 309 path.getBounds(bounds); 310 311 paint.setColor(CanvasKit.BLUE); 312 paint.setStrokeWidth(3.0); 313 canvas.drawRect(bounds, paint); 314 315 path.computeTightBounds(bounds); 316 paint.setColor(CanvasKit.RED); 317 paint.setStrokeWidth(3.0); 318 canvas.drawRect(bounds, paint); 319 320 path.delete(); 321 paint.delete(); 322 }); 323 324 gm('arcto_path', (canvas) => { 325 const paint = new CanvasKit.Paint(); 326 327 paint.setStyle(CanvasKit.PaintStyle.Stroke); 328 paint.setStrokeWidth(5.0); 329 paint.setAntiAlias(true); 330 paint.setColor(CanvasKit.BLACK); 331 332 const path = new CanvasKit.Path(); 333 334 // - x1, y1, x2, y2, radius 335 path.arcToTangent(40, 0, 40, 40, 40); 336 // - oval (as Rect), startAngle, sweepAngle, forceMoveTo 337 path.arcToOval(CanvasKit.LTRBRect(90, 10, 120, 200), 30, 300, true); 338 // - rx, ry, xAxisRotate, useSmallArc, isCCW, x, y 339 path.moveTo(5, 105); 340 path.arcToRotated(24, 24, 45, true, false, 82, 156); 341 342 canvas.drawPath(path, paint); 343 344 path.delete(); 345 paint.delete(); 346 }); 347 348 gm('path_relative', (canvas) => { 349 const paint = new CanvasKit.Paint(); 350 paint.setStrokeWidth(1.0); 351 paint.setAntiAlias(true); 352 paint.setColor(CanvasKit.Color(0, 0, 0, 1.0)); 353 paint.setStyle(CanvasKit.PaintStyle.Stroke); 354 355 const path = new CanvasKit.Path(); 356 path.rMoveTo(20, 5) 357 .rLineTo(10, 15) // 30, 20 358 .rLineTo(10, -5); // 40, 10 359 path.rLineTo(10, 10); // 50, 20 360 path.rLineTo(10, -20); // 60, 0 361 path.rLineTo(-40, 5); // 20, 5 362 363 path.moveTo(20, 80) 364 .rCubicTo(70, -70, 140, 70, 170, -70); // 90, 10, 160, 150, 190, 10 365 366 path.moveTo(36, 148) 367 .rQuadTo(30, 40, 84, -12) // 66, 188, 120, 136 368 .lineTo(36, 148); 369 370 path.moveTo(150, 180) 371 .rArcTo(24, 24, 45, true, false, -68, -24); // 82, 156 372 path.lineTo(160, 160); 373 374 canvas.drawPath(path, paint); 375 376 path.delete(); 377 paint.delete(); 378 }); 379 380 it('can measure the contours of a path', () => { 381 const path = new CanvasKit.Path(); 382 path.moveTo(10, 10) 383 .lineTo(40, 50); // should be length 50 because of the 3/4/5 triangle rule 384 385 path.moveTo(80, 0) 386 .lineTo(80, 10) 387 .lineTo(100, 5) 388 .lineTo(80, 0); 389 390 const meas = new CanvasKit.ContourMeasureIter(path, false, 1); 391 let cont = meas.next(); 392 expect(cont).toBeTruthy(); 393 394 expect(cont.length()).toBeCloseTo(50.0, 3); 395 const pt = cont.getPosTan(28.7); // arbitrary point 396 expect(pt[0]).toBeCloseTo(27.22, 3); // x 397 expect(pt[1]).toBeCloseTo(32.96, 3); // y 398 expect(pt[2]).toBeCloseTo(0.6, 3); // dy 399 expect(pt[3]).toBeCloseTo(0.8, 3); // dy 400 401 pt.set([-1, -1, -1, -1]); // fill with sentinel values. 402 cont.getPosTan(28.7, pt); // arbitrary point again, passing in an array to copy into. 403 expect(pt[0]).toBeCloseTo(27.22, 3); // x 404 expect(pt[1]).toBeCloseTo(32.96, 3); // y 405 expect(pt[2]).toBeCloseTo(0.6, 3); // dy 406 expect(pt[3]).toBeCloseTo(0.8, 3); // dy 407 408 const subpath = cont.getSegment(20, 40, true); // make sure this doesn't crash 409 410 cont.delete(); 411 cont = meas.next(); 412 expect(cont).toBeTruthy(); 413 expect(cont.length()).toBeCloseTo(51.231, 3); 414 415 cont.delete(); 416 expect(meas.next()).toBeFalsy(); 417 418 meas.delete(); 419 path.delete(); 420 }); 421 422 gm('drawpoly_path', (canvas) => { 423 const paint = new CanvasKit.Paint(); 424 paint.setStrokeWidth(1.0); 425 paint.setAntiAlias(true); 426 paint.setColor(CanvasKit.Color(0, 0, 0, 1.0)); 427 paint.setStyle(CanvasKit.PaintStyle.Stroke); 428 429 const points = [5, 5, 30, 20, 55, 5, 55, 50, 30, 30, 5, 50]; 430 431 const pointsObj = CanvasKit.Malloc(Float32Array, 6 * 2); 432 const mPoints = pointsObj.toTypedArray(); 433 mPoints.set([105, 105, 130, 120, 155, 105, 155, 150, 130, 130, 105, 150]); 434 435 const path = new CanvasKit.Path(); 436 path.addPoly(points, true) 437 .moveTo(100, 0) 438 .addPoly(mPoints, true); 439 440 canvas.drawPath(path, paint); 441 CanvasKit.Free(pointsObj); 442 443 path.delete(); 444 paint.delete(); 445 }); 446 447 // Test trim, adding paths to paths, and a bunch of other path methods. 448 gm('trim_path', (canvas) => { 449 450 const paint = new CanvasKit.Paint(); 451 paint.setStrokeWidth(1.0); 452 paint.setAntiAlias(true); 453 paint.setColor(CanvasKit.Color(0, 0, 0, 1.0)); 454 paint.setStyle(CanvasKit.PaintStyle.Stroke); 455 456 const arcpath = new CanvasKit.Path(); 457 arcpath.arc(400, 400, 100, 0, -90, false) // x, y, radius, startAngle, endAngle, ccw 458 .dash(3, 1, 0) 459 .conicTo(10, 20, 30, 40, 5) 460 .rConicTo(60, 70, 80, 90, 5) 461 .trim(0.2, 1, false); 462 463 const path = new CanvasKit.Path(); 464 path.addArc(CanvasKit.LTRBRect(10, 20, 100, 200), 30, 300) 465 .addRect(CanvasKit.LTRBRect(200, 200, 300, 300)) // test single arg, default cw 466 .addRect(CanvasKit.LTRBRect(240, 240, 260, 260), true) // test two arg, true means ccw 467 .addRect([260, 260, 290, 290], true) // test five arg, true means ccw 468 .addRRect([300, 10, 500, 290, // Rect in LTRB order 469 60, 60, 60, 60, 60, 60, 60, 60], // all radii are the same 470 false) // ccw 471 .addRRect(CanvasKit.RRectXY([350, 60, 450, 240], 20, 80), true) // Rect, rx, ry, ccw 472 .addPath(arcpath) 473 .transform(0.54, -0.84, 390.35, 474 0.84, 0.54, -114.53, 475 0, 0, 1); 476 477 canvas.drawPath(path, paint); 478 479 path.delete(); 480 paint.delete(); 481 }); 482 483 gm('winding_example', (canvas) => { 484 // Inspired by https://fiddle.skia.org/c/@Path_FillType_a 485 const path = new CanvasKit.Path(); 486 // Draw overlapping rects on top 487 path.addRect(CanvasKit.LTRBRect(10, 10, 30, 30), false); 488 path.addRect(CanvasKit.LTRBRect(20, 20, 40, 40), false); 489 // Draw overlapping rects on bottom, with different direction lines. 490 path.addRect(CanvasKit.LTRBRect(10, 60, 30, 80), false); 491 path.addRect(CanvasKit.LTRBRect(20, 70, 40, 90), true); 492 493 expect(path.getFillType()).toEqual(CanvasKit.FillType.Winding); 494 495 // Draw the two rectangles on the left side. 496 const paint = new CanvasKit.Paint(); 497 paint.setStyle(CanvasKit.PaintStyle.Stroke); 498 canvas.drawPath(path, paint); 499 500 const clipRect = CanvasKit.LTRBRect(0, 0, 51, 100); 501 paint.setStyle(CanvasKit.PaintStyle.Fill); 502 503 for (const fillType of [CanvasKit.FillType.Winding, CanvasKit.FillType.EvenOdd]) { 504 canvas.translate(51, 0); 505 canvas.save(); 506 canvas.clipRect(clipRect, CanvasKit.ClipOp.Intersect, false); 507 path.setFillType(fillType); 508 canvas.drawPath(path, paint); 509 canvas.restore(); 510 } 511 512 path.delete(); 513 paint.delete(); 514 }); 515 516 gm('as_winding', (canvas) => { 517 const evenOddPath = new CanvasKit.Path(); 518 // Draw overlapping rects 519 evenOddPath.addRect(CanvasKit.LTRBRect(10, 10, 70, 70), false); 520 evenOddPath.addRect(CanvasKit.LTRBRect(30, 30, 50, 50), false); 521 evenOddPath.setFillType(CanvasKit.FillType.EvenOdd); 522 523 const evenOddCmds = evenOddPath.toCmds(); 524 expect(evenOddCmds).toEqual(Float32Array.of( 525 CanvasKit.MOVE_VERB, 10, 10, 526 CanvasKit.LINE_VERB, 70, 10, 527 CanvasKit.LINE_VERB, 70, 70, 528 CanvasKit.LINE_VERB, 10, 70, 529 CanvasKit.CLOSE_VERB, 530 CanvasKit.MOVE_VERB, 30, 30, // This contour is drawn 531 CanvasKit.LINE_VERB, 50, 30, // clockwise, as specified. 532 CanvasKit.LINE_VERB, 50, 50, 533 CanvasKit.LINE_VERB, 30, 50, 534 CanvasKit.CLOSE_VERB 535 )); 536 537 const windingPath = evenOddPath.makeAsWinding(); 538 539 expect(windingPath.getFillType()).toBe(CanvasKit.FillType.Winding); 540 const windingCmds = windingPath.toCmds(); 541 expect(windingCmds).toEqual(Float32Array.of( 542 CanvasKit.MOVE_VERB, 10, 10, 543 CanvasKit.LINE_VERB, 70, 10, 544 CanvasKit.LINE_VERB, 70, 70, 545 CanvasKit.LINE_VERB, 10, 70, 546 CanvasKit.CLOSE_VERB, 547 CanvasKit.MOVE_VERB, 30, 50, // This contour has been 548 CanvasKit.LINE_VERB, 50, 50, // re-drawn counter-clockwise 549 CanvasKit.LINE_VERB, 50, 30, // so that it covers the same 550 CanvasKit.LINE_VERB, 30, 30, // area, but with the winding fill type. 551 CanvasKit.CLOSE_VERB 552 )); 553 554 const paint = new CanvasKit.Paint(); 555 paint.setStyle(CanvasKit.PaintStyle.Fill); 556 const font = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 20); 557 558 canvas.drawText('Original path (even odd)', 5, 20, paint, font); 559 canvas.translate(0, 50); 560 canvas.drawPath(evenOddPath, paint); 561 562 canvas.translate(300, 0); 563 canvas.drawPath(windingPath, paint); 564 565 canvas.translate(0, -50); 566 canvas.drawText('makeAsWinding path', 5, 20, paint, font); 567 568 evenOddPath.delete(); 569 windingPath.delete(); 570 }); 571}); 572