• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  
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