| /* | |
| * 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 "src/core/SkPathPriv.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 | |
| // ================================================================================= | |
| JSArray EMSCRIPTEN_KEEPALIVE ToCmds(const SkPath& path) { | |
| JSArray cmds = emscripten::val::array(); | |
| for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) { | |
| JSArray cmd = emscripten::val::array(); | |
| switch (verb) { | |
| case SkPathVerb::kMove: | |
| cmd.call<void>("push", MOVE, pts[0].x(), pts[0].y()); | |
| break; | |
| case SkPathVerb::kLine: | |
| cmd.call<void>("push", LINE, pts[1].x(), pts[1].y()); | |
| break; | |
| case SkPathVerb::kQuad: | |
| cmd.call<void>("push", QUAD, pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y()); | |
| break; | |
| case SkPathVerb::kConic: | |
| cmd.call<void>("push", CONIC, | |
| pts[1].x(), pts[1].y(), | |
| pts[2].x(), pts[2].y(), *w); | |
| break; | |
| case SkPathVerb::kCubic: | |
| 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 SkPathVerb::kClose: | |
| cmd.call<void>("push", CLOSE); | |
| 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)) != 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() == SkPathFillType::kWinding) { | |
| return emscripten::val("nonzero"); | |
| } else if (path.getFillType() == SkPathFillType::kEvenOdd) { | |
| 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", select_overload<void(SkPathFillType)>(&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_<SkPathFillType>("FillType") | |
| .value("WINDING", SkPathFillType::kWinding) | |
| .value("EVENODD", SkPathFillType::kEvenOdd) | |
| .value("INVERSE_WINDING", SkPathFillType::kInverseWinding) | |
| .value("INVERSE_EVENODD", SkPathFillType::kInverseEvenOdd); | |
| 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); | |
| } |