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