Home | History | Annotate | Download | only in npm-wasm
      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