Home | History | Annotate | Download | only in pathkit
      1 /*
      2  * Copyright 2018 Google LLC
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #include "SkCubicMap.h"
      9 #include "SkDashPathEffect.h"
     10 #include "SkFloatBits.h"
     11 #include "SkFloatingPoint.h"
     12 #include "SkMatrix.h"
     13 #include "SkPaint.h"
     14 #include "SkPaintDefaults.h"
     15 #include "SkParsePath.h"
     16 #include "SkPath.h"
     17 #include "SkPathOps.h"
     18 #include "SkRect.h"
     19 #include "SkString.h"
     20 #include "SkStrokeRec.h"
     21 #include "SkTrimPathEffect.h"
     22 
     23 #include <emscripten/emscripten.h>
     24 #include <emscripten/bind.h>
     25 
     26 using namespace emscripten;
     27 
     28 static const int MOVE = 0;
     29 static const int LINE = 1;
     30 static const int QUAD = 2;
     31 static const int CONIC = 3;
     32 static const int CUBIC = 4;
     33 static const int CLOSE = 5;
     34 
     35 // Just for self-documenting purposes where the main thing being returned is an
     36 // SkPath, but in an error case, something of type null (which is val) could also be
     37 // returned;
     38 using SkPathOrNull = emscripten::val;
     39 // Self-documenting for when we return a string
     40 using JSString = emscripten::val;
     41 using JSArray = emscripten::val;
     42 
     43 // =================================================================================
     44 // Creating/Exporting Paths with cmd arrays
     45 // =================================================================================
     46 
     47 template <typename VisitFunc>
     48 void VisitPath(const SkPath& p, VisitFunc&& f) {
     49     SkPath::RawIter iter(p);
     50     SkPoint pts[4];
     51     SkPath::Verb verb;
     52     while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
     53         f(verb, pts, iter);
     54     }
     55 }
     56 
     57 JSArray EMSCRIPTEN_KEEPALIVE ToCmds(const SkPath& path) {
     58     JSArray cmds = emscripten::val::array();
     59 
     60     VisitPath(path, [&cmds](SkPath::Verb verb, const SkPoint pts[4], SkPath::RawIter iter) {
     61         JSArray cmd = emscripten::val::array();
     62         switch (verb) {
     63         case SkPath::kMove_Verb:
     64             cmd.call<void>("push", MOVE, pts[0].x(), pts[0].y());
     65             break;
     66         case SkPath::kLine_Verb:
     67             cmd.call<void>("push", LINE, pts[1].x(), pts[1].y());
     68             break;
     69         case SkPath::kQuad_Verb:
     70             cmd.call<void>("push", QUAD, pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
     71             break;
     72         case SkPath::kConic_Verb:
     73             cmd.call<void>("push", CONIC,
     74                            pts[1].x(), pts[1].y(),
     75                            pts[2].x(), pts[2].y(), iter.conicWeight());
     76             break;
     77         case SkPath::kCubic_Verb:
     78             cmd.call<void>("push", CUBIC,
     79                            pts[1].x(), pts[1].y(),
     80                            pts[2].x(), pts[2].y(),
     81                            pts[3].x(), pts[3].y());
     82             break;
     83         case SkPath::kClose_Verb:
     84             cmd.call<void>("push", CLOSE);
     85             break;
     86         case SkPath::kDone_Verb:
     87             SkASSERT(false);
     88             break;
     89         }
     90         cmds.call<void>("push", cmd);
     91     });
     92     return cmds;
     93 }
     94 
     95 // This type signature is a mess, but it's necessary. See, we can't use "bind" (EMSCRIPTEN_BINDINGS)
     96 // and pointers to primitive types (Only bound types like SkPoint). We could if we used
     97 // cwrap (see https://becominghuman.ai/passing-and-returning-webassembly-array-parameters-a0f572c65d97)
     98 // but that requires us to stick to C code and, AFAIK, doesn't allow us to return nice things like
     99 // SkPath or SkOpBuilder.
    100 //
    101 // So, basically, if we are using C++ and EMSCRIPTEN_BINDINGS, we can't have primative pointers
    102 // in our function type signatures. (this gives an error message like "Cannot call foo due to unbound
    103 // types Pi, Pf").  But, we can just pretend they are numbers and cast them to be pointers and
    104 // the compiler is happy.
    105 SkPathOrNull EMSCRIPTEN_KEEPALIVE FromCmds(uintptr_t /* float* */ cptr, int numCmds) {
    106     const auto* cmds = reinterpret_cast<const float*>(cptr);
    107     SkPath path;
    108     float x1, y1, x2, y2, x3, y3;
    109 
    110     // if there are not enough arguments, bail with the path we've constructed so far.
    111     #define CHECK_NUM_ARGS(n) \
    112         if ((i + n) > numCmds) { \
    113             SkDebugf("Not enough args to match the verbs. Saw %d commands\n", numCmds); \
    114             return emscripten::val::null(); \
    115         }
    116 
    117     for(int i = 0; i < numCmds;){
    118          switch (sk_float_floor2int(cmds[i++])) {
    119             case MOVE:
    120                 CHECK_NUM_ARGS(2);
    121                 x1 = cmds[i++], y1 = cmds[i++];
    122                 path.moveTo(x1, y1);
    123                 break;
    124             case LINE:
    125                 CHECK_NUM_ARGS(2);
    126                 x1 = cmds[i++], y1 = cmds[i++];
    127                 path.lineTo(x1, y1);
    128                 break;
    129             case QUAD:
    130                 CHECK_NUM_ARGS(4);
    131                 x1 = cmds[i++], y1 = cmds[i++];
    132                 x2 = cmds[i++], y2 = cmds[i++];
    133                 path.quadTo(x1, y1, x2, y2);
    134                 break;
    135             case CONIC:
    136                 CHECK_NUM_ARGS(5);
    137                 x1 = cmds[i++], y1 = cmds[i++];
    138                 x2 = cmds[i++], y2 = cmds[i++];
    139                 x3 = cmds[i++]; // weight
    140                 path.conicTo(x1, y1, x2, y2, x3);
    141                 break;
    142             case CUBIC:
    143                 CHECK_NUM_ARGS(6);
    144                 x1 = cmds[i++], y1 = cmds[i++];
    145                 x2 = cmds[i++], y2 = cmds[i++];
    146                 x3 = cmds[i++], y3 = cmds[i++];
    147                 path.cubicTo(x1, y1, x2, y2, x3, y3);
    148                 break;
    149             case CLOSE:
    150                 path.close();
    151                 break;
    152             default:
    153                 SkDebugf("  path: UNKNOWN command %f, aborting dump...\n", cmds[i-1]);
    154                 return emscripten::val::null();
    155         }
    156     }
    157 
    158     #undef CHECK_NUM_ARGS
    159 
    160     return emscripten::val(path);
    161 }
    162 
    163 SkPath EMSCRIPTEN_KEEPALIVE NewPath() {
    164     return SkPath();
    165 }
    166 
    167 SkPath EMSCRIPTEN_KEEPALIVE CopyPath(const SkPath& a) {
    168     SkPath copy(a);
    169     return copy;
    170 }
    171 
    172 bool EMSCRIPTEN_KEEPALIVE Equals(const SkPath& a, const SkPath& b) {
    173     return a == b;
    174 }
    175 
    176 //========================================================================================
    177 // Path things
    178 //========================================================================================
    179 
    180 // All these Apply* methods are simple wrappers to avoid returning an object.
    181 // The default WASM bindings produce code that will leak if a return value
    182 // isn't assigned to a JS variable and has delete() called on it.
    183 // These Apply methods, combined with the smarter binding code allow for chainable
    184 // commands that don't leak if the return value is ignored (i.e. when used intuitively).
    185 
    186 void ApplyArcTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
    187                 SkScalar radius) {
    188     p.arcTo(x1, y1, x2, y2, radius);
    189 }
    190 
    191 void ApplyClose(SkPath& p) {
    192     p.close();
    193 }
    194 
    195 void ApplyConicTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
    196                   SkScalar w) {
    197     p.conicTo(x1, y1, x2, y2, w);
    198 }
    199 
    200 void ApplyCubicTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
    201                   SkScalar x3, SkScalar y3) {
    202     p.cubicTo(x1, y1, x2, y2, x3, y3);
    203 }
    204 
    205 void ApplyLineTo(SkPath& p, SkScalar x, SkScalar y) {
    206     p.lineTo(x, y);
    207 }
    208 
    209 void ApplyMoveTo(SkPath& p, SkScalar x, SkScalar y) {
    210     p.moveTo(x, y);
    211 }
    212 
    213 void ApplyQuadTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
    214     p.quadTo(x1, y1, x2, y2);
    215 }
    216 
    217 
    218 
    219 //========================================================================================
    220 // SVG things
    221 //========================================================================================
    222 
    223 JSString EMSCRIPTEN_KEEPALIVE ToSVGString(const SkPath& path) {
    224     SkString s;
    225     SkParsePath::ToSVGString(path, &s);
    226     // Wrapping it in val automatically turns it into a JS string.
    227     // Not too sure on performance implications, but is is simpler than
    228     // returning a raw pointer to const char * and then using
    229     // Pointer_stringify() on the calling side.
    230     return emscripten::val(s.c_str());
    231 }
    232 
    233 
    234 SkPathOrNull EMSCRIPTEN_KEEPALIVE FromSVGString(std::string str) {
    235     SkPath path;
    236     if (SkParsePath::FromSVGString(str.c_str(), &path)) {
    237         return emscripten::val(path);
    238     }
    239     return emscripten::val::null();
    240 }
    241 
    242 //========================================================================================
    243 // PATHOP things
    244 //========================================================================================
    245 
    246 bool EMSCRIPTEN_KEEPALIVE ApplySimplify(SkPath& path) {
    247     return Simplify(path, &path);
    248 }
    249 
    250 bool EMSCRIPTEN_KEEPALIVE ApplyPathOp(SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
    251     return Op(pathOne, pathTwo, op, &pathOne);
    252 }
    253 
    254 SkPathOrNull EMSCRIPTEN_KEEPALIVE MakeFromOp(const SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
    255     SkPath out;
    256     if (Op(pathOne, pathTwo, op, &out)) {
    257         return emscripten::val(out);
    258     }
    259     return emscripten::val::null();
    260 }
    261 
    262 SkPathOrNull EMSCRIPTEN_KEEPALIVE ResolveBuilder(SkOpBuilder& builder) {
    263     SkPath path;
    264     if (builder.resolve(&path)) {
    265         return emscripten::val(path);
    266     }
    267     return emscripten::val::null();
    268 }
    269 
    270 //========================================================================================
    271 // Canvas things
    272 //========================================================================================
    273 
    274 void EMSCRIPTEN_KEEPALIVE ToCanvas(const SkPath& path, emscripten::val /* Path2D or Canvas*/ ctx) {
    275     SkPath::Iter iter(path, false);
    276     SkPoint pts[4];
    277     SkPath::Verb verb;
    278     while ((verb = iter.next(pts, false)) != SkPath::kDone_Verb) {
    279         switch (verb) {
    280             case SkPath::kMove_Verb:
    281                 ctx.call<void>("moveTo", pts[0].x(), pts[0].y());
    282                 break;
    283             case SkPath::kLine_Verb:
    284                 ctx.call<void>("lineTo", pts[1].x(), pts[1].y());
    285                 break;
    286             case SkPath::kQuad_Verb:
    287                 ctx.call<void>("quadraticCurveTo", pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
    288                 break;
    289             case SkPath::kConic_Verb:
    290                 SkPoint quads[5];
    291                 // approximate with 2^1=2 quads.
    292                 SkPath::ConvertConicToQuads(pts[0], pts[1], pts[2], iter.conicWeight(), quads, 1);
    293                 ctx.call<void>("quadraticCurveTo", quads[1].x(), quads[1].y(), quads[2].x(), quads[2].y());
    294                 ctx.call<void>("quadraticCurveTo", quads[3].x(), quads[3].y(), quads[4].x(), quads[4].y());
    295                 break;
    296             case SkPath::kCubic_Verb:
    297                 ctx.call<void>("bezierCurveTo", pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y(),
    298                                                    pts[3].x(), pts[3].y());
    299                 break;
    300             case SkPath::kClose_Verb:
    301                 ctx.call<void>("closePath");
    302                 break;
    303             case SkPath::kDone_Verb:
    304                 break;
    305         }
    306     }
    307 }
    308 
    309 emscripten::val JSPath2D = emscripten::val::global("Path2D");
    310 
    311 emscripten::val EMSCRIPTEN_KEEPALIVE ToPath2D(const SkPath& path) {
    312     emscripten::val retVal = JSPath2D.new_();
    313     ToCanvas(path, retVal);
    314     return retVal;
    315 }
    316 
    317 // ======================================================================================
    318 // Path2D API things
    319 // ======================================================================================
    320 void ApplyAddRect(SkPath& path, SkScalar x, SkScalar y, SkScalar width, SkScalar height) {
    321     path.addRect(x, y, x+width, y+height);
    322 }
    323 
    324 void ApplyAddArc(SkPath& path, SkScalar x, SkScalar y, SkScalar radius,
    325               SkScalar startAngle, SkScalar endAngle, bool ccw) {
    326     SkPath temp;
    327     SkRect bounds = SkRect::MakeLTRB(x-radius, y-radius, x+radius, y+radius);
    328     const auto sweep = SkRadiansToDegrees(endAngle - startAngle) - 360 * ccw;
    329     temp.addArc(bounds, SkRadiansToDegrees(startAngle), sweep);
    330     path.addPath(temp, SkPath::kExtend_AddPathMode);
    331 }
    332 
    333 void ApplyEllipse(SkPath& path, SkScalar x, SkScalar y, SkScalar radiusX, SkScalar radiusY,
    334                      SkScalar rotation, SkScalar startAngle, SkScalar endAngle, bool ccw) {
    335     // This is easiest to do by making a new path and then extending the current path
    336     // (this properly catches the cases of if there's a moveTo before this call or not).
    337     SkRect bounds = SkRect::MakeLTRB(x-radiusX, y-radiusY, x+radiusX, y+radiusY);
    338     SkPath temp;
    339     const auto sweep = SkRadiansToDegrees(endAngle - startAngle) - (360 * ccw);
    340     temp.addArc(bounds, SkRadiansToDegrees(startAngle), sweep);
    341 
    342     SkMatrix m;
    343     m.setRotate(SkRadiansToDegrees(rotation), x, y);
    344     path.addPath(temp, m, SkPath::kExtend_AddPathMode);
    345 }
    346 
    347 // Allows for full matix control.
    348 void ApplyAddPath(SkPath& orig, const SkPath& newPath,
    349                    SkScalar scaleX, SkScalar skewX,  SkScalar transX,
    350                    SkScalar skewY,  SkScalar scaleY, SkScalar transY,
    351                    SkScalar pers0, SkScalar pers1, SkScalar pers2) {
    352     SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX,
    353                                    skewY , scaleY, transY,
    354                                    pers0 , pers1 , pers2);
    355     orig.addPath(newPath, m);
    356 }
    357 
    358 JSString GetFillTypeString(const SkPath& path) {
    359     if (path.getFillType() == SkPath::FillType::kWinding_FillType) {
    360         return emscripten::val("nonzero");
    361     } else if (path.getFillType() == SkPath::FillType::kEvenOdd_FillType) {
    362         return emscripten::val("evenodd");
    363     } else {
    364         SkDebugf("warning: can't translate inverted filltype to HTML Canvas\n");
    365         return emscripten::val("nonzero"); //Use default
    366     }
    367 }
    368 
    369 //========================================================================================
    370 // Path Effects
    371 //========================================================================================
    372 
    373 bool ApplyDash(SkPath& path, SkScalar on, SkScalar off, SkScalar phase) {
    374     SkScalar intervals[] = { on, off };
    375     auto pe = SkDashPathEffect::Make(intervals, 2, phase);
    376     if (!pe) {
    377         SkDebugf("Invalid args to dash()\n");
    378         return false;
    379     }
    380     SkStrokeRec rec(SkStrokeRec::InitStyle::kHairline_InitStyle);
    381     if (pe->filterPath(&path, path, &rec, nullptr)) {
    382         return true;
    383     }
    384     SkDebugf("Could not make dashed path\n");
    385     return false;
    386 }
    387 
    388 bool ApplyTrim(SkPath& path, SkScalar startT, SkScalar stopT, bool isComplement) {
    389     auto mode = isComplement ? SkTrimPathEffect::Mode::kInverted : SkTrimPathEffect::Mode::kNormal;
    390     auto pe = SkTrimPathEffect::Make(startT, stopT, mode);
    391     if (!pe) {
    392         SkDebugf("Invalid args to trim(): startT and stopT must be in [0,1]\n");
    393         return false;
    394     }
    395     SkStrokeRec rec(SkStrokeRec::InitStyle::kHairline_InitStyle);
    396     if (pe->filterPath(&path, path, &rec, nullptr)) {
    397         return true;
    398     }
    399     SkDebugf("Could not trim path\n");
    400     return false;
    401 }
    402 
    403 struct StrokeOpts {
    404     // Default values are set in chaining.js which allows clients
    405     // to set any number of them. Otherwise, the binding code complains if
    406     // any are omitted.
    407     SkScalar width;
    408     SkScalar miter_limit;
    409     SkPaint::Join join;
    410     SkPaint::Cap cap;
    411 };
    412 
    413 bool ApplyStroke(SkPath& path, StrokeOpts opts) {
    414     SkPaint p;
    415     p.setStyle(SkPaint::kStroke_Style);
    416     p.setStrokeCap(opts.cap);
    417     p.setStrokeJoin(opts.join);
    418     p.setStrokeWidth(opts.width);
    419     p.setStrokeMiter(opts.miter_limit);
    420 
    421     return p.getFillPath(path, &path);
    422 }
    423 
    424 //========================================================================================
    425 // Matrix things
    426 //========================================================================================
    427 
    428 struct SimpleMatrix {
    429     SkScalar scaleX, skewX,  transX;
    430     SkScalar skewY,  scaleY, transY;
    431     SkScalar pers0,  pers1,  pers2;
    432 };
    433 
    434 SkMatrix toSkMatrix(const SimpleMatrix& sm) {
    435     return SkMatrix::MakeAll(sm.scaleX, sm.skewX , sm.transX,
    436                              sm.skewY , sm.scaleY, sm.transY,
    437                              sm.pers0 , sm.pers1 , sm.pers2);
    438 }
    439 
    440 void ApplyTransform(SkPath& orig, const SimpleMatrix& sm) {
    441     orig.transform(toSkMatrix(sm));
    442 }
    443 
    444 void ApplyTransform(SkPath& orig,
    445                     SkScalar scaleX, SkScalar skewX,  SkScalar transX,
    446                     SkScalar skewY,  SkScalar scaleY, SkScalar transY,
    447                     SkScalar pers0, SkScalar pers1, SkScalar pers2) {
    448     SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX,
    449                                    skewY , scaleY, transY,
    450                                    pers0 , pers1 , pers2);
    451     orig.transform(m);
    452 }
    453 
    454 //========================================================================================
    455 // Testing things
    456 //========================================================================================
    457 
    458 // The use case for this is on the JS side is something like:
    459 //     PathKit.SkBits2FloatUnsigned(parseInt("0xc0a00000"))
    460 // to have precise float values for tests. In the C++ tests, we can use SkBits2Float because
    461 // it takes int32_t, but the JS parseInt basically returns an unsigned int. So, we add in
    462 // this helper which casts for us on the way to SkBits2Float.
    463 float SkBits2FloatUnsigned(uint32_t floatAsBits) {
    464     return SkBits2Float((int32_t) floatAsBits);
    465 }
    466 
    467 // Binds the classes to the JS
    468 //
    469 // See https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/embind.html#non-member-functions-on-the-javascript-prototype
    470 // for more on binding non-member functions to the JS object, allowing us to rewire
    471 // various functions.  That is, we can make the SkPath we expose appear to have methods
    472 // that the original SkPath does not, like rect(x, y, width, height) and toPath2D().
    473 //
    474 // An important detail for binding non-member functions is that the first argument
    475 // must be SkPath& (the reference part is very important).
    476 //
    477 // Note that we can't expose default or optional arguments, but we can have multiple
    478 // declarations of the same function that take different amounts of arguments.
    479 // For example, see _transform
    480 // Additionally, we are perfectly happy to handle default arguments and function
    481 // overloads in the JS glue code (see chaining.js::addPath() for an example).
    482 EMSCRIPTEN_BINDINGS(skia) {
    483     class_<SkPath>("SkPath")
    484         .constructor<>()
    485         .constructor<const SkPath&>()
    486 
    487         // Path2D API
    488         .function("_addPath", &ApplyAddPath)
    489         // 3 additional overloads of addPath are handled in JS bindings
    490         .function("_arc", &ApplyAddArc)
    491         .function("_arcTo", &ApplyArcTo)
    492         //"bezierCurveTo" alias handled in JS bindings
    493         .function("_close", &ApplyClose)
    494         //"closePath" alias handled in JS bindings
    495         .function("_conicTo", &ApplyConicTo)
    496         .function("_cubicTo", &ApplyCubicTo)
    497 
    498         .function("_ellipse", &ApplyEllipse)
    499         .function("_lineTo", &ApplyLineTo)
    500         .function("_moveTo", &ApplyMoveTo)
    501         // "quadraticCurveTo" alias handled in JS bindings
    502         .function("_quadTo", &ApplyQuadTo)
    503         .function("_rect", &ApplyAddRect)
    504 
    505         // Extra features
    506         .function("setFillType", &SkPath::setFillType)
    507         .function("getFillType", &SkPath::getFillType)
    508         .function("getFillTypeString", &GetFillTypeString)
    509         .function("getBounds", &SkPath::getBounds)
    510         .function("computeTightBounds", &SkPath::computeTightBounds)
    511         .function("equals", &Equals)
    512         .function("copy", &CopyPath)
    513 
    514         // PathEffects
    515         .function("_dash", &ApplyDash)
    516         .function("_trim", &ApplyTrim)
    517         .function("_stroke", &ApplyStroke)
    518 
    519         // Matrix
    520         .function("_transform", select_overload<void(SkPath& orig, const SimpleMatrix& sm)>(&ApplyTransform))
    521         .function("_transform", select_overload<void(SkPath& orig, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&ApplyTransform))
    522 
    523         // PathOps
    524         .function("_simplify", &ApplySimplify)
    525         .function("_op", &ApplyPathOp)
    526 
    527         // Exporting
    528         .function("toCmds", &ToCmds)
    529         .function("toPath2D", &ToPath2D)
    530         .function("toCanvas", &ToCanvas)
    531         .function("toSVGString", &ToSVGString)
    532 
    533 #ifdef PATHKIT_TESTING
    534         .function("dump", select_overload<void() const>(&SkPath::dump))
    535         .function("dumpHex", select_overload<void() const>(&SkPath::dumpHex))
    536 #endif
    537         ;
    538 
    539     class_<SkOpBuilder>("SkOpBuilder")
    540         .constructor<>()
    541 
    542         .function("add", &SkOpBuilder::add)
    543         .function("make", &ResolveBuilder)
    544         .function("resolve", &ResolveBuilder);
    545 
    546     // Without these function() bindings, the function would be exposed but oblivious to
    547     // our types (e.g. SkPath)
    548 
    549     // Import
    550     function("FromSVGString", &FromSVGString);
    551     function("NewPath", &NewPath);
    552     function("NewPath", &CopyPath);
    553     // FromCmds is defined in helper.js to make use of TypedArrays transparent.
    554     function("_FromCmds", &FromCmds);
    555     // Path2D is opaque, so we can't read in from it.
    556 
    557     // PathOps
    558     function("MakeFromOp", &MakeFromOp);
    559 
    560     enum_<SkPathOp>("PathOp")
    561         .value("DIFFERENCE",         SkPathOp::kDifference_SkPathOp)
    562         .value("INTERSECT",          SkPathOp::kIntersect_SkPathOp)
    563         .value("UNION",              SkPathOp::kUnion_SkPathOp)
    564         .value("XOR",                SkPathOp::kXOR_SkPathOp)
    565         .value("REVERSE_DIFFERENCE", SkPathOp::kReverseDifference_SkPathOp);
    566 
    567     enum_<SkPath::FillType>("FillType")
    568         .value("WINDING",            SkPath::FillType::kWinding_FillType)
    569         .value("EVENODD",            SkPath::FillType::kEvenOdd_FillType)
    570         .value("INVERSE_WINDING",    SkPath::FillType::kInverseWinding_FillType)
    571         .value("INVERSE_EVENODD",    SkPath::FillType::kInverseEvenOdd_FillType);
    572 
    573     constant("MOVE_VERB",  MOVE);
    574     constant("LINE_VERB",  LINE);
    575     constant("QUAD_VERB",  QUAD);
    576     constant("CONIC_VERB", CONIC);
    577     constant("CUBIC_VERB", CUBIC);
    578     constant("CLOSE_VERB", CLOSE);
    579 
    580     // A value object is much simpler than a class - it is returned as a JS
    581     // object and does not require delete().
    582     // https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/embind.html#value-types
    583     value_object<SkRect>("SkRect")
    584         .field("fLeft",   &SkRect::fLeft)
    585         .field("fTop",    &SkRect::fTop)
    586         .field("fRight",  &SkRect::fRight)
    587         .field("fBottom", &SkRect::fBottom);
    588 
    589     function("LTRBRect", &SkRect::MakeLTRB);
    590 
    591     // Stroke
    592     enum_<SkPaint::Join>("StrokeJoin")
    593         .value("MITER", SkPaint::Join::kMiter_Join)
    594         .value("ROUND", SkPaint::Join::kRound_Join)
    595         .value("BEVEL", SkPaint::Join::kBevel_Join);
    596 
    597     enum_<SkPaint::Cap>("StrokeCap")
    598         .value("BUTT",   SkPaint::Cap::kButt_Cap)
    599         .value("ROUND",  SkPaint::Cap::kRound_Cap)
    600         .value("SQUARE", SkPaint::Cap::kSquare_Cap);
    601 
    602     value_object<StrokeOpts>("StrokeOpts")
    603         .field("width",       &StrokeOpts::width)
    604         .field("miter_limit", &StrokeOpts::miter_limit)
    605         .field("join",        &StrokeOpts::join)
    606         .field("cap",         &StrokeOpts::cap);
    607 
    608     // Matrix
    609     // Allows clients to supply a 1D array of 9 elements and the bindings
    610     // will automatically turn it into a 3x3 2D matrix.
    611     // e.g. path.transform([0,1,2,3,4,5,6,7,8])
    612     // This is likely simpler for the client than exposing SkMatrix
    613     // directly and requiring them to do a lot of .delete().
    614     value_array<SimpleMatrix>("SkMatrix")
    615         .element(&SimpleMatrix::scaleX)
    616         .element(&SimpleMatrix::skewX)
    617         .element(&SimpleMatrix::transX)
    618 
    619         .element(&SimpleMatrix::skewY)
    620         .element(&SimpleMatrix::scaleY)
    621         .element(&SimpleMatrix::transY)
    622 
    623         .element(&SimpleMatrix::pers0)
    624         .element(&SimpleMatrix::pers1)
    625         .element(&SimpleMatrix::pers2);
    626 
    627     value_array<SkPoint>("SkPoint")
    628         .element(&SkPoint::fX)
    629         .element(&SkPoint::fY);
    630 
    631     // Not intended for external clients to call directly.
    632     // See helper.js for the client-facing implementation.
    633     class_<SkCubicMap>("_SkCubicMap")
    634         .constructor<SkPoint, SkPoint>()
    635 
    636         .function("computeYFromX", &SkCubicMap::computeYFromX)
    637         .function("computePtFromT", &SkCubicMap::computeFromT);
    638 
    639 
    640     // Test Utils
    641     function("SkBits2FloatUnsigned", &SkBits2FloatUnsigned);
    642 }
    643