|  | /* | 
|  | * Copyright 2018 Google LLC | 
|  | * | 
|  | * Use of this source code is governed by a BSD-style license that can be | 
|  | * found in the LICENSE file. | 
|  | */ | 
|  |  | 
|  | #include "include/core/SkCubicMap.h" | 
|  | #include "include/core/SkMatrix.h" | 
|  | #include "include/core/SkPaint.h" | 
|  | #include "include/core/SkPath.h" | 
|  | #include "include/core/SkRect.h" | 
|  | #include "include/core/SkString.h" | 
|  | #include "include/core/SkStrokeRec.h" | 
|  | #include "include/effects/SkDashPathEffect.h" | 
|  | #include "include/effects/SkTrimPathEffect.h" | 
|  | #include "include/pathops/SkPathOps.h" | 
|  | #include "include/private/SkFloatBits.h" | 
|  | #include "include/private/SkFloatingPoint.h" | 
|  | #include "include/utils/SkParsePath.h" | 
|  | #include "src/core/SkPaintDefaults.h" | 
|  |  | 
|  | #include <emscripten/emscripten.h> | 
|  | #include <emscripten/bind.h> | 
|  |  | 
|  | using namespace emscripten; | 
|  |  | 
|  | static const int MOVE = 0; | 
|  | static const int LINE = 1; | 
|  | static const int QUAD = 2; | 
|  | static const int CONIC = 3; | 
|  | static const int CUBIC = 4; | 
|  | static const int CLOSE = 5; | 
|  |  | 
|  | // Just for self-documenting purposes where the main thing being returned is an | 
|  | // SkPath, but in an error case, something of type null (which is val) could also be | 
|  | // returned; | 
|  | using SkPathOrNull = emscripten::val; | 
|  | // Self-documenting for when we return a string | 
|  | using JSString = emscripten::val; | 
|  | using JSArray = emscripten::val; | 
|  |  | 
|  | // ================================================================================= | 
|  | // Creating/Exporting Paths with cmd arrays | 
|  | // ================================================================================= | 
|  |  | 
|  | template <typename VisitFunc> | 
|  | void VisitPath(const SkPath& p, VisitFunc&& f) { | 
|  | SkPath::RawIter iter(p); | 
|  | SkPoint pts[4]; | 
|  | SkPath::Verb verb; | 
|  | while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { | 
|  | f(verb, pts, iter); | 
|  | } | 
|  | } | 
|  |  | 
|  | JSArray EMSCRIPTEN_KEEPALIVE ToCmds(const SkPath& path) { | 
|  | JSArray cmds = emscripten::val::array(); | 
|  |  | 
|  | VisitPath(path, [&cmds](SkPath::Verb verb, const SkPoint pts[4], SkPath::RawIter iter) { | 
|  | JSArray cmd = emscripten::val::array(); | 
|  | switch (verb) { | 
|  | case SkPath::kMove_Verb: | 
|  | cmd.call<void>("push", MOVE, pts[0].x(), pts[0].y()); | 
|  | break; | 
|  | case SkPath::kLine_Verb: | 
|  | cmd.call<void>("push", LINE, pts[1].x(), pts[1].y()); | 
|  | break; | 
|  | case SkPath::kQuad_Verb: | 
|  | cmd.call<void>("push", QUAD, pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y()); | 
|  | break; | 
|  | case SkPath::kConic_Verb: | 
|  | cmd.call<void>("push", CONIC, | 
|  | pts[1].x(), pts[1].y(), | 
|  | pts[2].x(), pts[2].y(), iter.conicWeight()); | 
|  | break; | 
|  | case SkPath::kCubic_Verb: | 
|  | cmd.call<void>("push", CUBIC, | 
|  | pts[1].x(), pts[1].y(), | 
|  | pts[2].x(), pts[2].y(), | 
|  | pts[3].x(), pts[3].y()); | 
|  | break; | 
|  | case SkPath::kClose_Verb: | 
|  | cmd.call<void>("push", CLOSE); | 
|  | break; | 
|  | case SkPath::kDone_Verb: | 
|  | SkASSERT(false); | 
|  | break; | 
|  | } | 
|  | cmds.call<void>("push", cmd); | 
|  | }); | 
|  | return cmds; | 
|  | } | 
|  |  | 
|  | // This type signature is a mess, but it's necessary. See, we can't use "bind" (EMSCRIPTEN_BINDINGS) | 
|  | // and pointers to primitive types (Only bound types like SkPoint). We could if we used | 
|  | // cwrap (see https://becominghuman.ai/passing-and-returning-webassembly-array-parameters-a0f572c65d97) | 
|  | // but that requires us to stick to C code and, AFAIK, doesn't allow us to return nice things like | 
|  | // SkPath or SkOpBuilder. | 
|  | // | 
|  | // So, basically, if we are using C++ and EMSCRIPTEN_BINDINGS, we can't have primative pointers | 
|  | // in our function type signatures. (this gives an error message like "Cannot call foo due to unbound | 
|  | // types Pi, Pf").  But, we can just pretend they are numbers and cast them to be pointers and | 
|  | // the compiler is happy. | 
|  | SkPathOrNull EMSCRIPTEN_KEEPALIVE FromCmds(uintptr_t /* float* */ cptr, int numCmds) { | 
|  | const auto* cmds = reinterpret_cast<const float*>(cptr); | 
|  | SkPath path; | 
|  | float x1, y1, x2, y2, x3, y3; | 
|  |  | 
|  | // if there are not enough arguments, bail with the path we've constructed so far. | 
|  | #define CHECK_NUM_ARGS(n) \ | 
|  | if ((i + n) > numCmds) { \ | 
|  | SkDebugf("Not enough args to match the verbs. Saw %d commands\n", numCmds); \ | 
|  | return emscripten::val::null(); \ | 
|  | } | 
|  |  | 
|  | for(int i = 0; i < numCmds;){ | 
|  | switch (sk_float_floor2int(cmds[i++])) { | 
|  | case MOVE: | 
|  | CHECK_NUM_ARGS(2); | 
|  | x1 = cmds[i++], y1 = cmds[i++]; | 
|  | path.moveTo(x1, y1); | 
|  | break; | 
|  | case LINE: | 
|  | CHECK_NUM_ARGS(2); | 
|  | x1 = cmds[i++], y1 = cmds[i++]; | 
|  | path.lineTo(x1, y1); | 
|  | break; | 
|  | case QUAD: | 
|  | CHECK_NUM_ARGS(4); | 
|  | x1 = cmds[i++], y1 = cmds[i++]; | 
|  | x2 = cmds[i++], y2 = cmds[i++]; | 
|  | path.quadTo(x1, y1, x2, y2); | 
|  | break; | 
|  | case CONIC: | 
|  | CHECK_NUM_ARGS(5); | 
|  | x1 = cmds[i++], y1 = cmds[i++]; | 
|  | x2 = cmds[i++], y2 = cmds[i++]; | 
|  | x3 = cmds[i++]; // weight | 
|  | path.conicTo(x1, y1, x2, y2, x3); | 
|  | break; | 
|  | case CUBIC: | 
|  | CHECK_NUM_ARGS(6); | 
|  | x1 = cmds[i++], y1 = cmds[i++]; | 
|  | x2 = cmds[i++], y2 = cmds[i++]; | 
|  | x3 = cmds[i++], y3 = cmds[i++]; | 
|  | path.cubicTo(x1, y1, x2, y2, x3, y3); | 
|  | break; | 
|  | case CLOSE: | 
|  | path.close(); | 
|  | break; | 
|  | default: | 
|  | SkDebugf("  path: UNKNOWN command %f, aborting dump...\n", cmds[i-1]); | 
|  | return emscripten::val::null(); | 
|  | } | 
|  | } | 
|  |  | 
|  | #undef CHECK_NUM_ARGS | 
|  |  | 
|  | return emscripten::val(path); | 
|  | } | 
|  |  | 
|  | SkPath EMSCRIPTEN_KEEPALIVE NewPath() { | 
|  | return SkPath(); | 
|  | } | 
|  |  | 
|  | SkPath EMSCRIPTEN_KEEPALIVE CopyPath(const SkPath& a) { | 
|  | SkPath copy(a); | 
|  | return copy; | 
|  | } | 
|  |  | 
|  | bool EMSCRIPTEN_KEEPALIVE Equals(const SkPath& a, const SkPath& b) { | 
|  | return a == b; | 
|  | } | 
|  |  | 
|  | //======================================================================================== | 
|  | // Path things | 
|  | //======================================================================================== | 
|  |  | 
|  | // All these Apply* methods are simple wrappers to avoid returning an object. | 
|  | // The default WASM bindings produce code that will leak if a return value | 
|  | // isn't assigned to a JS variable and has delete() called on it. | 
|  | // These Apply methods, combined with the smarter binding code allow for chainable | 
|  | // commands that don't leak if the return value is ignored (i.e. when used intuitively). | 
|  |  | 
|  | void ApplyArcTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, | 
|  | SkScalar radius) { | 
|  | p.arcTo(x1, y1, x2, y2, radius); | 
|  | } | 
|  |  | 
|  | void ApplyClose(SkPath& p) { | 
|  | p.close(); | 
|  | } | 
|  |  | 
|  | void ApplyConicTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, | 
|  | SkScalar w) { | 
|  | p.conicTo(x1, y1, x2, y2, w); | 
|  | } | 
|  |  | 
|  | void ApplyCubicTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, | 
|  | SkScalar x3, SkScalar y3) { | 
|  | p.cubicTo(x1, y1, x2, y2, x3, y3); | 
|  | } | 
|  |  | 
|  | void ApplyLineTo(SkPath& p, SkScalar x, SkScalar y) { | 
|  | p.lineTo(x, y); | 
|  | } | 
|  |  | 
|  | void ApplyMoveTo(SkPath& p, SkScalar x, SkScalar y) { | 
|  | p.moveTo(x, y); | 
|  | } | 
|  |  | 
|  | void ApplyQuadTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) { | 
|  | p.quadTo(x1, y1, x2, y2); | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | //======================================================================================== | 
|  | // SVG things | 
|  | //======================================================================================== | 
|  |  | 
|  | JSString EMSCRIPTEN_KEEPALIVE ToSVGString(const SkPath& path) { | 
|  | SkString s; | 
|  | SkParsePath::ToSVGString(path, &s); | 
|  | // Wrapping it in val automatically turns it into a JS string. | 
|  | // Not too sure on performance implications, but is is simpler than | 
|  | // returning a raw pointer to const char * and then using | 
|  | // UTF8ToString() on the calling side. | 
|  | return emscripten::val(s.c_str()); | 
|  | } | 
|  |  | 
|  |  | 
|  | SkPathOrNull EMSCRIPTEN_KEEPALIVE FromSVGString(std::string str) { | 
|  | SkPath path; | 
|  | if (SkParsePath::FromSVGString(str.c_str(), &path)) { | 
|  | return emscripten::val(path); | 
|  | } | 
|  | return emscripten::val::null(); | 
|  | } | 
|  |  | 
|  | //======================================================================================== | 
|  | // PATHOP things | 
|  | //======================================================================================== | 
|  |  | 
|  | bool EMSCRIPTEN_KEEPALIVE ApplySimplify(SkPath& path) { | 
|  | return Simplify(path, &path); | 
|  | } | 
|  |  | 
|  | bool EMSCRIPTEN_KEEPALIVE ApplyPathOp(SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) { | 
|  | 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)) { | 
|  | return emscripten::val(path); | 
|  | } | 
|  | return emscripten::val::null(); | 
|  | } | 
|  |  | 
|  | //======================================================================================== | 
|  | // Canvas things | 
|  | //======================================================================================== | 
|  |  | 
|  | void EMSCRIPTEN_KEEPALIVE ToCanvas(const SkPath& path, emscripten::val /* Path2D or Canvas*/ ctx) { | 
|  | SkPath::Iter iter(path, false); | 
|  | SkPoint pts[4]; | 
|  | SkPath::Verb verb; | 
|  | while ((verb = iter.next(pts, false)) != SkPath::kDone_Verb) { | 
|  | switch (verb) { | 
|  | case SkPath::kMove_Verb: | 
|  | ctx.call<void>("moveTo", pts[0].x(), pts[0].y()); | 
|  | break; | 
|  | case SkPath::kLine_Verb: | 
|  | ctx.call<void>("lineTo", pts[1].x(), pts[1].y()); | 
|  | break; | 
|  | case SkPath::kQuad_Verb: | 
|  | ctx.call<void>("quadraticCurveTo", pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y()); | 
|  | break; | 
|  | case SkPath::kConic_Verb: | 
|  | SkPoint quads[5]; | 
|  | // approximate with 2^1=2 quads. | 
|  | SkPath::ConvertConicToQuads(pts[0], pts[1], pts[2], iter.conicWeight(), quads, 1); | 
|  | ctx.call<void>("quadraticCurveTo", quads[1].x(), quads[1].y(), quads[2].x(), quads[2].y()); | 
|  | ctx.call<void>("quadraticCurveTo", quads[3].x(), quads[3].y(), quads[4].x(), quads[4].y()); | 
|  | break; | 
|  | case SkPath::kCubic_Verb: | 
|  | ctx.call<void>("bezierCurveTo", pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y(), | 
|  | pts[3].x(), pts[3].y()); | 
|  | break; | 
|  | case SkPath::kClose_Verb: | 
|  | ctx.call<void>("closePath"); | 
|  | break; | 
|  | case SkPath::kDone_Verb: | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | emscripten::val JSPath2D = emscripten::val::global("Path2D"); | 
|  |  | 
|  | emscripten::val EMSCRIPTEN_KEEPALIVE ToPath2D(const SkPath& path) { | 
|  | emscripten::val retVal = JSPath2D.new_(); | 
|  | ToCanvas(path, retVal); | 
|  | return retVal; | 
|  | } | 
|  |  | 
|  | // ====================================================================================== | 
|  | // Path2D API things | 
|  | // ====================================================================================== | 
|  | void ApplyAddRect(SkPath& path, SkScalar x, SkScalar y, SkScalar width, SkScalar height) { | 
|  | path.addRect(x, y, x+width, y+height); | 
|  | } | 
|  |  | 
|  | void ApplyAddArc(SkPath& path, SkScalar x, SkScalar y, SkScalar radius, | 
|  | SkScalar startAngle, SkScalar endAngle, bool ccw) { | 
|  | SkPath temp; | 
|  | SkRect bounds = SkRect::MakeLTRB(x-radius, y-radius, x+radius, y+radius); | 
|  | const auto sweep = SkRadiansToDegrees(endAngle - startAngle) - 360 * ccw; | 
|  | temp.addArc(bounds, SkRadiansToDegrees(startAngle), sweep); | 
|  | path.addPath(temp, SkPath::kExtend_AddPathMode); | 
|  | } | 
|  |  | 
|  | void ApplyEllipse(SkPath& path, SkScalar x, SkScalar y, SkScalar radiusX, SkScalar radiusY, | 
|  | SkScalar rotation, SkScalar startAngle, SkScalar endAngle, bool ccw) { | 
|  | // This is easiest to do by making a new path and then extending the current path | 
|  | // (this properly catches the cases of if there's a moveTo before this call or not). | 
|  | SkRect bounds = SkRect::MakeLTRB(x-radiusX, y-radiusY, x+radiusX, y+radiusY); | 
|  | SkPath temp; | 
|  | const auto sweep = SkRadiansToDegrees(endAngle - startAngle) - (360 * ccw); | 
|  | temp.addArc(bounds, SkRadiansToDegrees(startAngle), sweep); | 
|  |  | 
|  | SkMatrix m; | 
|  | m.setRotate(SkRadiansToDegrees(rotation), x, y); | 
|  | path.addPath(temp, m, SkPath::kExtend_AddPathMode); | 
|  | } | 
|  |  | 
|  | // Allows for full matix control. | 
|  | void ApplyAddPath(SkPath& orig, const SkPath& newPath, | 
|  | SkScalar scaleX, SkScalar skewX,  SkScalar transX, | 
|  | SkScalar skewY,  SkScalar scaleY, SkScalar transY, | 
|  | SkScalar pers0, SkScalar pers1, SkScalar pers2) { | 
|  | SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX, | 
|  | skewY , scaleY, transY, | 
|  | pers0 , pers1 , pers2); | 
|  | orig.addPath(newPath, m); | 
|  | } | 
|  |  | 
|  | JSString GetFillTypeString(const SkPath& path) { | 
|  | if (path.getFillType() == SkPath::FillType::kWinding_FillType) { | 
|  | return emscripten::val("nonzero"); | 
|  | } else if (path.getFillType() == SkPath::FillType::kEvenOdd_FillType) { | 
|  | return emscripten::val("evenodd"); | 
|  | } else { | 
|  | SkDebugf("warning: can't translate inverted filltype to HTML Canvas\n"); | 
|  | return emscripten::val("nonzero"); //Use default | 
|  | } | 
|  | } | 
|  |  | 
|  | //======================================================================================== | 
|  | // Path Effects | 
|  | //======================================================================================== | 
|  |  | 
|  | bool ApplyDash(SkPath& path, SkScalar on, SkScalar off, SkScalar phase) { | 
|  | SkScalar intervals[] = { on, off }; | 
|  | auto pe = SkDashPathEffect::Make(intervals, 2, phase); | 
|  | if (!pe) { | 
|  | SkDebugf("Invalid args to dash()\n"); | 
|  | return false; | 
|  | } | 
|  | SkStrokeRec rec(SkStrokeRec::InitStyle::kHairline_InitStyle); | 
|  | if (pe->filterPath(&path, path, &rec, nullptr)) { | 
|  | return true; | 
|  | } | 
|  | SkDebugf("Could not make dashed path\n"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool ApplyTrim(SkPath& path, SkScalar startT, SkScalar stopT, bool isComplement) { | 
|  | auto mode = isComplement ? SkTrimPathEffect::Mode::kInverted : SkTrimPathEffect::Mode::kNormal; | 
|  | auto pe = SkTrimPathEffect::Make(startT, stopT, mode); | 
|  | if (!pe) { | 
|  | SkDebugf("Invalid args to trim(): startT and stopT must be in [0,1]\n"); | 
|  | return false; | 
|  | } | 
|  | SkStrokeRec rec(SkStrokeRec::InitStyle::kHairline_InitStyle); | 
|  | if (pe->filterPath(&path, path, &rec, nullptr)) { | 
|  | return true; | 
|  | } | 
|  | SkDebugf("Could not trim path\n"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | struct StrokeOpts { | 
|  | // Default values are set in chaining.js which allows clients | 
|  | // to set any number of them. Otherwise, the binding code complains if | 
|  | // any are omitted. | 
|  | SkScalar width; | 
|  | SkScalar miter_limit; | 
|  | SkPaint::Join join; | 
|  | SkPaint::Cap cap; | 
|  | }; | 
|  |  | 
|  | bool ApplyStroke(SkPath& path, StrokeOpts opts) { | 
|  | SkPaint p; | 
|  | p.setStyle(SkPaint::kStroke_Style); | 
|  | p.setStrokeCap(opts.cap); | 
|  | p.setStrokeJoin(opts.join); | 
|  | p.setStrokeWidth(opts.width); | 
|  | p.setStrokeMiter(opts.miter_limit); | 
|  |  | 
|  | return p.getFillPath(path, &path); | 
|  | } | 
|  |  | 
|  | //======================================================================================== | 
|  | // Matrix things | 
|  | //======================================================================================== | 
|  |  | 
|  | struct SimpleMatrix { | 
|  | SkScalar scaleX, skewX,  transX; | 
|  | SkScalar skewY,  scaleY, transY; | 
|  | SkScalar pers0,  pers1,  pers2; | 
|  | }; | 
|  |  | 
|  | SkMatrix toSkMatrix(const SimpleMatrix& sm) { | 
|  | return SkMatrix::MakeAll(sm.scaleX, sm.skewX , sm.transX, | 
|  | sm.skewY , sm.scaleY, sm.transY, | 
|  | sm.pers0 , sm.pers1 , sm.pers2); | 
|  | } | 
|  |  | 
|  | void ApplyTransform(SkPath& orig, const SimpleMatrix& sm) { | 
|  | orig.transform(toSkMatrix(sm)); | 
|  | } | 
|  |  | 
|  | void ApplyTransform(SkPath& orig, | 
|  | SkScalar scaleX, SkScalar skewX,  SkScalar transX, | 
|  | SkScalar skewY,  SkScalar scaleY, SkScalar transY, | 
|  | SkScalar pers0, SkScalar pers1, SkScalar pers2) { | 
|  | SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX, | 
|  | skewY , scaleY, transY, | 
|  | pers0 , pers1 , pers2); | 
|  | orig.transform(m); | 
|  | } | 
|  |  | 
|  | //======================================================================================== | 
|  | // Testing things | 
|  | //======================================================================================== | 
|  |  | 
|  | // The use case for this is on the JS side is something like: | 
|  | //     PathKit.SkBits2FloatUnsigned(parseInt("0xc0a00000")) | 
|  | // to have precise float values for tests. In the C++ tests, we can use SkBits2Float because | 
|  | // it takes int32_t, but the JS parseInt basically returns an unsigned int. So, we add in | 
|  | // this helper which casts for us on the way to SkBits2Float. | 
|  | float SkBits2FloatUnsigned(uint32_t floatAsBits) { | 
|  | return SkBits2Float((int32_t) floatAsBits); | 
|  | } | 
|  |  | 
|  | // Binds the classes to the JS | 
|  | // | 
|  | // See https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/embind.html#non-member-functions-on-the-javascript-prototype | 
|  | // for more on binding non-member functions to the JS object, allowing us to rewire | 
|  | // various functions.  That is, we can make the SkPath we expose appear to have methods | 
|  | // that the original SkPath does not, like rect(x, y, width, height) and toPath2D(). | 
|  | // | 
|  | // An important detail for binding non-member functions is that the first argument | 
|  | // must be SkPath& (the reference part is very important). | 
|  | // | 
|  | // Note that we can't expose default or optional arguments, but we can have multiple | 
|  | // declarations of the same function that take different amounts of arguments. | 
|  | // For example, see _transform | 
|  | // Additionally, we are perfectly happy to handle default arguments and function | 
|  | // overloads in the JS glue code (see chaining.js::addPath() for an example). | 
|  | EMSCRIPTEN_BINDINGS(skia) { | 
|  | class_<SkPath>("SkPath") | 
|  | .constructor<>() | 
|  | .constructor<const SkPath&>() | 
|  |  | 
|  | // Path2D API | 
|  | .function("_addPath", &ApplyAddPath) | 
|  | // 3 additional overloads of addPath are handled in JS bindings | 
|  | .function("_arc", &ApplyAddArc) | 
|  | .function("_arcTo", &ApplyArcTo) | 
|  | //"bezierCurveTo" alias handled in JS bindings | 
|  | .function("_close", &ApplyClose) | 
|  | //"closePath" alias handled in JS bindings | 
|  | .function("_conicTo", &ApplyConicTo) | 
|  | .function("_cubicTo", &ApplyCubicTo) | 
|  |  | 
|  | .function("_ellipse", &ApplyEllipse) | 
|  | .function("_lineTo", &ApplyLineTo) | 
|  | .function("_moveTo", &ApplyMoveTo) | 
|  | // "quadraticCurveTo" alias handled in JS bindings | 
|  | .function("_quadTo", &ApplyQuadTo) | 
|  | .function("_rect", &ApplyAddRect) | 
|  |  | 
|  | // Extra features | 
|  | .function("setFillType", &SkPath::setFillType) | 
|  | .function("getFillType", &SkPath::getFillType) | 
|  | .function("getFillTypeString", &GetFillTypeString) | 
|  | .function("getBounds", &SkPath::getBounds) | 
|  | .function("computeTightBounds", &SkPath::computeTightBounds) | 
|  | .function("equals", &Equals) | 
|  | .function("copy", &CopyPath) | 
|  |  | 
|  | // PathEffects | 
|  | .function("_dash", &ApplyDash) | 
|  | .function("_trim", &ApplyTrim) | 
|  | .function("_stroke", &ApplyStroke) | 
|  |  | 
|  | // Matrix | 
|  | .function("_transform", select_overload<void(SkPath& orig, const SimpleMatrix& sm)>(&ApplyTransform)) | 
|  | .function("_transform", select_overload<void(SkPath& orig, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&ApplyTransform)) | 
|  |  | 
|  | // PathOps | 
|  | .function("_simplify", &ApplySimplify) | 
|  | .function("_op", &ApplyPathOp) | 
|  |  | 
|  | // Exporting | 
|  | .function("toCmds", &ToCmds) | 
|  | .function("toPath2D", &ToPath2D) | 
|  | .function("toCanvas", &ToCanvas) | 
|  | .function("toSVGString", &ToSVGString) | 
|  |  | 
|  | #ifdef PATHKIT_TESTING | 
|  | .function("dump", select_overload<void() const>(&SkPath::dump)) | 
|  | .function("dumpHex", select_overload<void() const>(&SkPath::dumpHex)) | 
|  | #endif | 
|  | ; | 
|  |  | 
|  | class_<SkOpBuilder>("SkOpBuilder") | 
|  | .constructor<>() | 
|  |  | 
|  | .function("add", &SkOpBuilder::add) | 
|  | .function("make", &ResolveBuilder) | 
|  | .function("resolve", &ResolveBuilder); | 
|  |  | 
|  | // Without these function() bindings, the function would be exposed but oblivious to | 
|  | // our types (e.g. SkPath) | 
|  |  | 
|  | // Import | 
|  | function("FromSVGString", &FromSVGString); | 
|  | 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("MakeFromOp", &MakeFromOp); | 
|  |  | 
|  | enum_<SkPathOp>("PathOp") | 
|  | .value("DIFFERENCE",         SkPathOp::kDifference_SkPathOp) | 
|  | .value("INTERSECT",          SkPathOp::kIntersect_SkPathOp) | 
|  | .value("UNION",              SkPathOp::kUnion_SkPathOp) | 
|  | .value("XOR",                SkPathOp::kXOR_SkPathOp) | 
|  | .value("REVERSE_DIFFERENCE", SkPathOp::kReverseDifference_SkPathOp); | 
|  |  | 
|  | enum_<SkPath::FillType>("FillType") | 
|  | .value("WINDING",            SkPath::FillType::kWinding_FillType) | 
|  | .value("EVENODD",            SkPath::FillType::kEvenOdd_FillType) | 
|  | .value("INVERSE_WINDING",    SkPath::FillType::kInverseWinding_FillType) | 
|  | .value("INVERSE_EVENODD",    SkPath::FillType::kInverseEvenOdd_FillType); | 
|  |  | 
|  | constant("MOVE_VERB",  MOVE); | 
|  | constant("LINE_VERB",  LINE); | 
|  | constant("QUAD_VERB",  QUAD); | 
|  | constant("CONIC_VERB", CONIC); | 
|  | constant("CUBIC_VERB", CUBIC); | 
|  | constant("CLOSE_VERB", CLOSE); | 
|  |  | 
|  | // A value object is much simpler than a class - it is returned as a JS | 
|  | // object and does not require delete(). | 
|  | // https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/embind.html#value-types | 
|  | value_object<SkRect>("SkRect") | 
|  | .field("fLeft",   &SkRect::fLeft) | 
|  | .field("fTop",    &SkRect::fTop) | 
|  | .field("fRight",  &SkRect::fRight) | 
|  | .field("fBottom", &SkRect::fBottom); | 
|  |  | 
|  | function("LTRBRect", &SkRect::MakeLTRB); | 
|  |  | 
|  | // Stroke | 
|  | enum_<SkPaint::Join>("StrokeJoin") | 
|  | .value("MITER", SkPaint::Join::kMiter_Join) | 
|  | .value("ROUND", SkPaint::Join::kRound_Join) | 
|  | .value("BEVEL", SkPaint::Join::kBevel_Join); | 
|  |  | 
|  | enum_<SkPaint::Cap>("StrokeCap") | 
|  | .value("BUTT",   SkPaint::Cap::kButt_Cap) | 
|  | .value("ROUND",  SkPaint::Cap::kRound_Cap) | 
|  | .value("SQUARE", SkPaint::Cap::kSquare_Cap); | 
|  |  | 
|  | value_object<StrokeOpts>("StrokeOpts") | 
|  | .field("width",       &StrokeOpts::width) | 
|  | .field("miter_limit", &StrokeOpts::miter_limit) | 
|  | .field("join",        &StrokeOpts::join) | 
|  | .field("cap",         &StrokeOpts::cap); | 
|  |  | 
|  | // Matrix | 
|  | // Allows clients to supply a 1D array of 9 elements and the bindings | 
|  | // will automatically turn it into a 3x3 2D matrix. | 
|  | // e.g. path.transform([0,1,2,3,4,5,6,7,8]) | 
|  | // This is likely simpler for the client than exposing SkMatrix | 
|  | // directly and requiring them to do a lot of .delete(). | 
|  | value_array<SimpleMatrix>("SkMatrix") | 
|  | .element(&SimpleMatrix::scaleX) | 
|  | .element(&SimpleMatrix::skewX) | 
|  | .element(&SimpleMatrix::transX) | 
|  |  | 
|  | .element(&SimpleMatrix::skewY) | 
|  | .element(&SimpleMatrix::scaleY) | 
|  | .element(&SimpleMatrix::transY) | 
|  |  | 
|  | .element(&SimpleMatrix::pers0) | 
|  | .element(&SimpleMatrix::pers1) | 
|  | .element(&SimpleMatrix::pers2); | 
|  |  | 
|  | value_array<SkPoint>("SkPoint") | 
|  | .element(&SkPoint::fX) | 
|  | .element(&SkPoint::fY); | 
|  |  | 
|  | // Not intended for external clients to call directly. | 
|  | // See helper.js for the client-facing implementation. | 
|  | class_<SkCubicMap>("_SkCubicMap") | 
|  | .constructor<SkPoint, SkPoint>() | 
|  |  | 
|  | .function("computeYFromX", &SkCubicMap::computeYFromX) | 
|  | .function("computePtFromT", &SkCubicMap::computeFromT); | 
|  |  | 
|  |  | 
|  | // Test Utils | 
|  | function("SkBits2FloatUnsigned", &SkBits2FloatUnsigned); | 
|  | } |