blob: c8d821fba510fba88500b90449684a0e5b971d5d [file] [log] [blame]
Kevin Lubick22647d02018-07-06 14:31:23 -04001/*
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
Kevin Lubick217056c2018-09-20 17:39:31 -04008#include "SkCubicMap.h"
Kevin Lubick97d6d982018-08-10 15:53:16 -04009#include "SkDashPathEffect.h"
Kevin Lubick92eaa3c2018-07-16 21:00:52 -040010#include "SkFloatBits.h"
Kevin Lubick22647d02018-07-06 14:31:23 -040011#include "SkFloatingPoint.h"
Kevin Lubick641bf872018-08-06 14:49:39 -040012#include "SkMatrix.h"
Kevin Lubick97d6d982018-08-10 15:53:16 -040013#include "SkPaint.h"
Kevin Lubick217056c2018-09-20 17:39:31 -040014#include "SkPaintDefaults.h"
Kevin Lubick22647d02018-07-06 14:31:23 -040015#include "SkParsePath.h"
16#include "SkPath.h"
17#include "SkPathOps.h"
Kevin Lubick641bf872018-08-06 14:49:39 -040018#include "SkRect.h"
Kevin Lubick22647d02018-07-06 14:31:23 -040019#include "SkString.h"
Kevin Lubick217056c2018-09-20 17:39:31 -040020#include "SkStrokeRec.h"
Kevin Lubick97d6d982018-08-10 15:53:16 -040021#include "SkTrimPathEffect.h"
Kevin Lubick22647d02018-07-06 14:31:23 -040022
23#include <emscripten/emscripten.h>
24#include <emscripten/bind.h>
25
26using namespace emscripten;
27
28static const int MOVE = 0;
29static const int LINE = 1;
30static const int QUAD = 2;
Kevin Lubick97d6d982018-08-10 15:53:16 -040031static const int CONIC = 3;
Kevin Lubick22647d02018-07-06 14:31:23 -040032static const int CUBIC = 4;
33static const int CLOSE = 5;
34
Kevin Lubick5f0e3a12018-08-07 11:30:12 -040035// Just for self-documenting purposes where the main thing being returned is an
Kevin Lubick97d6d982018-08-10 15:53:16 -040036// SkPath, but in an error case, something of type null (which is val) could also be
Kevin Lubick5f0e3a12018-08-07 11:30:12 -040037// returned;
Kevin Lubick97d6d982018-08-10 15:53:16 -040038using SkPathOrNull = emscripten::val;
39// Self-documenting for when we return a string
40using JSString = emscripten::val;
Kevin Lubickb5ae3b52018-11-03 07:51:19 -040041using JSArray = emscripten::val;
Kevin Lubick5f0e3a12018-08-07 11:30:12 -040042
Kevin Lubick22647d02018-07-06 14:31:23 -040043// =================================================================================
Kevin Lubick641bf872018-08-06 14:49:39 -040044// Creating/Exporting Paths with cmd arrays
Kevin Lubick22647d02018-07-06 14:31:23 -040045// =================================================================================
46
Florin Malitae1824da2018-07-12 10:33:39 -040047template <typename VisitFunc>
48void VisitPath(const SkPath& p, VisitFunc&& f) {
49 SkPath::RawIter iter(p);
Kevin Lubick22647d02018-07-06 14:31:23 -040050 SkPoint pts[4];
51 SkPath::Verb verb;
Florin Malitae1824da2018-07-12 10:33:39 -040052 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
Kevin Lubick641bf872018-08-06 14:49:39 -040053 f(verb, pts, iter);
Kevin Lubick22647d02018-07-06 14:31:23 -040054 }
55}
56
Kevin Lubickb5ae3b52018-11-03 07:51:19 -040057JSArray EMSCRIPTEN_KEEPALIVE ToCmds(const SkPath& path) {
58 JSArray cmds = emscripten::val::array();
Kevin Lubick22647d02018-07-06 14:31:23 -040059
Kevin Lubick641bf872018-08-06 14:49:39 -040060 VisitPath(path, [&cmds](SkPath::Verb verb, const SkPoint pts[4], SkPath::RawIter iter) {
Kevin Lubickb5ae3b52018-11-03 07:51:19 -040061 JSArray cmd = emscripten::val::array();
Kevin Lubick22647d02018-07-06 14:31:23 -040062 switch (verb) {
Florin Malitae1824da2018-07-12 10:33:39 -040063 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:
Kevin Lubick97d6d982018-08-10 15:53:16 -040073 cmd.call<void>("push", CONIC,
74 pts[1].x(), pts[1].y(),
75 pts[2].x(), pts[2].y(), iter.conicWeight());
Florin Malitae1824da2018-07-12 10:33:39 -040076 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;
Kevin Lubick22647d02018-07-06 14:31:23 -040089 }
90 cmds.call<void>("push", cmd);
Florin Malitae1824da2018-07-12 10:33:39 -040091 });
Kevin Lubick22647d02018-07-06 14:31:23 -040092 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.
Kevin Lubick97d6d982018-08-10 15:53:16 -0400105SkPathOrNull EMSCRIPTEN_KEEPALIVE FromCmds(uintptr_t /* float* */ cptr, int numCmds) {
Florin Malitae1824da2018-07-12 10:33:39 -0400106 const auto* cmds = reinterpret_cast<const float*>(cptr);
Kevin Lubick22647d02018-07-06 14:31:23 -0400107 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); \
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400114 return emscripten::val::null(); \
Kevin Lubick22647d02018-07-06 14:31:23 -0400115 }
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;
Kevin Lubickc623af22018-08-17 14:42:53 -0400135 case CONIC:
Kevin Lubickc6c48aa2018-08-17 15:00:43 -0400136 CHECK_NUM_ARGS(5);
Kevin Lubickc623af22018-08-17 14:42:53 -0400137 x1 = cmds[i++], y1 = cmds[i++];
138 x2 = cmds[i++], y2 = cmds[i++];
Kevin Lubickd9936482018-08-24 10:44:16 -0400139 x3 = cmds[i++]; // weight
Kevin Lubickc623af22018-08-17 14:42:53 -0400140 path.conicTo(x1, y1, x2, y2, x3);
141 break;
Kevin Lubick22647d02018-07-06 14:31:23 -0400142 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]);
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400154 return emscripten::val::null();
Kevin Lubick22647d02018-07-06 14:31:23 -0400155 }
156 }
157
158 #undef CHECK_NUM_ARGS
159
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400160 return emscripten::val(path);
Kevin Lubick22647d02018-07-06 14:31:23 -0400161}
162
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400163SkPath EMSCRIPTEN_KEEPALIVE NewPath() {
164 return SkPath();
165}
166
Kevin Lubick084d9962018-08-15 13:28:27 -0400167SkPath EMSCRIPTEN_KEEPALIVE CopyPath(const SkPath& a) {
168 SkPath copy(a);
169 return copy;
170}
171
172bool EMSCRIPTEN_KEEPALIVE Equals(const SkPath& a, const SkPath& b) {
173 return a == b;
174}
175
Kevin Lubick22647d02018-07-06 14:31:23 -0400176//========================================================================================
Kevin Lubick11194ab2018-08-17 13:52:56 -0400177// 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
186void ApplyArcTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
187 SkScalar radius) {
188 p.arcTo(x1, y1, x2, y2, radius);
189}
190
191void ApplyClose(SkPath& p) {
192 p.close();
193}
194
195void ApplyConicTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
196 SkScalar w) {
197 p.conicTo(x1, y1, x2, y2, w);
198}
199
200void 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
205void ApplyLineTo(SkPath& p, SkScalar x, SkScalar y) {
206 p.lineTo(x, y);
207}
208
209void ApplyMoveTo(SkPath& p, SkScalar x, SkScalar y) {
210 p.moveTo(x, y);
211}
212
213void ApplyQuadTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
214 p.quadTo(x1, y1, x2, y2);
215}
216
217
218
219//========================================================================================
Kevin Lubick641bf872018-08-06 14:49:39 -0400220// SVG things
Kevin Lubick22647d02018-07-06 14:31:23 -0400221//========================================================================================
222
Kevin Lubick97d6d982018-08-10 15:53:16 -0400223JSString EMSCRIPTEN_KEEPALIVE ToSVGString(const SkPath& path) {
Kevin Lubick22647d02018-07-06 14:31:23 -0400224 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 // Pointer_stringify() on the calling side.
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400230 return emscripten::val(s.c_str());
Kevin Lubick22647d02018-07-06 14:31:23 -0400231}
232
233
Kevin Lubick97d6d982018-08-10 15:53:16 -0400234SkPathOrNull EMSCRIPTEN_KEEPALIVE FromSVGString(std::string str) {
Kevin Lubick22647d02018-07-06 14:31:23 -0400235 SkPath path;
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400236 if (SkParsePath::FromSVGString(str.c_str(), &path)) {
237 return emscripten::val(path);
238 }
239 return emscripten::val::null();
Kevin Lubick22647d02018-07-06 14:31:23 -0400240}
241
242//========================================================================================
Kevin Lubick641bf872018-08-06 14:49:39 -0400243// PATHOP things
Kevin Lubick22647d02018-07-06 14:31:23 -0400244//========================================================================================
245
Kevin Lubick11194ab2018-08-17 13:52:56 -0400246bool EMSCRIPTEN_KEEPALIVE ApplySimplify(SkPath& path) {
247 return Simplify(path, &path);
Kevin Lubick22647d02018-07-06 14:31:23 -0400248}
249
Kevin Lubick11194ab2018-08-17 13:52:56 -0400250bool EMSCRIPTEN_KEEPALIVE ApplyPathOp(SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
251 return Op(pathOne, pathTwo, op, &pathOne);
Kevin Lubick22647d02018-07-06 14:31:23 -0400252}
253
Kevin Lubickd9936482018-08-24 10:44:16 -0400254SkPathOrNull 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
Kevin Lubick97d6d982018-08-10 15:53:16 -0400262SkPathOrNull EMSCRIPTEN_KEEPALIVE ResolveBuilder(SkOpBuilder& builder) {
Kevin Lubick22647d02018-07-06 14:31:23 -0400263 SkPath path;
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400264 if (builder.resolve(&path)) {
265 return emscripten::val(path);
266 }
267 return emscripten::val::null();
Kevin Lubick22647d02018-07-06 14:31:23 -0400268}
269
270//========================================================================================
Kevin Lubick641bf872018-08-06 14:49:39 -0400271// Canvas things
Kevin Lubick22647d02018-07-06 14:31:23 -0400272//========================================================================================
273
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400274void EMSCRIPTEN_KEEPALIVE ToCanvas(const SkPath& path, emscripten::val /* Path2D or Canvas*/ ctx) {
Kevin Lubick22647d02018-07-06 14:31:23 -0400275 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:
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400281 ctx.call<void>("moveTo", pts[0].x(), pts[0].y());
Kevin Lubick22647d02018-07-06 14:31:23 -0400282 break;
283 case SkPath::kLine_Verb:
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400284 ctx.call<void>("lineTo", pts[1].x(), pts[1].y());
Kevin Lubick22647d02018-07-06 14:31:23 -0400285 break;
286 case SkPath::kQuad_Verb:
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400287 ctx.call<void>("quadraticCurveTo", pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
Kevin Lubick22647d02018-07-06 14:31:23 -0400288 break;
289 case SkPath::kConic_Verb:
Kevin Lubick641bf872018-08-06 14:49:39 -0400290 SkPoint quads[5];
291 // approximate with 2^1=2 quads.
292 SkPath::ConvertConicToQuads(pts[0], pts[1], pts[2], iter.conicWeight(), quads, 1);
Kevin Lubick641bf872018-08-06 14:49:39 -0400293 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());
Kevin Lubick22647d02018-07-06 14:31:23 -0400295 break;
296 case SkPath::kCubic_Verb:
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400297 ctx.call<void>("bezierCurveTo", pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y(),
Kevin Lubick22647d02018-07-06 14:31:23 -0400298 pts[3].x(), pts[3].y());
299 break;
300 case SkPath::kClose_Verb:
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400301 ctx.call<void>("closePath");
Kevin Lubick22647d02018-07-06 14:31:23 -0400302 break;
303 case SkPath::kDone_Verb:
304 break;
305 }
306 }
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400307}
308
309emscripten::val JSPath2D = emscripten::val::global("Path2D");
310
Kevin Lubick641bf872018-08-06 14:49:39 -0400311emscripten::val EMSCRIPTEN_KEEPALIVE ToPath2D(const SkPath& path) {
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400312 emscripten::val retVal = JSPath2D.new_();
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400313 ToCanvas(path, retVal);
Kevin Lubick22647d02018-07-06 14:31:23 -0400314 return retVal;
315}
316
Kevin Lubick641bf872018-08-06 14:49:39 -0400317// ======================================================================================
318// Path2D API things
319// ======================================================================================
Kevin Lubick11194ab2018-08-17 13:52:56 -0400320void ApplyAddRect(SkPath& path, SkScalar x, SkScalar y, SkScalar width, SkScalar height) {
Kevin Lubick641bf872018-08-06 14:49:39 -0400321 path.addRect(x, y, x+width, y+height);
322}
323
Kevin Lubick11194ab2018-08-17 13:52:56 -0400324void ApplyAddArc(SkPath& path, SkScalar x, SkScalar y, SkScalar radius,
325 SkScalar startAngle, SkScalar endAngle, bool ccw) {
Kevin Lubick641bf872018-08-06 14:49:39 -0400326 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
Kevin Lubick11194ab2018-08-17 13:52:56 -0400333void ApplyEllipse(SkPath& path, SkScalar x, SkScalar y, SkScalar radiusX, SkScalar radiusY,
Kevin Lubick641bf872018-08-06 14:49:39 -0400334 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
Kevin Lubick641bf872018-08-06 14:49:39 -0400347// Allows for full matix control.
Kevin Lubick11194ab2018-08-17 13:52:56 -0400348void ApplyAddPath(SkPath& orig, const SkPath& newPath,
Kevin Lubick641bf872018-08-06 14:49:39 -0400349 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
Kevin Lubick084d9962018-08-15 13:28:27 -0400358JSString GetFillTypeString(const SkPath& path) {
Kevin Lubick97d6d982018-08-10 15:53:16 -0400359 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
Kevin Lubick11194ab2018-08-17 13:52:56 -0400373bool ApplyDash(SkPath& path, SkScalar on, SkScalar off, SkScalar phase) {
Kevin Lubick97d6d982018-08-10 15:53:16 -0400374 SkScalar intervals[] = { on, off };
375 auto pe = SkDashPathEffect::Make(intervals, 2, phase);
376 if (!pe) {
377 SkDebugf("Invalid args to dash()\n");
Kevin Lubick11194ab2018-08-17 13:52:56 -0400378 return false;
Kevin Lubick97d6d982018-08-10 15:53:16 -0400379 }
Kevin Lubick11194ab2018-08-17 13:52:56 -0400380 SkStrokeRec rec(SkStrokeRec::InitStyle::kHairline_InitStyle);
381 if (pe->filterPath(&path, path, &rec, nullptr)) {
382 return true;
Kevin Lubick97d6d982018-08-10 15:53:16 -0400383 }
384 SkDebugf("Could not make dashed path\n");
Kevin Lubick11194ab2018-08-17 13:52:56 -0400385 return false;
Kevin Lubick97d6d982018-08-10 15:53:16 -0400386}
387
Kevin Lubick11194ab2018-08-17 13:52:56 -0400388bool ApplyTrim(SkPath& path, SkScalar startT, SkScalar stopT, bool isComplement) {
Kevin Lubick97d6d982018-08-10 15:53:16 -0400389 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");
Kevin Lubick11194ab2018-08-17 13:52:56 -0400393 return false;
Kevin Lubick97d6d982018-08-10 15:53:16 -0400394 }
Kevin Lubick11194ab2018-08-17 13:52:56 -0400395 SkStrokeRec rec(SkStrokeRec::InitStyle::kHairline_InitStyle);
396 if (pe->filterPath(&path, path, &rec, nullptr)) {
397 return true;
Kevin Lubick97d6d982018-08-10 15:53:16 -0400398 }
399 SkDebugf("Could not trim path\n");
Kevin Lubick11194ab2018-08-17 13:52:56 -0400400 return false;
Kevin Lubick97d6d982018-08-10 15:53:16 -0400401}
402
Kevin Lubick11194ab2018-08-17 13:52:56 -0400403struct 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};
Kevin Lubick97d6d982018-08-10 15:53:16 -0400412
Kevin Lubick11194ab2018-08-17 13:52:56 -0400413bool ApplyStroke(SkPath& path, StrokeOpts opts) {
Kevin Lubick97d6d982018-08-10 15:53:16 -0400414 SkPaint p;
415 p.setStyle(SkPaint::kStroke_Style);
Kevin Lubick11194ab2018-08-17 13:52:56 -0400416 p.setStrokeCap(opts.cap);
417 p.setStrokeJoin(opts.join);
418 p.setStrokeWidth(opts.width);
419 p.setStrokeMiter(opts.miter_limit);
Kevin Lubick97d6d982018-08-10 15:53:16 -0400420
Kevin Lubick11194ab2018-08-17 13:52:56 -0400421 return p.getFillPath(path, &path);
Kevin Lubick97d6d982018-08-10 15:53:16 -0400422}
423
Kevin Lubick92eaa3c2018-07-16 21:00:52 -0400424//========================================================================================
Kevin Lubick084d9962018-08-15 13:28:27 -0400425// Matrix things
426//========================================================================================
427
428struct SimpleMatrix {
429 SkScalar scaleX, skewX, transX;
430 SkScalar skewY, scaleY, transY;
431 SkScalar pers0, pers1, pers2;
432};
433
434SkMatrix 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
Kevin Lubick11194ab2018-08-17 13:52:56 -0400440void ApplyTransform(SkPath& orig, const SimpleMatrix& sm) {
441 orig.transform(toSkMatrix(sm));
Kevin Lubick084d9962018-08-15 13:28:27 -0400442}
443
Kevin Lubick11194ab2018-08-17 13:52:56 -0400444void ApplyTransform(SkPath& orig,
445 SkScalar scaleX, SkScalar skewX, SkScalar transX,
446 SkScalar skewY, SkScalar scaleY, SkScalar transY,
447 SkScalar pers0, SkScalar pers1, SkScalar pers2) {
Kevin Lubick084d9962018-08-15 13:28:27 -0400448 SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX,
449 skewY , scaleY, transY,
450 pers0 , pers1 , pers2);
Kevin Lubick11194ab2018-08-17 13:52:56 -0400451 orig.transform(m);
Kevin Lubick084d9962018-08-15 13:28:27 -0400452}
453
454//========================================================================================
Kevin Lubick644d8e72018-08-09 13:58:04 -0400455// Testing things
Kevin Lubick92eaa3c2018-07-16 21:00:52 -0400456//========================================================================================
457
Kevin Lubick644d8e72018-08-09 13:58:04 -0400458// 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.
463float SkBits2FloatUnsigned(uint32_t floatAsBits) {
464 return SkBits2Float((int32_t) floatAsBits);
Kevin Lubick92eaa3c2018-07-16 21:00:52 -0400465}
466
Kevin Lubick22647d02018-07-06 14:31:23 -0400467// Binds the classes to the JS
Kevin Lubick641bf872018-08-06 14:49:39 -0400468//
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).
Kevin Lubick11194ab2018-08-17 13:52:56 -0400476//
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).
Kevin Lubick22647d02018-07-06 14:31:23 -0400482EMSCRIPTEN_BINDINGS(skia) {
483 class_<SkPath>("SkPath")
484 .constructor<>()
Kevin Lubick084d9962018-08-15 13:28:27 -0400485 .constructor<const SkPath&>()
Kevin Lubick22647d02018-07-06 14:31:23 -0400486
Kevin Lubick641bf872018-08-06 14:49:39 -0400487 // Path2D API
Kevin Lubick11194ab2018-08-17 13:52:56 -0400488 .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)
Kevin Lubickd9936482018-08-24 10:44:16 -0400494 //"closePath" alias handled in JS bindings
Kevin Lubick11194ab2018-08-17 13:52:56 -0400495 .function("_conicTo", &ApplyConicTo)
496 .function("_cubicTo", &ApplyCubicTo)
Kevin Lubick641bf872018-08-06 14:49:39 -0400497
Kevin Lubick11194ab2018-08-17 13:52:56 -0400498 .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)
Kevin Lubick641bf872018-08-06 14:49:39 -0400504
Kevin Lubick644d8e72018-08-09 13:58:04 -0400505 // Extra features
506 .function("setFillType", &SkPath::setFillType)
507 .function("getFillType", &SkPath::getFillType)
Kevin Lubick084d9962018-08-15 13:28:27 -0400508 .function("getFillTypeString", &GetFillTypeString)
Kevin Lubick644d8e72018-08-09 13:58:04 -0400509 .function("getBounds", &SkPath::getBounds)
510 .function("computeTightBounds", &SkPath::computeTightBounds)
Kevin Lubick084d9962018-08-15 13:28:27 -0400511 .function("equals", &Equals)
512 .function("copy", &CopyPath)
Kevin Lubick644d8e72018-08-09 13:58:04 -0400513
Kevin Lubick97d6d982018-08-10 15:53:16 -0400514 // PathEffects
Kevin Lubick11194ab2018-08-17 13:52:56 -0400515 .function("_dash", &ApplyDash)
516 .function("_trim", &ApplyTrim)
517 .function("_stroke", &ApplyStroke)
Kevin Lubick97d6d982018-08-10 15:53:16 -0400518
Kevin Lubick084d9962018-08-15 13:28:27 -0400519 // Matrix
Kevin Lubick11194ab2018-08-17 13:52:56 -0400520 .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))
Kevin Lubick084d9962018-08-15 13:28:27 -0400522
Kevin Lubick641bf872018-08-06 14:49:39 -0400523 // PathOps
Kevin Lubick11194ab2018-08-17 13:52:56 -0400524 .function("_simplify", &ApplySimplify)
525 .function("_op", &ApplyPathOp)
Kevin Lubick641bf872018-08-06 14:49:39 -0400526
527 // Exporting
528 .function("toCmds", &ToCmds)
529 .function("toPath2D", &ToPath2D)
530 .function("toCanvas", &ToCanvas)
531 .function("toSVGString", &ToSVGString)
532
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400533#ifdef PATHKIT_TESTING
534 .function("dump", select_overload<void() const>(&SkPath::dump))
Kevin Lubick97d6d982018-08-10 15:53:16 -0400535 .function("dumpHex", select_overload<void() const>(&SkPath::dumpHex))
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400536#endif
537 ;
Kevin Lubick22647d02018-07-06 14:31:23 -0400538
539 class_<SkOpBuilder>("SkOpBuilder")
540 .constructor<>()
541
Kevin Lubick641bf872018-08-06 14:49:39 -0400542 .function("add", &SkOpBuilder::add)
Kevin Lubickd9936482018-08-24 10:44:16 -0400543 .function("make", &ResolveBuilder)
Kevin Lubick641bf872018-08-06 14:49:39 -0400544 .function("resolve", &ResolveBuilder);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400545
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);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400551 function("NewPath", &NewPath);
Kevin Lubick084d9962018-08-15 13:28:27 -0400552 function("NewPath", &CopyPath);
Kevin Lubickd9936482018-08-24 10:44:16 -0400553 // FromCmds is defined in helper.js to make use of TypedArrays transparent.
554 function("_FromCmds", &FromCmds);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400555 // Path2D is opaque, so we can't read in from it.
556
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400557 // PathOps
Kevin Lubickd9936482018-08-24 10:44:16 -0400558 function("MakeFromOp", &MakeFromOp);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400559
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
Kevin Lubick644d8e72018-08-09 13:58:04 -0400567 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
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400573 constant("MOVE_VERB", MOVE);
574 constant("LINE_VERB", LINE);
575 constant("QUAD_VERB", QUAD);
Kevin Lubick97d6d982018-08-10 15:53:16 -0400576 constant("CONIC_VERB", CONIC);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400577 constant("CUBIC_VERB", CUBIC);
578 constant("CLOSE_VERB", CLOSE);
579
Kevin Lubick644d8e72018-08-09 13:58:04 -0400580 // 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
Kevin Lubickd9936482018-08-24 10:44:16 -0400589 function("LTRBRect", &SkRect::MakeLTRB);
Kevin Lubick644d8e72018-08-09 13:58:04 -0400590
Kevin Lubick97d6d982018-08-10 15:53:16 -0400591 // 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
Kevin Lubick11194ab2018-08-17 13:52:56 -0400602 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);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400607
Kevin Lubick084d9962018-08-15 13:28:27 -0400608 // 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);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400626
Kevin Lubick774be5a2018-09-07 14:00:41 -0400627 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
Kevin Lubick644d8e72018-08-09 13:58:04 -0400640 // Test Utils
641 function("SkBits2FloatUnsigned", &SkBits2FloatUnsigned);
Kevin Lubick22647d02018-07-06 14:31:23 -0400642}