[PathKit] Write more complete docs and clean up API to be consistent
Breaking Changes (should be minor, as it's mostly just things
for testing):
- PathKit.ApplyPathOp should have returned a new SkPath, but didn't.
It now does and is named "MakeFromOp", which makes the convention of
"Have 'make' in name, needs delete" more consistent.
- PathKit.FromCmds(arr) now only needs to take the JS Array and
will handle the TypedArrays under the hood. If clients want to deal
with TypedArrays themselves, they can use _FromCmds(ptr, len) directly.
- PathKit.MakeLTRBRect is now just PathKit.LTRBRect. The thing
returned is a normal JS Object and doesn't need delete().
As per custom with v0 apps, we are updating the minor version
to v0.3.0 to account for breaking changes.
Docs-Preview: https://skia.org/?cl=147960
Bug: skia:8216
Change-Id: Ia3626e69f3e97698fc62a6aee876af005e29ffca
Reviewed-on: https://skia-review.googlesource.com/147960
Reviewed-by: Mike Reed <reed@google.com>
Reviewed-by: Heather Miller <hcm@google.com>
diff --git a/experimental/pathkit/Makefile b/experimental/pathkit/Makefile
index f0e7f45..0547877 100644
--- a/experimental/pathkit/Makefile
+++ b/experimental/pathkit/Makefile
@@ -13,7 +13,7 @@
publish:
cd npm-wasm; npm publish
- cd ../npm-asmjs; npm publish
+ cd npm-asmjs; npm publish
update-major:
cd npm-wasm; npm version major
@@ -22,12 +22,12 @@
update-minor:
cd npm-wasm; npm version minor
- cd ../npm-asmjs; npm version minor
+ cd npm-asmjs; npm version minor
echo "Don't forget to publish."
update-patch:
cd npm-wasm; npm version patch
- cd ../npm-asmjs; npm version patch
+ cd npm-asmjs; npm version patch
echo "Don't forget to publish."
# Build the library and run the tests. If developing locally, test-continuous is better
diff --git a/experimental/pathkit/chaining.js b/experimental/pathkit/chaining.js
index d4902c6..f901d38 100644
--- a/experimental/pathkit/chaining.js
+++ b/experimental/pathkit/chaining.js
@@ -115,13 +115,13 @@
return null;
};
- PathKit.SkPath.prototype.quadraticCurveTo = function(x1, y1, x2, y2) {
- this._quadTo(x1, y1, x2, y2);
+ PathKit.SkPath.prototype.quadraticCurveTo = function(cpx, cpy, x, y) {
+ this._quadTo(cpx, cpy, x, y);
return this;
};
- PathKit.SkPath.prototype.quadTo = function(x1, y1, x2, y2) {
- this._quadTo(x1, y1, x2, y2);
+ PathKit.SkPath.prototype.quadTo = function(cpx, cpy, x, y) {
+ this._quadTo(cpx, cpy, x, y);
return this;
};
diff --git a/experimental/pathkit/externs.js b/experimental/pathkit/externs.js
index b68a3a2..9116254 100644
--- a/experimental/pathkit/externs.js
+++ b/experimental/pathkit/externs.js
@@ -24,6 +24,9 @@
SkBits2FloatUnsigned: function(num) {},
_malloc: function(size) {},
onRuntimeInitialized: function() {},
+ _FromCmds: function(ptr, size) {},
+ loadCmdsTypedArray: function(arr) {},
+ FromCmds: function(arr) {},
HEAPF32: {},
@@ -40,7 +43,7 @@
_lineTo: function(x1, y1) {},
_moveTo: function(x1, y1) {},
_op: function(otherPath, op) {},
- _quadTo: function(x1, y1, x2, y2) {},
+ _quadTo: function(cpx, cpy, x, y) {},
_rect: function(x, y, w, h) {},
_simplify: function() {},
_stroke: function(opts) {},
diff --git a/experimental/pathkit/helper.js b/experimental/pathkit/helper.js
index 0d476ab..4f84782 100644
--- a/experimental/pathkit/helper.js
+++ b/experimental/pathkit/helper.js
@@ -7,9 +7,9 @@
var Float32ArrayCache = {};
// Takes a 2D array of commands and puts them into the WASM heap
- // as a 1D array. This allowing them to referenced from the C++ code.
+ // as a 1D array. This allows them to referenced from the C++ code.
// Returns a 2 element array, with the first item being essentially a
- // pointer to the array and the second item being the lengh of
+ // pointer to the array and the second item being the length of
// the new 1D array.
//
// Example usage:
@@ -26,7 +26,7 @@
//
// If arguments at index 1... in each cmd row are strings, they will be
// parsed as hex, and then converted to floats using SkBits2FloatUnsigned
- PathKit['loadCmdsTypedArray'] = function(arr) {
+ PathKit.loadCmdsTypedArray = function(arr) {
var len = 0;
for (var r = 0; r < arr.length; r++) {
len += arr[r].length;
@@ -57,5 +57,13 @@
PathKit.HEAPF32.set(ta, ptr / ta.BYTES_PER_ELEMENT);
return [ptr, len];
}
+
+ // Experimentation has shown that using TypedArrays to pass arrays from
+ // JS to C++ is faster than passing the JS Arrays across.
+ // See above for example of cmds.
+ PathKit.FromCmds = function(cmds) {
+ var ptrLen = PathKit.loadCmdsTypedArray(cmds);
+ return PathKit._FromCmds(ptrLen[0], ptrLen[1]);
+ }
}(Module)); // When this file is loaded in, the high level object is "Module";
diff --git a/experimental/pathkit/npm-asmjs/example.html b/experimental/pathkit/npm-asmjs/example.html
index 3479b83..e5c85e7 100644
--- a/experimental/pathkit/npm-asmjs/example.html
+++ b/experimental/pathkit/npm-asmjs/example.html
@@ -37,6 +37,7 @@
<canvas class=big id=canvas8></canvas>
<canvas class=big id=canvas9></canvas>
<canvas class=big id=canvas10></canvas>
+<canvas class=big id=canvas11></canvas>
<canvas class=big id=canvasTransform></canvas>
<h2> Supports fill-rules of nonzero and evenodd </h2>
@@ -193,58 +194,74 @@
// no-op
(path) => path,
// dash
- (path) => path.dash(10, 3, 0),
+ (path, counter) => path.dash(10, 3, counter/5),
// trim (takes optional 3rd param for returning the trimmed part
// or the complement)
- (path) => path.trim(0.25, 0.8, false),
+ (path, counter) => path.trim((counter/100) % 1, 0.8, false),
// simplify
(path) => path.simplify(),
// stroke
- (path) => path.stroke({
- width: 15,
+ (path, counter) => path.stroke({
+ width: 10 * (Math.sin(counter/30) + 1),
join: PathKit.StrokeJoin.BEVEL,
cap: PathKit.StrokeCap.BUTT,
miter_limit: 1,
}),
// "offset effect", that is, making a border around the shape.
- (path) => {
+ (path, counter) => {
let orig = path.copy();
path.stroke({
- width: 10,
+ width: 10 + (counter / 4) % 50,
join: PathKit.StrokeJoin.ROUND,
cap: PathKit.StrokeCap.SQUARE,
})
.op(orig, PathKit.PathOp.DIFFERENCE);
orig.delete();
+ },
+ (path, counter) => {
+ let simplified = path.simplify().copy();
+ path.stroke({
+ width: 2 + (counter / 2) % 100,
+ join: PathKit.StrokeJoin.BEVEL,
+ cap: PathKit.StrokeCap.BUTT,
+ })
+ .op(simplified, PathKit.PathOp.REVERSE_DIFFERENCE);
+ simplified.delete();
}
];
- let names = ["(plain)", "Dash", "Trim", "Simplify", "Stroke", "Offset"];
+ let names = ["(plain)", "Dash", "Trim", "Simplify", "Stroke", "Grow", "Shrink"];
- for (let i = 0; i < effects.length; i++) {
- let path = PathKit.NewPath();
- drawStar(path);
+ let counter = 0;
+ function frame() {
+ counter++;
+ for (let i = 0; i < effects.length; i++) {
+ let path = PathKit.NewPath();
+ drawStar(path);
- // The transforms apply directly to the path.
- effects[i](path);
+ // The transforms apply directly to the path.
+ effects[i](path, counter);
- let ctx = document.getElementById(`canvas${i+5}`).getContext('2d');
- setCanvasSize(ctx, 300, 300);
- ctx.strokeStyle = '#3c597a';
- ctx.fillStyle = '#3c597a';
- if (i === 4 || i === 5) {
- ctx.fill(path.toPath2D(), path.getFillTypeString());
- } else {
- ctx.stroke(path.toPath2D());
+ let ctx = document.getElementById(`canvas${i+5}`).getContext('2d');
+ setCanvasSize(ctx, 300, 300);
+ ctx.strokeStyle = '#3c597a';
+ ctx.fillStyle = '#3c597a';
+ if (i >=4 ) {
+ ctx.fill(path.toPath2D(), path.getFillTypeString());
+ } else {
+ ctx.stroke(path.toPath2D());
+ }
+
+ ctx.font = '42px monospace';
+
+ let x = 150-ctx.measureText(names[i]).width/2;
+ ctx.strokeText(names[i], x, 290);
+
+ path.delete();
}
-
- ctx.font = '42px monospace';
-
- let x = 150-ctx.measureText(names[i]).width/2;
- ctx.strokeText(names[i], x, 290);
-
- path.delete();
+ window.requestAnimationFrame(frame);
}
+ window.requestAnimationFrame(frame);
}
function MatrixTransformExample(PathKit) {
diff --git a/experimental/pathkit/npm-asmjs/package.json b/experimental/pathkit/npm-asmjs/package.json
index b938696..792d35a 100644
--- a/experimental/pathkit/npm-asmjs/package.json
+++ b/experimental/pathkit/npm-asmjs/package.json
@@ -1,6 +1,6 @@
{
"name": "experimental-pathkit-asmjs",
- "version": "0.2.0",
+ "version": "0.3.0",
"description": "A asm.js version of Skia's PathOps toolkit",
"main": "bin/pathkit.js",
"homepage": "https://github.com/google/skia/tree/master/experimental/pathkit",
diff --git a/experimental/pathkit/npm-wasm/example.html b/experimental/pathkit/npm-wasm/example.html
index 60d6981..a6c8cca 100644
--- a/experimental/pathkit/npm-wasm/example.html
+++ b/experimental/pathkit/npm-wasm/example.html
@@ -37,6 +37,7 @@
<canvas class=big id=canvas8></canvas>
<canvas class=big id=canvas9></canvas>
<canvas class=big id=canvas10></canvas>
+<canvas class=big id=canvas11></canvas>
<canvas class=big id=canvasTransform></canvas>
<h2> Supports fill-rules of nonzero and evenodd </h2>
@@ -193,58 +194,74 @@
// no-op
(path) => path,
// dash
- (path) => path.dash(10, 3, 0),
+ (path, counter) => path.dash(10, 3, counter/5),
// trim (takes optional 3rd param for returning the trimmed part
// or the complement)
- (path) => path.trim(0.25, 0.8, false),
+ (path, counter) => path.trim((counter/100) % 1, 0.8, false),
// simplify
(path) => path.simplify(),
// stroke
- (path) => path.stroke({
- width: 15,
+ (path, counter) => path.stroke({
+ width: 10 * (Math.sin(counter/30) + 1),
join: PathKit.StrokeJoin.BEVEL,
cap: PathKit.StrokeCap.BUTT,
miter_limit: 1,
}),
// "offset effect", that is, making a border around the shape.
- (path) => {
+ (path, counter) => {
let orig = path.copy();
path.stroke({
- width: 10,
+ width: 10 + (counter / 4) % 50,
join: PathKit.StrokeJoin.ROUND,
cap: PathKit.StrokeCap.SQUARE,
})
.op(orig, PathKit.PathOp.DIFFERENCE);
orig.delete();
+ },
+ (path, counter) => {
+ let simplified = path.simplify().copy();
+ path.stroke({
+ width: 2 + (counter / 2) % 100,
+ join: PathKit.StrokeJoin.BEVEL,
+ cap: PathKit.StrokeCap.BUTT,
+ })
+ .op(simplified, PathKit.PathOp.REVERSE_DIFFERENCE);
+ simplified.delete();
}
];
- let names = ["(plain)", "Dash", "Trim", "Simplify", "Stroke", "Offset"];
+ let names = ["(plain)", "Dash", "Trim", "Simplify", "Stroke", "Grow", "Shrink"];
- for (let i = 0; i < effects.length; i++) {
- let path = PathKit.NewPath();
- drawStar(path);
+ let counter = 0;
+ function frame() {
+ counter++;
+ for (let i = 0; i < effects.length; i++) {
+ let path = PathKit.NewPath();
+ drawStar(path);
- // The transforms apply directly to the path.
- effects[i](path);
+ // The transforms apply directly to the path.
+ effects[i](path, counter);
- let ctx = document.getElementById(`canvas${i+5}`).getContext('2d');
- setCanvasSize(ctx, 300, 300);
- ctx.strokeStyle = '#3c597a';
- ctx.fillStyle = '#3c597a';
- if (i === 4 || i === 5) {
- ctx.fill(path.toPath2D(), path.getFillTypeString());
- } else {
- ctx.stroke(path.toPath2D());
+ let ctx = document.getElementById(`canvas${i+5}`).getContext('2d');
+ setCanvasSize(ctx, 300, 300);
+ ctx.strokeStyle = '#3c597a';
+ ctx.fillStyle = '#3c597a';
+ if (i >=4 ) {
+ ctx.fill(path.toPath2D(), path.getFillTypeString());
+ } else {
+ ctx.stroke(path.toPath2D());
+ }
+
+ ctx.font = '42px monospace';
+
+ let x = 150-ctx.measureText(names[i]).width/2;
+ ctx.strokeText(names[i], x, 290);
+
+ path.delete();
}
-
- ctx.font = '42px monospace';
-
- let x = 150-ctx.measureText(names[i]).width/2;
- ctx.strokeText(names[i], x, 290);
-
- path.delete();
+ window.requestAnimationFrame(frame);
}
+ window.requestAnimationFrame(frame);
}
function MatrixTransformExample(PathKit) {
diff --git a/experimental/pathkit/npm-wasm/package.json b/experimental/pathkit/npm-wasm/package.json
index c951625..f83ab63 100644
--- a/experimental/pathkit/npm-wasm/package.json
+++ b/experimental/pathkit/npm-wasm/package.json
@@ -1,6 +1,6 @@
{
"name": "experimental-pathkit-wasm",
- "version": "0.2.0",
+ "version": "0.3.0",
"description": "A WASM version of Skia's PathOps toolkit",
"main": "bin/pathkit.js",
"homepage": "https://github.com/google/skia/tree/master/experimental/pathkit",
diff --git a/experimental/pathkit/pathkit_wasm_bindings.cpp b/experimental/pathkit/pathkit_wasm_bindings.cpp
index c680db1..40772d8 100644
--- a/experimental/pathkit/pathkit_wasm_bindings.cpp
+++ b/experimental/pathkit/pathkit_wasm_bindings.cpp
@@ -134,7 +134,7 @@
CHECK_NUM_ARGS(5);
x1 = cmds[i++], y1 = cmds[i++];
x2 = cmds[i++], y2 = cmds[i++];
- x3 = cmds[i++]; // width
+ x3 = cmds[i++]; // weight
path.conicTo(x1, y1, x2, y2, x3);
break;
case CUBIC:
@@ -249,6 +249,14 @@
return Op(pathOne, pathTwo, op, &pathOne);
}
+SkPathOrNull EMSCRIPTEN_KEEPALIVE MakeFromOp(const SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
+ SkPath out;
+ if (Op(pathOne, pathTwo, op, &out)) {
+ return emscripten::val(out);
+ }
+ return emscripten::val::null();
+}
+
SkPathOrNull EMSCRIPTEN_KEEPALIVE ResolveBuilder(SkOpBuilder& builder) {
SkPath path;
if (builder.resolve(&path)) {
@@ -481,9 +489,9 @@
.function("_arcTo", &ApplyArcTo)
//"bezierCurveTo" alias handled in JS bindings
.function("_close", &ApplyClose)
+ //"closePath" alias handled in JS bindings
.function("_conicTo", &ApplyConicTo)
.function("_cubicTo", &ApplyCubicTo)
- //"closePath" alias handled in JS bindings
.function("_ellipse", &ApplyEllipse)
.function("_lineTo", &ApplyLineTo)
@@ -530,6 +538,7 @@
.constructor<>()
.function("add", &SkOpBuilder::add)
+ .function("make", &ResolveBuilder)
.function("resolve", &ResolveBuilder);
// Without these function() bindings, the function would be exposed but oblivious to
@@ -537,13 +546,14 @@
// Import
function("FromSVGString", &FromSVGString);
- function("FromCmds", &FromCmds);
function("NewPath", &NewPath);
function("NewPath", &CopyPath);
+ // FromCmds is defined in helper.js to make use of TypedArrays transparent.
+ function("_FromCmds", &FromCmds);
// Path2D is opaque, so we can't read in from it.
// PathOps
- function("ApplyPathOp", &ApplyPathOp);
+ function("MakeFromOp", &MakeFromOp);
enum_<SkPathOp>("PathOp")
.value("DIFFERENCE", SkPathOp::kDifference_SkPathOp)
@@ -574,7 +584,7 @@
.field("fRight", &SkRect::fRight)
.field("fBottom", &SkRect::fBottom);
- function("MakeLTRBRect", &SkRect::MakeLTRB);
+ function("LTRBRect", &SkRect::MakeLTRB);
// Stroke
enum_<SkPaint::Join>("StrokeJoin")
diff --git a/experimental/pathkit/tests/path.spec.js b/experimental/pathkit/tests/path.spec.js
index e77be63..852435b 100644
--- a/experimental/pathkit/tests/path.spec.js
+++ b/experimental/pathkit/tests/path.spec.js
@@ -112,13 +112,13 @@
LoadPathKit.then(() => {
// Based on test_bounds_crbug_513799
let path = PathKit.NewPath();
- expect(path.getBounds()).toEqual(PathKit.MakeLTRBRect(0, 0, 0, 0));
+ expect(path.getBounds()).toEqual(PathKit.LTRBRect(0, 0, 0, 0));
path.moveTo(-5, -8);
- expect(path.getBounds()).toEqual(PathKit.MakeLTRBRect(-5, -8, -5, -8));
+ expect(path.getBounds()).toEqual(PathKit.LTRBRect(-5, -8, -5, -8));
path.rect(1, 2, 2, 2);
- expect(path.getBounds()).toEqual(PathKit.MakeLTRBRect(-5, -8, 3, 4));
+ expect(path.getBounds()).toEqual(PathKit.LTRBRect(-5, -8, 3, 4));
path.moveTo(1, 2);
- expect(path.getBounds()).toEqual(PathKit.MakeLTRBRect(-5, -8, 3, 4));
+ expect(path.getBounds()).toEqual(PathKit.LTRBRect(-5, -8, 3, 4));
path.delete();
done();
});
@@ -130,9 +130,9 @@
let path = PathKit.NewPath();
path.moveTo(1, 1);
path.quadraticCurveTo(4, 3, 2, 2);
- expect(path.getBounds()).toEqual(PathKit.MakeLTRBRect(1, 1, 4, 3));
+ expect(path.getBounds()).toEqual(PathKit.LTRBRect(1, 1, 4, 3));
ExpectRectsToBeEqual(path.computeTightBounds(),
- PathKit.MakeLTRBRect(1, 1,
+ PathKit.LTRBRect(1, 1,
bits2float("0x40333334"), // 2.8
bits2float("0x40155556"))); // 2.3333333
path.delete();
diff --git a/experimental/pathkit/tests/pathops.spec.js b/experimental/pathkit/tests/pathops.spec.js
index 39d879c..273a6a4 100644
--- a/experimental/pathkit/tests/pathops.spec.js
+++ b/experimental/pathkit/tests/pathops.spec.js
@@ -117,11 +117,6 @@
return e;
}
- function fromCmds(cmds) {
- let [ptr, len] = PathKit.loadCmdsTypedArray(cmds);
- return PathKit.FromCmds(ptr, len);
- }
-
it('combines two paths with .op() and matches what we see from C++', function(done) {
LoadPathKit.then(() => {
// Test JSON created with:
@@ -135,11 +130,11 @@
for (testName of testNames) {
let test = json[testName];
- let path1 = fromCmds(test.p1);
+ let path1 = PathKit.FromCmds(test.p1);
expect(path1).not.toBeNull(`path1 error when loading cmds '${test.p1}'`);
path1.setFillType(getFillType(test.fillType1));
- let path2 = fromCmds(test.p2);
+ let path2 = PathKit.FromCmds(test.p2);
expect(path2).not.toBeNull(`path2 error when loading cmds '${test.p2}'`);
path2.setFillType(getFillType(test.fillType2));
@@ -149,7 +144,7 @@
expect(combined).toBeNull(`Test ${testName} should have not created output, but did`);
} else {
expect(combined).not.toBeNull();
- let expected = fromCmds(test.out);
+ let expected = PathKit.FromCmds(test.out);
// Do a tolerant match.
let diff = diffPaths(expected, combined);
if (test.expectMatch === 'yes'){
@@ -192,7 +187,7 @@
for (testName of testNames) {
let test = json[testName];
- let path = fromCmds(test.path);
+ let path = PathKit.FromCmds(test.path);
expect(path).not.toBeNull(`path1 error when loading cmds '${test.path}'`);
path.setFillType(getFillType(test.fillType));
@@ -202,7 +197,7 @@
expect(simplified).toBeNull(`Test ${testName} should have not created output, but did`);
} else {
expect(simplified).not.toBeNull();
- let expected = fromCmds(test.out);
+ let expected = PathKit.FromCmds(test.out);
// Do a tolerant match.
let diff = diffPaths(expected, simplified);
if (test.expectMatch === 'yes'){
diff --git a/experimental/pathkit/tests/svg.spec.js b/experimental/pathkit/tests/svg.spec.js
index c28618b..ca3afe2 100644
--- a/experimental/pathkit/tests/svg.spec.js
+++ b/experimental/pathkit/tests/svg.spec.js
@@ -45,8 +45,7 @@
[PathKit.LINE_VERB, 5, 295],
[PathKit.LINE_VERB, 205, 5],
[PathKit.CLOSE_VERB]];
- let [ptr, len] = PathKit.loadCmdsTypedArray(cmds);
- let path = PathKit.FromCmds(ptr, len);
+ let path = PathKit.FromCmds(cmds);
let svgStr = path.toSVGString();
// We output it in terse form, which is different than Wikipedia's version
@@ -64,8 +63,7 @@
[PathKit.LINE_VERB, 5, 295],
[PathKit.LINE_VERB, "0x15e80300", "0x400004dc"], // 9.37088e-26f, 2.0003f
[PathKit.CLOSE_VERB]];
- let [ptr, len] = PathKit.loadCmdsTypedArray(cmds);
- let path = PathKit.FromCmds(ptr, len);
+ let path = PathKit.FromCmds(cmds);
let svgStr = path.toSVGString();
expect(svgStr).toEqual('M9.37088e-26 2.0003L795 5L595 295L5 295L9.37088e-26 2.0003Z');
diff --git a/site/user/modules/pathkit.md b/site/user/modules/pathkit.md
index 8cf1b10..9dacee4 100644
--- a/site/user/modules/pathkit.md
+++ b/site/user/modules/pathkit.md
@@ -7,23 +7,774 @@
Download the library
--------------------
-See [the npm page](https://www.npmjs.com/package/experimental-pathkit-wasm) for details on downloading
-and getting started.
+See the the npm page for either the [WebAssembly](https://www.npmjs.com/package/experimental-pathkit-wasm) version
+or the [asm.js](https://www.npmjs.com/package/experimental-pathkit-asmjs) version
+for details on downloading and getting started.
+
+WebAssembly has faster load times and better overall performance but is
+currently supported by only Chrome and Firefox (with a flag).
+The asm.js version should run anywhere JavaScript does.
Features
--------
-PathKit is still under rapid development, so the exact API is still changing.
+PathKit is still under rapid development, so the exact API is subject to change.
The primary features are:
- API compatibility (e.g. drop-in replacement) with [Path2D](https://developer.mozilla.org/en-US/docs/Web/API/Path2D)
- Can output to SVG / Canvas / Path2D
- - Exposes a variety of path effects: <img width=800 src="./PathKit_effects.png"/>
+ - Exposes a variety of path effects:
+
+<style>
+ canvas.patheffect {
+ border: 1px dashed #AAA;
+ width: 200px;
+ height: 200px;
+ }
+</style>
+
+<div id=effects>
+ <canvas class=patheffect id=canvas1 title="Plain: A drawn star with overlapping solid lines"></canvas>
+ <canvas class=patheffect id=canvas2 title="Dash: A drawn star with overlapping dashed lines"></canvas>
+ <canvas class=patheffect id=canvas3 title="Trim: A portion of a drawn star with overlapping solid lines"></canvas>
+ <canvas class=patheffect id=canvas4 title="Simplify: A drawn star with non-overlapping solid lines."></canvas>
+ <canvas class=patheffect id=canvas5 title="Stroke: A drawn star with non-overlapping solid lines stroked at various thicknesses and with square edges"></canvas>
+ <canvas class=patheffect id=canvas6 title="Grow: A drawn star's expanding outline"></canvas>
+ <canvas class=patheffect id=canvas7 title="Shrink: A solid drawn star shrunk down"></canvas>
+ <canvas class=patheffect id=canvasTransform title="Transform: A drawn star moved and rotated by an Affine Matrix"></canvas>
+</div>
+
+<script src="https://unpkg.com/experimental-pathkit-asmjs@0.3.0/bin/pathkit.js"></script>
+<script>
+ try {
+ PathKitInit({
+ locateFile: (file) => 'https://unpkg.com/experimental-pathkit-asmjs@0.3.0/bin/'+file,
+ }).then((PathKit) => {
+ // Code goes here using PathKit
+ PathEffectsExample(PathKit);
+ MatrixTransformExample(PathKit);
+ });
+
+ }
+ catch(error) {
+ console.warn(error, 'falling back to image');
+ docment.getElementById('effects').innerHTML = '<img width=800 src="./PathKit_effects.png"/>'
+ }
+
+ function setCanvasSize(ctx, width, height) {
+ ctx.canvas.width = width;
+ ctx.canvas.height = height;
+ }
+
+ function drawStar(path) {
+ let R = 115.2, C = 128.0;
+ path.moveTo(C + R + 22, C);
+ for (let i = 1; i < 8; i++) {
+ let a = 2.6927937 * i;
+ path.lineTo(C + R * Math.cos(a) + 22, C + R * Math.sin(a));
+ }
+ path.closePath();
+ return path;
+ }
+
+ function PathEffectsExample(PathKit) {
+ let effects = [
+ // no-op
+ (path) => path,
+ // dash
+ (path, counter) => path.dash(10, 3, counter/5),
+ // trim (takes optional 3rd param for returning the trimmed part
+ // or the complement)
+ (path, counter) => path.trim((counter/100) % 1, 0.8, false),
+ // simplify
+ (path) => path.simplify(),
+ // stroke
+ (path, counter) => path.stroke({
+ width: 10 * (Math.sin(counter/30) + 1),
+ join: PathKit.StrokeJoin.BEVEL,
+ cap: PathKit.StrokeCap.BUTT,
+ miter_limit: 1,
+ }),
+ // "offset effect", that is, making a border around the shape.
+ (path, counter) => {
+ let orig = path.copy();
+ path.stroke({
+ width: 10 + (counter / 4) % 50,
+ join: PathKit.StrokeJoin.ROUND,
+ cap: PathKit.StrokeCap.SQUARE,
+ })
+ .op(orig, PathKit.PathOp.DIFFERENCE);
+ orig.delete();
+ },
+ (path, counter) => {
+ let simplified = path.simplify().copy();
+ path.stroke({
+ width: 2 + (counter / 2) % 100,
+ join: PathKit.StrokeJoin.BEVEL,
+ cap: PathKit.StrokeCap.BUTT,
+ })
+ .op(simplified, PathKit.PathOp.REVERSE_DIFFERENCE);
+ simplified.delete();
+ }
+ ];
+
+ let names = ["(plain)", "Dash", "Trim", "Simplify", "Stroke", "Grow", "Shrink"];
+
+ let counter = 0;
+ function frame() {
+ counter++;
+ for (let i = 0; i < effects.length; i++) {
+ let path = PathKit.NewPath();
+ drawStar(path);
+
+ // The transforms apply directly to the path.
+ effects[i](path, counter);
+
+ let ctx = document.getElementById(`canvas${i+1}`).getContext('2d');
+ setCanvasSize(ctx, 300, 300);
+ ctx.strokeStyle = '#3c597a';
+ ctx.fillStyle = '#3c597a';
+ if (i >=4 ) {
+ ctx.fill(path.toPath2D(), path.getFillTypeString());
+ } else {
+ ctx.stroke(path.toPath2D());
+ }
+
+ ctx.font = '42px monospace';
+
+ let x = 150-ctx.measureText(names[i]).width/2;
+ ctx.strokeText(names[i], x, 290);
+
+ path.delete();
+ }
+ window.requestAnimationFrame(frame);
+ }
+ window.requestAnimationFrame(frame);
+ }
+
+ function MatrixTransformExample(PathKit) {
+ // Creates an animated star that twists and moves.
+ let ctx = document.getElementById('canvasTransform').getContext('2d');
+ setCanvasSize(ctx, 300, 300);
+ ctx.strokeStyle = '#3c597a';
+
+ let path = drawStar(PathKit.NewPath());
+ // TODO(kjlubick): Perhaps expose some matrix helper functions to allow
+ // clients to build their own matrices like this?
+ // These matrices represent a 2 degree rotation and a 1% scale factor.
+ let scaleUp = [1.0094, -0.0352, 3.1041,
+ 0.0352, 1.0094, -6.4885,
+ 0 , 0 , 1];
+
+ let scaleDown = [ 0.9895, 0.0346, -2.8473,
+ -0.0346, 0.9895, 6.5276,
+ 0 , 0 , 1];
+
+ let i = 0;
+ function frame(){
+ i++;
+ if (Math.round(i/100) % 2) {
+ path.transform(scaleDown);
+ } else {
+ path.transform(scaleUp);
+ }
+
+ ctx.clearRect(0, 0, 300, 300);
+ ctx.stroke(path.toPath2D());
+
+ ctx.font = '42px monospace';
+ let x = 150-ctx.measureText('Transform').width/2;
+ ctx.strokeText('Transform', x, 290);
+
+ window.requestAnimationFrame(frame);
+ }
+ window.requestAnimationFrame(frame);
+ }
+</script>
-Example code
+Example Code
------------
The best place to look for examples on how to use PathKit would be in the
-[example.html](https://github.com/google/skia/blob/master/experimental/pathkit/npm-wasm/example.html#L45)
-which comes in the npm package.
\ No newline at end of file
+[example.html](https://github.com/google/skia/blob/master/experimental/pathkit/npm-wasm/example.html#L45),
+which comes in the npm package.
+
+
+API
+----
+
+The primary feature of the library is the `SkPath` object. It can be created:
+
+ - From the SVG string of a path `PathKit.FromSVGString(str)`
+ - From a 2D array of verbs and arguments `PathKit.FromCmds(cmds)`
+ - From `PathKit.NewPath()` (It will be blank)
+ - As a copy of an existing `SkPath` with `path.copy()` or `PathKit.NewPath(path)`
+
+It can be exported as:
+
+ - An SVG string `path.toSVGString()`
+ - A [Path2D](https://developer.mozilla.org/en-US/docs/Web/API/Path2D) object `path.toPath2D()`
+ - Directly to a canvas 2D context `path.toCanvas(ctx)`
+ - A 2D array of verbs and arguments `path.toCmds()`
+
+Once an SkPath object has been made, it can be interacted with in the following ways:
+
+ - expanded by any of the Path2D operations (`moveTo`, `lineTo`, `rect`, `arc`, etc)
+ - combined with other paths using `op` or `PathKit.MakeFromOp(p1, p2, op)`. For example, `path1.op(path2, PathKit.PathOp.INTERSECT)` will set path1 to be the area represented by where path1 and path2 overlap (intersect). `PathKit.MakeFromOp(path1, path2, PathKit.PathOp.INTERSECT)` will do the same but returned as a new `SkPath` object.
+ - adjusted with some of the effects (`trim`, `dash`, `stroke`, etc)
+
+
+**Important**: Any objects (`SkPath`, `SkOpBuilder`, etc) that are created must be cleaned up with `path.delete()` when they
+leave the scope to avoid leaking the memory in the WASM heap. This includes any of the constructors, `copy()`,
+or any function prefixed with "make".
+
+
+### PathKit ###
+
+#### `FromSVGString(str)` ####
+**str** - `String` representing an [SVGPath](https://www.w3schools.com/graphics/svg_path.asp)
+
+Returns an `SkPath` with the same verbs and arguments as the SVG string, or `null` on a failure.
+
+Example:
+
+ let path = PathKit.FromSVGString('M150 0 L75 200 L225 200 Z');
+ // path represents a triangle
+ // don't forget to do path.delete() when it goes out of scope.
+
+#### `FromCmds(cmds)` ####
+**cmds** - `Array<Array<Number>>`, a 2D array of commands, where a command is a verb
+ followed by its arguments.
+
+Returns an `SkPath` with the verbs and arguments from the list or `null` on a failure.
+
+This can be faster than calling `.moveTo()`, `.lineTo()`, etc many times.
+
+Example:
+
+ let cmds = [
+ [PathKit.MOVE_VERB, 0, 10],
+ [PathKit.LINE_VERB, 30, 40],
+ [PathKit.QUAD_VERB, 20, 50, 45, 60],
+ ];
+ let path = PathKit.FromCmds(cmds);
+ // path is the same as if a user had done
+ // let path = PathKit.NewPath().moveTo(0, 10).lineTo(30, 40).quadTo(20, 50, 45, 60);
+ // don't forget to do path.delete() when it goes out of scope.
+
+#### `NewPath()` ####
+
+Returns an empty `SkPath` object.
+
+Example:
+
+ let path = PathKit.NewPath();
+ path.moveTo(0, 10)
+ .lineTo(30, 40)
+ .quadTo(20, 50, 45, 60);
+ // don't forget to do path.delete() when it goes out of scope.
+ // Users can also do let path = new PathKit.SkPath();
+
+#### `NewPath(pathToCopy)` ####
+**pathToCopy** - SkPath, a path to make a copy of.
+
+Returns a `SkPath` that is a copy of the passed in `SkPath`.
+
+Example:
+
+ let otherPath = ...;
+ let clone = PathKit.NewPath(otherPath);
+ clone.simplify();
+ // don't forget to do clone.delete() when it goes out of scope.
+ // Users can also do let clone = new PathKit.SkPath(otherPath);
+ // or let clone = otherPath.copy();
+
+#### `MakeFromOp(pathOne, pathTwo, op)` ####
+**pathOne** - `SkPath`, a path. <br>
+**pathTwo** - `SkPath`, a path. <br>
+**op** - `PathOp`, an op to apply
+
+Returns a new `SkPath` that is the result of applying the given PathOp to the first and second
+path (order matters).
+
+Example:
+
+ let pathOne = PathKit.NewPath().moveTo(0, 20).lineTo(10, 10).lineTo(20, 20).close();
+ let pathTwo = PathKit.NewPath().moveTo(10, 20).lineTo(20, 10).lineTo(30, 20).close();
+ let mountains = PathKit.MakeFromOp(pathOne, pathTwo, PathKit.PathOp.UNION);
+ // don't forget to do mountains.delete() when it goes out of scope.
+ // Users can also do pathOne.op(pathTwo, PathKit.PathOp.UNION);
+ // to have the resulting path be stored to pathOne and avoid allocating another object.
+
+### SkPath (object) ###
+
+#### `addPath(otherPath)` ####
+**otherPath** - `SkPath`, a path to append to this path
+
+Adds the given path to `this` and then returns `this` for chaining purposes.
+
+#### `addPath(otherPath, transform)` ####
+**otherPath** - `SkPath`, a path to append to this path. <br>
+**transform** - [SVGMatrix](https://developer.mozilla.org/en-US/docs/Web/API/SVGMatrix),
+ a transform to apply to otherPath before appending it.
+
+Adds the given path to `this` after applying the transform and then returns `this` for
+chaining purposes. See [Path2D.addPath()](https://developer.mozilla.org/en-US/docs/Web/API/Path2D/addPath)
+for more details.
+
+#### `addPath(otherPath, a, b, c, d, e, f)` ####
+**otherPath** - `SkPath`, a path to append to this path. <br>
+**a, b, c, d, e, f** - `Number`, the six components of an
+ [SVGMatrix](https://developer.mozilla.org/en-US/docs/Web/API/SVGMatrix),
+ which define the transform to apply to otherPath before appending it.
+
+Adds the given path to `this` after applying the transform and then returns `this` for
+chaining purposes. See [Path2D.addPath()](https://developer.mozilla.org/en-US/docs/Web/API/Path2D/addPath)
+for more details.
+
+Example:
+
+ let box = PathKit.NewPath().rect(0, 0, 100, 100);
+ let moreBoxes = PathKit.NewPath();
+ // add box un-transformed (i.e. at 0, 0)
+ moreBoxes.addPath(box)
+ // the params fill out a 2d matrix like:
+ // a c e
+ // b d f
+ // 0 0 1
+ // add box 300 points to the right
+ .addPath(box, 1, 0, 0, 1, 300, 0)
+ // add a box shrunk by 50% in both directions
+ .addPath(box, 0.5, 0, 0, 0.5, 0, 0);
+ // moreBoxes now has 3 paths appended to it
+
+#### `addPath(otherPath, scaleX, skewX, transX, skewY, scaleY, transY, pers0, pers1, pers2)` ####
+**otherPath** - `SkPath`, a path to append to this path. <br>
+**scaleX, skewX, transX, skewY, scaleY, transY, pers0, pers1, pers2** -
+ `Number`, the nine components of an
+ [Affine Matrix](https://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations),
+ which define the transform to apply to otherPath before appending it.
+
+Adds the given path to `this` after applying the transform and then returns `this` for
+chaining purposes.
+
+Example:
+
+ let box = PathKit.NewPath().rect(0, 0, 100, 100);
+ let moreBoxes = PathKit.NewPath();
+ // add box un-transformed (i.e. at 0, 0)
+ moreBoxes.addPath(box)
+ // add box 300 points to the right
+ .addPath(box, 1, 0, 0,
+ 0, 1, 300,
+ 0, 0 ,1)
+ // add a box shrunk by 50% in both directions
+ .addPath(box, 0.5, 0, 0,
+ 0, 0.5, 0,
+ 0, 0, 1)
+ // moreBoxes now has 3 paths appended to it
+
+#### `arc(x, y, radius, startAngle, endAngle, ccw=false)` ####
+**x, y** - `Number`, The coordinates of the arc's center. <br>
+**radius** - `Number`, The radius of the arc. <br>
+**startAngle, endAngle** - `Number`, the start and end of the angle, measured
+ clockwise from the positive x axis and in radians. <br>
+**ccw** - `Boolean`, optional argument specifying if the arc should be drawn
+ counter-clockwise between **startAngle** and **endAngle** instead of
+ clockwise, the default.
+
+Adds the described arc to `this` then returns `this` for
+chaining purposes. See [Path2D.arc()](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/arc)
+for more details.
+
+Example:
+
+ let path = PathKit.NewPath();
+ path.moveTo(20, 120);
+ .arc(20, 120, 18, 0, 1.75 * Math.PI);
+ .lineTo(20, 120);
+ // path looks like a pie with a 1/8th slice removed.
+
+#### `arcTo(x1, y1, x2, y2, radius)` ####
+**x1, y1, x2, y2** - `Number`, The coordinates defining the control points. <br>
+**radius** - `Number`, The radius of the arc.
+
+Adds the described arc to `this` (appending a line, if needed) then returns `this` for
+chaining purposes. See [Path2D.arcTo()](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/arcTo)
+for more details.
+
+#### `close()` or `closePath()` ####
+Returns the pen to the start of the current sub-path, then returns `this` for
+chaining purposes. See [Path2D.closePath()](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/closePath)
+for more details.
+
+#### `computeTightBounds()` ####
+
+Returns an `SkRect` that represents the minimum and maximum area of
+`this` path. See [SkPath reference](https://skia.org/user/api/SkPath_Reference#SkPath_computeTightBounds)
+for more details.
+
+#### `conicTo(x1, y1, x2, y2, w)` ####
+**x1, y1, x2, y2** - `Number`, The coordinates defining the control point and the end point. <br>
+**w** - `Number`, The weight of the conic.
+
+Adds the described conic line to `this` (appending a line, if needed) then returns `this` for
+chaining purposes. See [SkPath reference](https://skia.org/user/api/SkPath_Reference#SkPath_conicTo)
+for more details.
+
+#### `copy()` ####
+
+Return a copy of `this` path.
+
+#### `cubicTo(cp1x, cp1y, cp2x, cp2y, x, y)` or `bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)` ####
+**cp1x, cp1y, cp2x, cp2y** - `Number`, The coordinates defining the control points. <br>
+**x,y** - `Number`, The coordinates defining the end point
+
+Adds the described cubic line to `this` (appending a line, if needed) then returns `this` for
+chaining purposes. See [Path2D.bezierCurveTo](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/bezierCurveTo)
+for more details.
+
+#### `dash(on, off, phase)` ####
+**on, off** - `Number`, The number of pixels the dash should be on (drawn) and off (blank). <br>
+**phase** - `Number`, The number of pixels the on/off should be offset (mod **on** + **off**)
+
+Applies a dashed path effect to `this` then returns `this` for chaining purposes.
+See the "Dash" effect above for a visual example.
+
+Example:
+
+ let box = PathKit.NewPath().rect(0, 0, 100, 100);
+ box.dash(20, 10, 3);
+ // box is now a dashed rectangle that will draw for 20 pixels, then
+ // stop for 10 pixels. Since phase is 3, the first line won't start
+ // at (0, 0), but 3 pixels around the path (3, 0)
+
+#### `ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, ccw=false)` ####
+**x, y** - `Number`, The coordinates of the center of the ellipse. <br>
+**radiusX, radiusY** - `Number`, The radii in the X and Y directions. <br>
+**rotation** - `Number`, The rotation in radians of this ellipse. <br>
+**startAngle, endAngle** - `Number`, the starting and ending angles of which to draw,
+ measured in radians from the positive x axis. <br>
+**ccw** - `Boolean`, optional argument specifying if the ellipse should be drawn
+ counter-clockwise between **startAngle** and **endAngle** instead of
+ clockwise, the default.
+
+Adds the described ellipse to `this` then returns `this` for chaining purposes.
+See [Path2D.ellipse](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/ellipse)
+for more details.
+
+#### `equals(otherPath)` ####
+**otherPath** - `SkPath`, the path to compare to.
+
+Returns a `Boolean` value based on if `this` path is equal
+to **otherPath**.
+
+#### `getBounds()` ####
+
+Returns an `SkRect` that represents the minimum and maximum area of
+`this` path. See [SkPath reference](https://skia.org/user/api/SkPath_Reference#SkPath_getBounds)
+for more details.
+
+#### `getFillType()` ####
+
+Returns a `FillType` based on what this path is. This defaults to
+`PathKit.FillType.WINDING`, but may change with `op()` or `simplify()`.
+
+Clients will typically want `getFillTypeString()` because that value
+can be passed directly to an SVG or Canvas.
+
+#### `getFillTypeString()` ####
+
+Returns a `String` representing the fillType of `this` path.
+The values are either "nonzero" or "evenodd".
+
+Example:
+
+ let path = ...;
+ let ctx = document.getElementById('canvas1').getContext('2d');
+ ctx.strokeStyle = 'green';
+ ctx.fill(path.toPath2D(), path.getFillTypeString());
+
+#### `moveTo(x, y)` ####
+**x, y** - `Number`, The coordinates of where the pen should be moved to.
+
+Moves the pen (without drawing) to the given coordinates then returns `this` for chaining purposes.
+See [Path2D.moveTo](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/moveTo)
+for more details.
+
+#### `lineTo(x, y)` ####
+**x, y** - `Number`, The coordinates of where the pen should be moved to.
+
+Draws a straight line to the given coordinates then returns `this` for chaining purposes.
+See [Path2D.lineTo](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineTo)
+for more details.
+
+#### `op(otherPath, operation)` ####
+**otherPath** - `SkPath`, The other path to be combined with `this`. <br>
+**operation** - `PathOp`, The operation to apply to the two paths.
+
+Combines otherPath into `this` path with the given operation and returns `this`
+for chaining purposes.
+
+Example:
+
+ let pathOne = PathKit.NewPath().moveTo(0, 20).lineTo(10, 10).lineTo(20, 20).close();
+ let pathTwo = PathKit.NewPath().moveTo(10, 20).lineTo(20, 10).lineTo(30, 20).close();
+ // Combine the two triangles to look like two mountains
+ let mountains = pathOne.copy().op(pathOne, pathTwo, PathKit.PathOp.UNION);
+ // set pathOne to be the small triangle where pathOne and pathTwo overlap
+ pathOne.op(pathOne, pathTwo, PathKit.PathOp.INTERSECT);
+ // since copy() was called, don't forget to call delete() on mountains.
+
+#### `quadTo(cpx, cpy, x, y)` or `quadraticCurveTo(cpx, cpy, x, y)` ####
+**cpx, cpy** - `Number`, The coordinates for the control point. <br>
+**x, y** - `Number`, The coordinates for the end point.
+
+Draws a quadratic Bézier curve with the given coordinates then returns `this` for chaining purposes.
+See [Path2D.quadraticCurveTo](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/quadraticCurveTo)
+for more details.
+
+#### `rect(x, y, w, h)` ####
+**x, y** - `Number`, The coordinates of the upper-left corner of the rectangle. <br>
+**w, h** - `Number`, The width and height of the rectangle
+
+Draws a rectangle on `this`, then returns `this` for chaining purposes.
+See [Path2D.rect](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/rect)
+for more details.
+
+#### `setFillType(fillType)` ####
+**fillType** - `FillType`, the new fillType.
+
+Set the fillType of the path. See [SkPath reference](https://skia.org/user/api/SkPath_Reference#SkPath_FillType)
+for more details.
+
+#### `simplify()` ####
+Set `this` path to a set of *non-overlapping* contours that describe the same area
+as the original path. See the "Simplify" effect above for a visual example.
+
+#### `stroke(opts)` ####
+**opts** - `StrokeOpts`, contains the options for stroking.
+
+
+Strokes `this` path out with the given options. This can be used for a variety of
+effects. See the "Stroke", "Grow", and "Shrink" effects above for visual examples.
+
+Example:
+
+ let box = PathKit.NewPath().rect(0, 0, 100, 100);
+ // Stroke the path with width 10 and rounded corners
+ let rounded = box.copy().stroke({width: 10, join: PathKit.StrokeJoin.ROUND});
+ // Grow effect, that is, a 20 pixel expansion around the box.
+ let grow = box.copy().stroke({width: 20}).op(box, PathKit.PathOp.DIFFERENCE);
+ // Shrink effect, in which we subtract away from the original
+ let simplified = box.copy().simplify(); // sometimes required for complicated paths
+ let shrink = box.copy().stroke({width: 15, cap: PathKit.StrokeCap.BUTT})
+ .op(simplified, PathKit.PathOp.REVERSE_DIFFERENCE);
+ // Don't forget to call delete() on each of the copies!
+
+#### `toCanvas(ctx)` ####
+**ctx** - `Canvas2DContext`, Canvas on which to draw the path.
+
+Draws `this` path on the passed in
+[Canvas Context](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D).
+
+Example:
+
+ let box = PathKit.NewPath().rect(0, 0, 100, 100);
+ let ctx = document.getElementById('canvas1').getContext('2d');
+ ctx.strokeStyle = 'green';
+ ctx.beginPath();
+ box.toCanvas(ctx);
+ ctx.stroke(); // could also ctx.fill()
+
+#### `toCmds()` ####
+
+Returns a 2D Array of verbs and args. See `PathKit.FromCmds()` for
+more details.
+
+#### `toPath2D()` ####
+
+Returns a [Path2D](https://developer.mozilla.org/en-US/docs/Web/API/Path2D) object
+that has the same operations as `this` path.
+
+Example:
+
+ let box = PathKit.NewPath().rect(0, 0, 100, 100);
+ let ctx = document.getElementById('canvas1').getContext('2d');
+ ctx.strokeStyle = 'green';
+ ctx.stroke(box.toPath2D());
+
+#### `toSVGString()` ####
+
+Returns a `String` representing an [SVGPath](https://www.w3schools.com/graphics/svg_path.asp) based on `this` path.
+
+Example:
+
+ let box = PathKit.NewPath().rect(0, 0, 100, 100);
+ let svg = document.getElementById('svg1');
+ let newPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
+ newPath.setAttribute('stroke', 'green');
+ newPath.setAttribute('fill', 'white');
+ newPath.setAttribute('d', box.toSVGString());
+ svg.appendChild(newPath);
+
+#### `transform(matr)` ####
+**matr** - `SkMatrix`, i.e. an `Array<Number>` of the nine numbers of an Affine Transform Matrix.
+
+Applies the specified [transform](https://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations)
+to `this` and then returns `this` for chaining purposes.
+
+#### `transform(scaleX, skewX, transX, skewY, scaleY, transY, pers0, pers1, pers2)` ####
+**scaleX, skewX, transX, skewY, scaleY, transY, pers0, pers1, pers2** -
+ `Number`, the nine numbers of an Affine Transform Matrix.
+
+Applies the specified [transform](https://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations)
+to `this` and then returns `this` for chaining purposes.
+
+Example:
+
+ let path = PathKit.NewPath().rect(0, 0, 100, 100);
+ // scale up the path by 5x
+ path.transform([5, 0, 0,
+ 0, 5, 0,
+ 0, 0, 1]);
+ // move the path 75 px to the right.
+ path.transform(1, 0, 75,
+ 0, 1, 0,
+ 0, 0, 1);
+
+#### `trim(startT, stopT, isComplement=false)` ####
+**startT, stopT** - `Number`, values in [0, 1] that indicate the start and stop
+ "percentages" of the path to draw <br>
+**isComplement** - `Boolean`, If the complement of the trimmed section should
+ be drawn instead of the areas between **startT** and **stopT**.
+
+Sets `this` path to be a subset of the original path, then returns `this` for chaining purposes.
+See the "Trim" effect above for a visual example.
+
+Example:
+
+ let box = PathKit.NewPath().rect(0, 0, 100, 100);
+ box.trim(0.25, 1.0);
+ // box is now the 3 segments that look like a U
+ // (the top segment has been removed).
+
+
+### SkOpBuilder (object) ###
+This object enables chaining multiple PathOps together.
+Create one with `let builder = new PathKit.SkOpBuilder();`
+When created, the internal state is "empty path".
+Don't forget to call `delete()` on both the builder and the result
+of `resolve()`
+
+#### `add(path, operation)` ####
+**path** - `SkPath`, The path to be combined with the given rule. <br>
+**operation** - `PathOp`, The operation to apply to the two paths.
+
+Adds a path and the operand to the builder.
+
+#### `make()` or `resolve()` ####
+
+Creates and returns a new `SkPath` based on all the given paths
+and operands.
+
+Don't forget to call `.delete()` on the returned path when it goes out of scope.
+
+
+### SkMatrix (struct) ###
+`SkMatrix` translates between a C++ struct and a JS Array.
+It basically takes a nine element 1D Array and turns it into a
+3x3 2D Affine Matrix.
+
+### SkRect (struct) ###
+
+`SkRect` translates between a C++ struct and a JS Object with
+the following keys (all values are `Number`:
+
+ - **fLeft**: x coordinate of top-left corner
+ - **fTop**: y coordinate of top-left corner
+ - **fRight**: x coordinate of bottom-right corner
+ - **fBottom**: y coordinate of bottom-rightcorner
+
+### StrokeOpts (struct) ###
+`StrokeOpts` translates between a C++ struct and a JS Object with
+the following keys:
+
+ - **width**, `Number` the width of the lines of the path. Default 1.
+ - **miter_limit**, `Number`, the miter limit. Defautl 4. See [SkPaint reference](https://skia.org/user/api/SkPaint_Reference#Miter_Limit) for more details.
+ - **join**, `StrokeJoin`, the join to use. Default `PathKit.StrokeJoin.MITER`.
+See [SkPaint reference](https://skia.org/user/api/SkPaint_Reference#SkPaint_Join) for more details.
+ - **cap**, `StrokeCap`, the cap to use. Default `PathKit.StrokeCap.BUTT`.
+See [SkPaint reference](https://skia.org/user/api/SkPaint_Reference#Stroke_Cap) for more details.
+
+### PathOp (enum) ###
+The following enum values are exposed. They are essentially constant
+objects, differentiated by thier `.value` property.
+
+ - `PathKit.PathOp.DIFFERENCE`
+ - `PathKit.PathOp.INTERSECT`
+ - `PathKit.PathOp.REVERSE_DIFFERENCE`
+ - `PathKit.PathOp.UNION`
+ - `PathKit.PathOp.XOR`
+
+These are used in `PathKit.MakeFromOp()` and `SkPath.op()`.
+
+### FillType (enum) ###
+The following enum values are exposed. They are essentially constant
+objects, differentiated by thier `.value` property.
+
+ - `PathKit.FillType.WINDING` (also known as nonzero)
+ - `PathKit.FillType.EVENODD`
+ - `PathKit.FillType.INVERSE_WINDING`
+ - `PathKit.FillType.INVERSE_EVENODD`
+
+These are used by `SkPath.getFillType()` and `SkPath.setFillType()`, but
+generally clients will want `SkPath.getFillTypeString()`.
+
+### StrokeJoin (enum) ###
+The following enum values are exposed. They are essentially constant
+objects, differentiated by thier `.value` property.
+
+ - `PathKit.StrokeJoin.MITER`
+ - `PathKit.StrokeJoin.ROUND`
+ - `PathKit.StrokeJoin.BEVEL`
+
+See [SkPaint reference](https://skia.org/user/api/SkPaint_Reference#SkPaint_Join) for more details.
+
+### StrokeCap (enum) ###
+The following enum values are exposed. They are essentially constant
+objects, differentiated by thier `.value` property.
+
+ - `PathKit.StrokeCap.BUTT`
+ - `PathKit.StrokeCap.ROUND`
+ - `PathKit.StrokeCap.SQUARE`
+
+See [SkPaint reference](https://skia.org/user/api/SkPaint_Reference#Stroke_Cap) for more details.
+
+### Constants ###
+The following constants are exposed:
+
+ - `PathKit.MOVE_VERB` = 0
+ - `PathKit.LINE_VERB` = 1
+ - `PathKit.QUAD_VERB` = 2
+ - `PathKit.CONIC_VERB` = 3
+ - `PathKit.CUBIC_VERB` = 4
+ - `PathKit.CLOSE_VERB` = 5
+
+These are only needed for `PathKit.FromCmds()`.
+
+### Functions for testing only ###
+
+#### `PathKit.LTRBRect(left, top, right, bottom)` ####
+**left** - `Number`, x coordinate of top-left corner of the `SkRect`. <br>
+**top** - `Number`, y coordinate of top-left corner of the `SkRect`. <br>
+**right** - `Number`, x coordinate of bottom-right corner of the `SkRect`. <br>
+**bottom** - `Number`, y coordinate of bottom-right corner of the `SkRect`.
+
+Returns an `SkRect` object with the given params.
+
+#### `SkPath.dump()` ####
+
+Prints all the verbs and arguments to the console.
+Only available on Debug and Test builds.