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