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