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