• 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  }).ready().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