• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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