• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 <!DOCTYPE html>
2 <title>PathKit (Skia's Geometry + WASM)</title>
3 <meta charset="utf-8" />
4 <meta http-equiv="X-UA-Compatible" content="IE=edge">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 
7 <style>
8   svg, canvas {
9     border: 1px dashed #AAA;
10   }
11 
12   canvas {
13     width: 200px;
14     height: 200px;
15   }
16 
17   canvas.big {
18     width: 300px;
19     height: 300px;
20   }
21 
22 </style>
23 
24 <h2> Can output to an SVG Path, a Canvas, or a Path2D object </h2>
25 <svg id=svg1 xmlns='http://www.w3.org/2000/svg' width=200 height=200></svg>
26 <canvas id=canvas1></canvas>
27 <canvas id=canvas2></canvas>
28 
29 <h2> Interact with NewPath() just like a Path2D Object </h2>
30 <canvas class=big id=canvas3></canvas>
31 <canvas class=big id=canvas4></canvas>
32 
33 <h2> Has various Path Effects </h2>
34 <canvas class=big id=canvas5></canvas>
35 <canvas class=big id=canvas6></canvas>
36 <canvas class=big id=canvas7></canvas>
37 <canvas class=big id=canvas8></canvas>
38 <canvas class=big id=canvas9></canvas>
39 <canvas class=big id=canvas10></canvas>
40 <canvas class=big id=canvas11></canvas>
41 <canvas class=big id=canvasTransform></canvas>
42 
43 <h2> Supports fill-rules of nonzero and evenodd </h2>
44 <svg id=svg2 xmlns='http://www.w3.org/2000/svg' width=200 height=200></svg>
45 <svg id=svg3 xmlns='http://www.w3.org/2000/svg' width=200 height=200></svg>
46 
47 <h2> Solves Cubics for Y given X </h2>
48 <canvas class=big id=cubics></canvas>
49 
50 <script type="text/javascript" src="/node_modules/pathkit-wasm/bin/pathkit.js"></script>
51 
52 <script type="text/javascript" charset="utf-8">
53 
54   PathKitInit({
55     locateFile: (file) => '/node_modules/pathkit-wasm/bin/'+file,
56   }).then((PathKit) => {
57     window.PathKit = PathKit;
58     OutputsExample(PathKit);
59     Path2DExample(PathKit);
60     PathEffectsExample(PathKit);
61     MatrixTransformExample(PathKit);
62     FilledSVGExample(PathKit);
63     CubicSolverExample(PathKit);
64   });
65 
66   function setCanvasSize(ctx, width, height) {
67     ctx.canvas.width = width;
68     ctx.canvas.height = height;
69   }
70 
71   function OutputsExample(PathKit) {
72     let firstPath = PathKit.FromSVGString('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');
73 
74     let secondPath = PathKit.NewPath();
75     // Acts somewhat like the Canvas API, except can be chained
76     secondPath.moveTo(1, 1)
77               .lineTo(20, 1)
78               .lineTo(10, 30)
79               .closePath();
80 
81     // Join the two paths together (mutating firstPath in the process)
82     firstPath.op(secondPath, PathKit.PathOp.INTERSECT);
83 
84     let simpleStr = firstPath.toSVGString();
85 
86     let newSVG = document.createElementNS('http://www.w3.org/2000/svg', 'path');
87     newSVG.setAttribute('stroke', 'rgb(0,0,200)');
88     newSVG.setAttribute('fill', 'white');
89     newSVG.setAttribute('transform', 'scale(8,8)');
90     newSVG.setAttribute('d', simpleStr);
91     document.getElementById('svg1').appendChild(newSVG);
92 
93     // Draw directly to Canvas
94     let ctx = document.getElementById('canvas1').getContext('2d');
95     setCanvasSize(ctx, 200, 200);
96     ctx.strokeStyle = 'green';
97     ctx.fillStyle = 'white';
98     ctx.scale(8, 8);
99     ctx.beginPath();
100     firstPath.toCanvas(ctx);
101     ctx.stroke();
102 
103     // create Path2D object and use it in a Canvas.
104     let path2D = firstPath.toPath2D();
105     ctx = document.getElementById('canvas2').getContext('2d');
106     setCanvasSize(ctx, 200, 200);
107     ctx.canvas.width = 200
108     ctx.scale(8, 8);
109     ctx.fillStyle = 'purple';
110     ctx.strokeStyle = 'orange';
111     ctx.fill(path2D);
112     ctx.stroke(path2D);
113 
114     // clean up WASM memory
115     // See http://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/embind.html?highlight=memory#memory-management
116     firstPath.delete();
117     secondPath.delete();
118   }
119 
120   function Path2DExample(PathKit) {
121     let objs = [new Path2D(), PathKit.NewPath(), new Path2D(), PathKit.NewPath()];
122     let canvases = [
123       document.getElementById('canvas3').getContext('2d'),
124       document.getElementById('canvas4').getContext('2d')
125     ];
126 
127     for (i = 0; i <= 1; i++) {
128       let path = objs[i];
129 
130       path.moveTo(20, 5);
131       path.lineTo(30, 20);
132       path.lineTo(40, 10);
133       path.lineTo(50, 20);
134       path.lineTo(60, 0);
135       path.lineTo(20, 5);
136 
137       path.moveTo(20, 80);
138       path.bezierCurveTo(90, 10, 160, 150, 190, 10);
139 
140       path.moveTo(36, 148);
141       path.quadraticCurveTo(66, 188, 120, 136);
142       path.lineTo(36, 148);
143 
144       path.rect(5, 170, 20, 20);
145 
146       path.moveTo(150, 180);
147       path.arcTo(150, 100, 50, 200, 20);
148       path.lineTo(160, 160);
149 
150       path.moveTo(20, 120);
151       path.arc(20, 120, 18, 0, 1.75 * Math.PI);
152       path.lineTo(20, 120);
153 
154       let secondPath = objs[i+2];
155       secondPath.ellipse(130, 25, 30, 10, -1*Math.PI/8, Math.PI/6, 1.5*Math.PI, false);
156 
157       path.addPath(secondPath);
158 
159       let m = document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGMatrix();
160       m.a = 1; m.b = 0;
161       m.c = 0; m.d = 1;
162       m.e = 0; m.f = 20.5;
163 
164       path.addPath(secondPath, m);
165       // With PathKit, one can also just provide the 6 params as floats, to avoid
166       // the overhead of making an SVGMatrix
167       // path.addPath(secondPath, 1, 0, 0, 1, 0, 20.5);
168 
169       canvasCtx = canvases[i];
170       canvasCtx.fillStyle = 'blue';
171       setCanvasSize(canvasCtx, 300, 300);
172       canvasCtx.scale(1.5, 1.5);
173       if (path.toPath2D) {
174         canvasCtx.stroke(path.toPath2D());
175       } else {
176         canvasCtx.stroke(path);
177       }
178     }
179 
180 
181     objs[1].delete();
182   }
183 
184   // see https://fiddle.skia.org/c/@discrete_path
185   function drawStar(path) {
186     let R = 115.2, C = 128.0;
187     path.moveTo(C + R + 22, C);
188     for (let i = 1; i < 8; i++) {
189       let a = 2.6927937 * i;
190       path.lineTo(C + R * Math.cos(a) + 22, C + R * Math.sin(a));
191     }
192     path.closePath();
193     return path;
194   }
195 
196   function PathEffectsExample(PathKit) {
197     let effects = [
198       // no-op
199       (path) => path,
200       // dash
201       (path, counter) => path.dash(10, 3, counter/5),
202       // trim (takes optional 3rd param for returning the trimmed part
203       // or the complement)
204       (path, counter) => path.trim((counter/100) % 1, 0.8, false),
205       // simplify
206       (path) => path.simplify(),
207       // stroke
208       (path, counter) => path.stroke({
209         width: 10 * (Math.sin(counter/30) + 1),
210         join: PathKit.StrokeJoin.BEVEL,
211         cap: PathKit.StrokeCap.BUTT,
212         miter_limit: 1,
213       }),
214       // "offset effect", that is, making a border around the shape.
215       (path, counter) => {
216         let orig = path.copy();
217         path.stroke({
218           width: 10 + (counter / 4) % 50,
219           join: PathKit.StrokeJoin.ROUND,
220           cap: PathKit.StrokeCap.SQUARE,
221         })
222           .op(orig, PathKit.PathOp.DIFFERENCE);
223         orig.delete();
224       },
225       (path, counter) => {
226         let simplified = path.simplify().copy();
227         path.stroke({
228           width: 2 + (counter / 2) % 100,
229           join: PathKit.StrokeJoin.BEVEL,
230           cap: PathKit.StrokeCap.BUTT,
231         })
232           .op(simplified, PathKit.PathOp.REVERSE_DIFFERENCE);
233         simplified.delete();
234       }
235     ];
236 
237     let names = ["(plain)", "Dash", "Trim", "Simplify", "Stroke", "Grow", "Shrink"];
238 
239     let counter = 0;
240     function frame() {
241       counter++;
242       for (let i = 0; i < effects.length; i++) {
243         let path = PathKit.NewPath();
244         drawStar(path);
245 
246         // The transforms apply directly to the path.
247         effects[i](path, counter);
248 
249         let ctx = document.getElementById(`canvas${i+5}`).getContext('2d');
250         setCanvasSize(ctx, 300, 300);
251         ctx.strokeStyle = '#3c597a';
252         ctx.fillStyle = '#3c597a';
253         if (i >=4 ) {
254           ctx.fill(path.toPath2D(), path.getFillTypeString());
255         } else {
256           ctx.stroke(path.toPath2D());
257         }
258 
259         ctx.font = '42px monospace';
260 
261         let x = 150-ctx.measureText(names[i]).width/2;
262         ctx.strokeText(names[i], x, 290);
263 
264         path.delete();
265       }
266       window.requestAnimationFrame(frame);
267     }
268     window.requestAnimationFrame(frame);
269   }
270 
271   function MatrixTransformExample(PathKit) {
272     // Creates an animated star that twists and moves.
273     let ctx = document.getElementById('canvasTransform').getContext('2d');
274     setCanvasSize(ctx, 300, 300);
275     ctx.strokeStyle = '#3c597a';
276 
277     let path = drawStar(PathKit.NewPath());
278     // TODO(kjlubick): Perhaps expose some matrix helper functions to allow
279     // clients to build their own matrices like this?
280     // These matrices represent a 2 degree rotation and a 1% scale factor.
281     let scaleUp = [1.0094, -0.0352,  3.1041,
282                    0.0352,  1.0094, -6.4885,
283                    0     ,  0      , 1];
284 
285     let scaleDown = [ 0.9895, 0.0346, -2.8473,
286                      -0.0346, 0.9895,  6.5276,
287                       0     , 0     ,  1];
288 
289     let i = 0;
290     function frame(){
291       i++;
292       if (Math.round(i/100) % 2) {
293         path.transform(scaleDown);
294       } else {
295         path.transform(scaleUp);
296       }
297 
298       ctx.clearRect(0, 0, 300, 300);
299       ctx.stroke(path.toPath2D());
300 
301       ctx.font = '42px monospace';
302       let x = 150-ctx.measureText('Transform').width/2;
303       ctx.strokeText('Transform', x, 290);
304 
305       window.requestAnimationFrame(frame);
306     }
307     window.requestAnimationFrame(frame);
308   }
309 
310   function FilledSVGExample(PathKit) {
311     let innerRect = PathKit.NewPath();
312     innerRect.rect(80, 100, 40, 40);
313 
314     let outerRect = PathKit.NewPath();
315     outerRect.rect(50, 10, 100, 100)
316              .op(innerRect, PathKit.PathOp.XOR);
317 
318     let str = outerRect.toSVGString();
319 
320     let diffSVG = document.createElementNS('http://www.w3.org/2000/svg', 'path');
321     diffSVG.setAttribute('stroke', 'red');
322     diffSVG.setAttribute('fill', 'black');
323     // force fill-rule to nonzero to demonstrate difference
324     diffSVG.setAttribute('fill-rule', 'nonzero');
325     diffSVG.setAttribute('d', str);
326     document.getElementById('svg2').appendChild(diffSVG);
327 
328     let unionSVG = document.createElementNS('http://www.w3.org/2000/svg', 'path');
329     unionSVG.setAttribute('stroke', 'red');
330     unionSVG.setAttribute('fill', 'black');
331     // ask what the path thinks fill-rule should be ('evenodd')
332     // SVG and Canvas both use the same keys ('nonzero' and 'evenodd') and both
333     // default to 'nonzero', so one call supports both.
334     unionSVG.setAttribute('fill-rule', outerRect.getFillTypeString());
335     unionSVG.setAttribute('d', str);
336     document.getElementById('svg3').appendChild(unionSVG);
337 
338     outerRect.delete();
339     innerRect.delete();
340   }
341 
342   function CubicSolverExample(PathKit) {
343     let ctx = document.getElementById('cubics').getContext('2d');
344     setCanvasSize(ctx, 300, 300);
345     // Draw lines between cp0 (0, 0) and cp1 and then cp2 and cp3 (1, 1)
346     // scaled up to be on a 300x300 grid instead of unit square
347     ctx.strokeStyle = 'black';
348     ctx.beginPath();
349     ctx.moveTo(0, 0);
350     ctx.lineTo(0.1 * 300, 0.5*300);
351 
352     ctx.moveTo(0.5 * 300, 0.1*300);
353     ctx.lineTo(300, 300);
354     ctx.stroke();
355 
356 
357     ctx.strokeStyle = 'green';
358     ctx.beginPath();
359 
360     for (let x = 0; x < 300; x++) {
361       // scale X into unit square
362       let y = PathKit.cubicYFromX(0.1, 0.5, 0.5, 0.1, x/300);
363       ctx.arc(x, y*300, 2, 0, 2*Math.PI);
364     }
365     ctx.stroke();
366   }
367 
368 </script>
369