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