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>
VisitPath(const SkPath & p,VisitFunc && f)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 
ToCmds(const SkPath & path)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.
FromCmds(uintptr_t cptr,int numCmds)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 
NewPath()163 SkPath EMSCRIPTEN_KEEPALIVE NewPath() {
164     return SkPath();
165 }
166 
CopyPath(const SkPath & a)167 SkPath EMSCRIPTEN_KEEPALIVE CopyPath(const SkPath& a) {
168     SkPath copy(a);
169     return copy;
170 }
171 
Equals(const SkPath & a,const SkPath & b)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 
ApplyArcTo(SkPath & p,SkScalar x1,SkScalar y1,SkScalar x2,SkScalar y2,SkScalar radius)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 
ApplyClose(SkPath & p)191 void ApplyClose(SkPath& p) {
192     p.close();
193 }
194 
ApplyConicTo(SkPath & p,SkScalar x1,SkScalar y1,SkScalar x2,SkScalar y2,SkScalar w)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 
ApplyCubicTo(SkPath & p,SkScalar x1,SkScalar y1,SkScalar x2,SkScalar y2,SkScalar x3,SkScalar y3)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 
ApplyLineTo(SkPath & p,SkScalar x,SkScalar y)205 void ApplyLineTo(SkPath& p, SkScalar x, SkScalar y) {
206     p.lineTo(x, y);
207 }
208 
ApplyMoveTo(SkPath & p,SkScalar x,SkScalar y)209 void ApplyMoveTo(SkPath& p, SkScalar x, SkScalar y) {
210     p.moveTo(x, y);
211 }
212 
ApplyQuadTo(SkPath & p,SkScalar x1,SkScalar y1,SkScalar x2,SkScalar y2)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 
ToSVGString(const SkPath & path)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     // UTF8ToString() on the calling side.
230     return emscripten::val(s.c_str());
231 }
232 
233 
FromSVGString(std::string str)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 
ApplySimplify(SkPath & path)246 bool EMSCRIPTEN_KEEPALIVE ApplySimplify(SkPath& path) {
247     return Simplify(path, &path);
248 }
249 
ApplyPathOp(SkPath & pathOne,const SkPath & pathTwo,SkPathOp op)250 bool EMSCRIPTEN_KEEPALIVE ApplyPathOp(SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
251     return Op(pathOne, pathTwo, op, &pathOne);
252 }
253 
MakeFromOp(const SkPath & pathOne,const SkPath & pathTwo,SkPathOp op)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 
ResolveBuilder(SkOpBuilder & builder)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 
ToCanvas(const SkPath & path,emscripten::val ctx)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 
ToPath2D(const SkPath & path)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 // ======================================================================================
ApplyAddRect(SkPath & path,SkScalar x,SkScalar y,SkScalar width,SkScalar height)320 void ApplyAddRect(SkPath& path, SkScalar x, SkScalar y, SkScalar width, SkScalar height) {
321     path.addRect(x, y, x+width, y+height);
322 }
323 
ApplyAddArc(SkPath & path,SkScalar x,SkScalar y,SkScalar radius,SkScalar startAngle,SkScalar endAngle,bool ccw)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 
ApplyEllipse(SkPath & path,SkScalar x,SkScalar y,SkScalar radiusX,SkScalar radiusY,SkScalar rotation,SkScalar startAngle,SkScalar endAngle,bool ccw)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.
ApplyAddPath(SkPath & orig,const SkPath & newPath,SkScalar scaleX,SkScalar skewX,SkScalar transX,SkScalar skewY,SkScalar scaleY,SkScalar transY,SkScalar pers0,SkScalar pers1,SkScalar pers2)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 
GetFillTypeString(const SkPath & path)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 
ApplyDash(SkPath & path,SkScalar on,SkScalar off,SkScalar phase)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 
ApplyTrim(SkPath & path,SkScalar startT,SkScalar stopT,bool isComplement)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 
ApplyStroke(SkPath & path,StrokeOpts opts)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 
toSkMatrix(const SimpleMatrix & sm)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 
ApplyTransform(SkPath & orig,const SimpleMatrix & sm)440 void ApplyTransform(SkPath& orig, const SimpleMatrix& sm) {
441     orig.transform(toSkMatrix(sm));
442 }
443 
ApplyTransform(SkPath & orig,SkScalar scaleX,SkScalar skewX,SkScalar transX,SkScalar skewY,SkScalar scaleY,SkScalar transY,SkScalar pers0,SkScalar pers1,SkScalar pers2)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.
SkBits2FloatUnsigned(uint32_t floatAsBits)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).
EMSCRIPTEN_BINDINGS(skia)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