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