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