blob: 4e84c96e9792f7865fd5796fd745de3e273e7767 [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 Lubick97d6d982018-08-10 15:53:16 -04008#include "SkDashPathEffect.h"
Kevin Lubick92eaa3c2018-07-16 21:00:52 -04009#include "SkFloatBits.h"
Kevin Lubick22647d02018-07-06 14:31:23 -040010#include "SkFloatingPoint.h"
Kevin Lubick641bf872018-08-06 14:49:39 -040011#include "SkMatrix.h"
Kevin Lubick97d6d982018-08-10 15:53:16 -040012#include "SkPaint.h"
Kevin Lubick22647d02018-07-06 14:31:23 -040013#include "SkParsePath.h"
14#include "SkPath.h"
15#include "SkPathOps.h"
Kevin Lubick641bf872018-08-06 14:49:39 -040016#include "SkRect.h"
Kevin Lubick22647d02018-07-06 14:31:23 -040017#include "SkString.h"
Kevin Lubick97d6d982018-08-10 15:53:16 -040018#include "SkTrimPathEffect.h"
Kevin Lubick22647d02018-07-06 14:31:23 -040019
20#include <emscripten/emscripten.h>
21#include <emscripten/bind.h>
22
23using namespace emscripten;
24
25static const int MOVE = 0;
26static const int LINE = 1;
27static const int QUAD = 2;
Kevin Lubick97d6d982018-08-10 15:53:16 -040028static const int CONIC = 3;
Kevin Lubick22647d02018-07-06 14:31:23 -040029static const int CUBIC = 4;
30static const int CLOSE = 5;
31
Kevin Lubick5f0e3a12018-08-07 11:30:12 -040032// Just for self-documenting purposes where the main thing being returned is an
Kevin Lubick97d6d982018-08-10 15:53:16 -040033// SkPath, but in an error case, something of type null (which is val) could also be
Kevin Lubick5f0e3a12018-08-07 11:30:12 -040034// returned;
Kevin Lubick97d6d982018-08-10 15:53:16 -040035using SkPathOrNull = emscripten::val;
36// Self-documenting for when we return a string
37using JSString = emscripten::val;
Kevin Lubick5f0e3a12018-08-07 11:30:12 -040038
Kevin Lubick22647d02018-07-06 14:31:23 -040039// =================================================================================
Kevin Lubick641bf872018-08-06 14:49:39 -040040// Creating/Exporting Paths with cmd arrays
Kevin Lubick22647d02018-07-06 14:31:23 -040041// =================================================================================
42
Florin Malitae1824da2018-07-12 10:33:39 -040043template <typename VisitFunc>
44void VisitPath(const SkPath& p, VisitFunc&& f) {
45 SkPath::RawIter iter(p);
Kevin Lubick22647d02018-07-06 14:31:23 -040046 SkPoint pts[4];
47 SkPath::Verb verb;
Florin Malitae1824da2018-07-12 10:33:39 -040048 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
Kevin Lubick641bf872018-08-06 14:49:39 -040049 f(verb, pts, iter);
Kevin Lubick22647d02018-07-06 14:31:23 -040050 }
51}
52
Kevin Lubick641bf872018-08-06 14:49:39 -040053emscripten::val EMSCRIPTEN_KEEPALIVE ToCmds(const SkPath& path) {
Kevin Lubick5f0e3a12018-08-07 11:30:12 -040054 emscripten::val cmds = emscripten::val::array();
Kevin Lubick22647d02018-07-06 14:31:23 -040055
Kevin Lubick641bf872018-08-06 14:49:39 -040056 VisitPath(path, [&cmds](SkPath::Verb verb, const SkPoint pts[4], SkPath::RawIter iter) {
Kevin Lubick5f0e3a12018-08-07 11:30:12 -040057 emscripten::val cmd = emscripten::val::array();
Kevin Lubick22647d02018-07-06 14:31:23 -040058 switch (verb) {
Florin Malitae1824da2018-07-12 10:33:39 -040059 case SkPath::kMove_Verb:
60 cmd.call<void>("push", MOVE, pts[0].x(), pts[0].y());
61 break;
62 case SkPath::kLine_Verb:
63 cmd.call<void>("push", LINE, pts[1].x(), pts[1].y());
64 break;
65 case SkPath::kQuad_Verb:
66 cmd.call<void>("push", QUAD, pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
67 break;
68 case SkPath::kConic_Verb:
Kevin Lubick97d6d982018-08-10 15:53:16 -040069 cmd.call<void>("push", CONIC,
70 pts[1].x(), pts[1].y(),
71 pts[2].x(), pts[2].y(), iter.conicWeight());
Florin Malitae1824da2018-07-12 10:33:39 -040072 break;
73 case SkPath::kCubic_Verb:
74 cmd.call<void>("push", CUBIC,
75 pts[1].x(), pts[1].y(),
76 pts[2].x(), pts[2].y(),
77 pts[3].x(), pts[3].y());
78 break;
79 case SkPath::kClose_Verb:
80 cmd.call<void>("push", CLOSE);
81 break;
82 case SkPath::kDone_Verb:
83 SkASSERT(false);
84 break;
Kevin Lubick22647d02018-07-06 14:31:23 -040085 }
86 cmds.call<void>("push", cmd);
Florin Malitae1824da2018-07-12 10:33:39 -040087 });
Kevin Lubick22647d02018-07-06 14:31:23 -040088 return cmds;
89}
90
91// This type signature is a mess, but it's necessary. See, we can't use "bind" (EMSCRIPTEN_BINDINGS)
92// and pointers to primitive types (Only bound types like SkPoint). We could if we used
93// cwrap (see https://becominghuman.ai/passing-and-returning-webassembly-array-parameters-a0f572c65d97)
94// but that requires us to stick to C code and, AFAIK, doesn't allow us to return nice things like
95// SkPath or SkOpBuilder.
96//
97// So, basically, if we are using C++ and EMSCRIPTEN_BINDINGS, we can't have primative pointers
98// in our function type signatures. (this gives an error message like "Cannot call foo due to unbound
99// types Pi, Pf"). But, we can just pretend they are numbers and cast them to be pointers and
100// the compiler is happy.
Kevin Lubick97d6d982018-08-10 15:53:16 -0400101SkPathOrNull EMSCRIPTEN_KEEPALIVE FromCmds(uintptr_t /* float* */ cptr, int numCmds) {
Florin Malitae1824da2018-07-12 10:33:39 -0400102 const auto* cmds = reinterpret_cast<const float*>(cptr);
Kevin Lubick22647d02018-07-06 14:31:23 -0400103 SkPath path;
104 float x1, y1, x2, y2, x3, y3;
105
106 // if there are not enough arguments, bail with the path we've constructed so far.
107 #define CHECK_NUM_ARGS(n) \
108 if ((i + n) > numCmds) { \
109 SkDebugf("Not enough args to match the verbs. Saw %d commands\n", numCmds); \
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400110 return emscripten::val::null(); \
Kevin Lubick22647d02018-07-06 14:31:23 -0400111 }
112
113 for(int i = 0; i < numCmds;){
114 switch (sk_float_floor2int(cmds[i++])) {
115 case MOVE:
116 CHECK_NUM_ARGS(2);
117 x1 = cmds[i++], y1 = cmds[i++];
118 path.moveTo(x1, y1);
119 break;
120 case LINE:
121 CHECK_NUM_ARGS(2);
122 x1 = cmds[i++], y1 = cmds[i++];
123 path.lineTo(x1, y1);
124 break;
125 case QUAD:
126 CHECK_NUM_ARGS(4);
127 x1 = cmds[i++], y1 = cmds[i++];
128 x2 = cmds[i++], y2 = cmds[i++];
129 path.quadTo(x1, y1, x2, y2);
130 break;
131 case CUBIC:
132 CHECK_NUM_ARGS(6);
133 x1 = cmds[i++], y1 = cmds[i++];
134 x2 = cmds[i++], y2 = cmds[i++];
135 x3 = cmds[i++], y3 = cmds[i++];
136 path.cubicTo(x1, y1, x2, y2, x3, y3);
137 break;
138 case CLOSE:
139 path.close();
140 break;
141 default:
142 SkDebugf(" path: UNKNOWN command %f, aborting dump...\n", cmds[i-1]);
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400143 return emscripten::val::null();
Kevin Lubick22647d02018-07-06 14:31:23 -0400144 }
145 }
146
147 #undef CHECK_NUM_ARGS
148
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400149 return emscripten::val(path);
Kevin Lubick22647d02018-07-06 14:31:23 -0400150}
151
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400152SkPath EMSCRIPTEN_KEEPALIVE NewPath() {
153 return SkPath();
154}
155
Kevin Lubick084d9962018-08-15 13:28:27 -0400156SkPath EMSCRIPTEN_KEEPALIVE CopyPath(const SkPath& a) {
157 SkPath copy(a);
158 return copy;
159}
160
161bool EMSCRIPTEN_KEEPALIVE Equals(const SkPath& a, const SkPath& b) {
162 return a == b;
163}
164
Kevin Lubick22647d02018-07-06 14:31:23 -0400165//========================================================================================
Kevin Lubick641bf872018-08-06 14:49:39 -0400166// SVG things
Kevin Lubick22647d02018-07-06 14:31:23 -0400167//========================================================================================
168
Kevin Lubick97d6d982018-08-10 15:53:16 -0400169JSString EMSCRIPTEN_KEEPALIVE ToSVGString(const SkPath& path) {
Kevin Lubick22647d02018-07-06 14:31:23 -0400170 SkString s;
171 SkParsePath::ToSVGString(path, &s);
172 // Wrapping it in val automatically turns it into a JS string.
173 // Not too sure on performance implications, but is is simpler than
174 // returning a raw pointer to const char * and then using
175 // Pointer_stringify() on the calling side.
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400176 return emscripten::val(s.c_str());
Kevin Lubick22647d02018-07-06 14:31:23 -0400177}
178
179
Kevin Lubick97d6d982018-08-10 15:53:16 -0400180SkPathOrNull EMSCRIPTEN_KEEPALIVE FromSVGString(std::string str) {
Kevin Lubick22647d02018-07-06 14:31:23 -0400181 SkPath path;
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400182 if (SkParsePath::FromSVGString(str.c_str(), &path)) {
183 return emscripten::val(path);
184 }
185 return emscripten::val::null();
Kevin Lubick22647d02018-07-06 14:31:23 -0400186}
187
188//========================================================================================
Kevin Lubick641bf872018-08-06 14:49:39 -0400189// PATHOP things
Kevin Lubick22647d02018-07-06 14:31:23 -0400190//========================================================================================
191
Kevin Lubick97d6d982018-08-10 15:53:16 -0400192SkPathOrNull EMSCRIPTEN_KEEPALIVE SimplifyPath(const SkPath& path) {
Kevin Lubick22647d02018-07-06 14:31:23 -0400193 SkPath simple;
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400194 if (Simplify(path, &simple)) {
195 return emscripten::val(simple);
196 }
197 return emscripten::val::null();
Kevin Lubick22647d02018-07-06 14:31:23 -0400198}
199
Kevin Lubick97d6d982018-08-10 15:53:16 -0400200SkPathOrNull EMSCRIPTEN_KEEPALIVE ApplyPathOp(const SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
Kevin Lubick22647d02018-07-06 14:31:23 -0400201 SkPath path;
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400202 if (Op(pathOne, pathTwo, op, &path)) {
203 return emscripten::val(path);
204 }
205 return emscripten::val::null();
Kevin Lubick22647d02018-07-06 14:31:23 -0400206}
207
Kevin Lubick97d6d982018-08-10 15:53:16 -0400208SkPathOrNull EMSCRIPTEN_KEEPALIVE ResolveBuilder(SkOpBuilder& builder) {
Kevin Lubick22647d02018-07-06 14:31:23 -0400209 SkPath path;
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400210 if (builder.resolve(&path)) {
211 return emscripten::val(path);
212 }
213 return emscripten::val::null();
Kevin Lubick22647d02018-07-06 14:31:23 -0400214}
215
216//========================================================================================
Kevin Lubick641bf872018-08-06 14:49:39 -0400217// Canvas things
Kevin Lubick22647d02018-07-06 14:31:23 -0400218//========================================================================================
219
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400220void EMSCRIPTEN_KEEPALIVE ToCanvas(const SkPath& path, emscripten::val /* Path2D or Canvas*/ ctx) {
Kevin Lubick22647d02018-07-06 14:31:23 -0400221 SkPath::Iter iter(path, false);
222 SkPoint pts[4];
223 SkPath::Verb verb;
224 while ((verb = iter.next(pts, false)) != SkPath::kDone_Verb) {
225 switch (verb) {
226 case SkPath::kMove_Verb:
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400227 ctx.call<void>("moveTo", pts[0].x(), pts[0].y());
Kevin Lubick22647d02018-07-06 14:31:23 -0400228 break;
229 case SkPath::kLine_Verb:
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400230 ctx.call<void>("lineTo", pts[1].x(), pts[1].y());
Kevin Lubick22647d02018-07-06 14:31:23 -0400231 break;
232 case SkPath::kQuad_Verb:
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400233 ctx.call<void>("quadraticCurveTo", pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
Kevin Lubick22647d02018-07-06 14:31:23 -0400234 break;
235 case SkPath::kConic_Verb:
Kevin Lubick641bf872018-08-06 14:49:39 -0400236 SkPoint quads[5];
237 // approximate with 2^1=2 quads.
238 SkPath::ConvertConicToQuads(pts[0], pts[1], pts[2], iter.conicWeight(), quads, 1);
Kevin Lubick641bf872018-08-06 14:49:39 -0400239 ctx.call<void>("quadraticCurveTo", quads[1].x(), quads[1].y(), quads[2].x(), quads[2].y());
240 ctx.call<void>("quadraticCurveTo", quads[3].x(), quads[3].y(), quads[4].x(), quads[4].y());
Kevin Lubick22647d02018-07-06 14:31:23 -0400241 break;
242 case SkPath::kCubic_Verb:
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400243 ctx.call<void>("bezierCurveTo", pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y(),
Kevin Lubick22647d02018-07-06 14:31:23 -0400244 pts[3].x(), pts[3].y());
245 break;
246 case SkPath::kClose_Verb:
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400247 ctx.call<void>("closePath");
Kevin Lubick22647d02018-07-06 14:31:23 -0400248 break;
249 case SkPath::kDone_Verb:
250 break;
251 }
252 }
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400253}
254
255emscripten::val JSPath2D = emscripten::val::global("Path2D");
256
Kevin Lubick641bf872018-08-06 14:49:39 -0400257emscripten::val EMSCRIPTEN_KEEPALIVE ToPath2D(const SkPath& path) {
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400258 emscripten::val retVal = JSPath2D.new_();
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400259 ToCanvas(path, retVal);
Kevin Lubick22647d02018-07-06 14:31:23 -0400260 return retVal;
261}
262
Kevin Lubick641bf872018-08-06 14:49:39 -0400263// ======================================================================================
264// Path2D API things
265// ======================================================================================
266void Path2DAddRect(SkPath& path, SkScalar x, SkScalar y, SkScalar width, SkScalar height) {
267 path.addRect(x, y, x+width, y+height);
268}
269
270void Path2DAddArc(SkPath& path, SkScalar x, SkScalar y, SkScalar radius,
271 SkScalar startAngle, SkScalar endAngle, bool ccw) {
272 SkPath temp;
273 SkRect bounds = SkRect::MakeLTRB(x-radius, y-radius, x+radius, y+radius);
274 const auto sweep = SkRadiansToDegrees(endAngle - startAngle) - 360 * ccw;
275 temp.addArc(bounds, SkRadiansToDegrees(startAngle), sweep);
276 path.addPath(temp, SkPath::kExtend_AddPathMode);
277}
278
279void Path2DAddArc(SkPath& path, SkScalar x, SkScalar y, SkScalar radius,
280 SkScalar startAngle, SkScalar endAngle) {
281 Path2DAddArc(path, x, y, radius, startAngle, endAngle, false);
282}
283
284void Path2DAddEllipse(SkPath& path, SkScalar x, SkScalar y, SkScalar radiusX, SkScalar radiusY,
285 SkScalar rotation, SkScalar startAngle, SkScalar endAngle, bool ccw) {
286 // This is easiest to do by making a new path and then extending the current path
287 // (this properly catches the cases of if there's a moveTo before this call or not).
288 SkRect bounds = SkRect::MakeLTRB(x-radiusX, y-radiusY, x+radiusX, y+radiusY);
289 SkPath temp;
290 const auto sweep = SkRadiansToDegrees(endAngle - startAngle) - (360 * ccw);
291 temp.addArc(bounds, SkRadiansToDegrees(startAngle), sweep);
292
293 SkMatrix m;
294 m.setRotate(SkRadiansToDegrees(rotation), x, y);
295 path.addPath(temp, m, SkPath::kExtend_AddPathMode);
296}
297
298void Path2DAddEllipse(SkPath& path, SkScalar x, SkScalar y, SkScalar radiusX, SkScalar radiusY,
299 SkScalar rotation, SkScalar startAngle, SkScalar endAngle) {
300 Path2DAddEllipse(path, x, y, radiusX, radiusY, rotation, startAngle, endAngle, false);
301}
302
303void Path2DAddPath(SkPath& orig, const SkPath& newPath) {
304 orig.addPath(newPath);
305}
306
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400307void Path2DAddPath(SkPath& orig, const SkPath& newPath, emscripten::val /* SVGMatrix*/ t) {
Kevin Lubick641bf872018-08-06 14:49:39 -0400308 SkMatrix m = SkMatrix::MakeAll(
309 t["a"].as<SkScalar>(), t["c"].as<SkScalar>(), t["e"].as<SkScalar>(),
310 t["b"].as<SkScalar>(), t["d"].as<SkScalar>(), t["f"].as<SkScalar>(),
311 0 , 0 , 1);
312 orig.addPath(newPath, m);
313}
314
315// Mimics the order of SVGMatrix, just w/o the SVG Matrix
316// This order is scaleX, skewY, skewX, scaleY, transX, transY
317// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform#Transform_functions
318void Path2DAddPath(SkPath& orig, const SkPath& newPath, SkScalar a, SkScalar b, SkScalar c, SkScalar d, SkScalar e, SkScalar f) {
319 SkMatrix m = SkMatrix::MakeAll(a, c, e,
320 b, d, f,
321 0, 0, 1);
322 orig.addPath(newPath, m);
323}
324
325// Allows for full matix control.
326void Path2DAddPath(SkPath& orig, const SkPath& newPath,
327 SkScalar scaleX, SkScalar skewX, SkScalar transX,
328 SkScalar skewY, SkScalar scaleY, SkScalar transY,
329 SkScalar pers0, SkScalar pers1, SkScalar pers2) {
330 SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX,
331 skewY , scaleY, transY,
332 pers0 , pers1 , pers2);
333 orig.addPath(newPath, m);
334}
335
Kevin Lubick084d9962018-08-15 13:28:27 -0400336JSString GetFillTypeString(const SkPath& path) {
Kevin Lubick97d6d982018-08-10 15:53:16 -0400337 if (path.getFillType() == SkPath::FillType::kWinding_FillType) {
338 return emscripten::val("nonzero");
339 } else if (path.getFillType() == SkPath::FillType::kEvenOdd_FillType) {
340 return emscripten::val("evenodd");
341 } else {
342 SkDebugf("warning: can't translate inverted filltype to HTML Canvas\n");
343 return emscripten::val("nonzero"); //Use default
344 }
345}
346
347//========================================================================================
348// Path Effects
349//========================================================================================
350
351SkPathOrNull PathEffectDash(const SkPath& path, SkScalar on, SkScalar off, SkScalar phase) {
352 SkPath output;
353 SkScalar intervals[] = { on, off };
354 auto pe = SkDashPathEffect::Make(intervals, 2, phase);
355 if (!pe) {
356 SkDebugf("Invalid args to dash()\n");
357 return emscripten::val::null();
358 }
359 if (pe->filterPath(&output, path, nullptr, nullptr)) {
360 return emscripten::val(output);
361 }
362 SkDebugf("Could not make dashed path\n");
363 return emscripten::val::null();
364}
365
366SkPathOrNull PathEffectTrim(const SkPath& path, SkScalar startT, SkScalar stopT, bool isComplement) {
367 SkPath output;
368 auto mode = isComplement ? SkTrimPathEffect::Mode::kInverted : SkTrimPathEffect::Mode::kNormal;
369 auto pe = SkTrimPathEffect::Make(startT, stopT, mode);
370 if (!pe) {
371 SkDebugf("Invalid args to trim(): startT and stopT must be in [0,1]\n");
372 return emscripten::val::null();
373 }
374 if (pe->filterPath(&output, path, nullptr, nullptr)) {
375 return emscripten::val(output);
376 }
377 SkDebugf("Could not trim path\n");
378 return emscripten::val::null();
379}
380
381SkPathOrNull PathEffectTrim(const SkPath& path, SkScalar startT, SkScalar stopT) {
382 return PathEffectTrim(path, startT, stopT, false);
383}
384
385SkPathOrNull PathEffectStroke(const SkPath& path, SkScalar width, SkPaint::Join join, SkPaint::Cap cap) {
386 SkPath output;
387 SkPaint p;
388 p.setStyle(SkPaint::kStroke_Style);
389 p.setStrokeCap(cap);
390 p.setStrokeJoin(join);
391 p.setStrokeWidth(width);
392
393 if (p.getFillPath(path, &output)) {
394 return emscripten::val(output);
395 }
396 SkDebugf("Could not stroke path\n");
397 return emscripten::val::null();
398}
399
Kevin Lubick92eaa3c2018-07-16 21:00:52 -0400400//========================================================================================
Kevin Lubick084d9962018-08-15 13:28:27 -0400401// Matrix things
402//========================================================================================
403
404struct SimpleMatrix {
405 SkScalar scaleX, skewX, transX;
406 SkScalar skewY, scaleY, transY;
407 SkScalar pers0, pers1, pers2;
408};
409
410SkMatrix toSkMatrix(const SimpleMatrix& sm) {
411 return SkMatrix::MakeAll(sm.scaleX, sm.skewX , sm.transX,
412 sm.skewY , sm.scaleY, sm.transY,
413 sm.pers0 , sm.pers1 , sm.pers2);
414}
415
416SkPathOrNull PathTransform(const SkPath& orig, const SimpleMatrix& sm) {
417 SkPath output;
418 orig.transform(toSkMatrix(sm), &output);
419 return emscripten::val(output);
420}
421
422SkPathOrNull PathTransform(const SkPath& orig,
423 SkScalar scaleX, SkScalar skewX, SkScalar transX,
424 SkScalar skewY, SkScalar scaleY, SkScalar transY,
425 SkScalar pers0, SkScalar pers1, SkScalar pers2) {
426 SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX,
427 skewY , scaleY, transY,
428 pers0 , pers1 , pers2);
429 SkPath output;
430 orig.transform(m, &output);
431 return emscripten::val(output);
432}
433
434//========================================================================================
Kevin Lubick644d8e72018-08-09 13:58:04 -0400435// Testing things
Kevin Lubick92eaa3c2018-07-16 21:00:52 -0400436//========================================================================================
437
Kevin Lubick644d8e72018-08-09 13:58:04 -0400438// The use case for this is on the JS side is something like:
439// PathKit.SkBits2FloatUnsigned(parseInt("0xc0a00000"))
440// to have precise float values for tests. In the C++ tests, we can use SkBits2Float because
441// it takes int32_t, but the JS parseInt basically returns an unsigned int. So, we add in
442// this helper which casts for us on the way to SkBits2Float.
443float SkBits2FloatUnsigned(uint32_t floatAsBits) {
444 return SkBits2Float((int32_t) floatAsBits);
Kevin Lubick92eaa3c2018-07-16 21:00:52 -0400445}
446
Kevin Lubick22647d02018-07-06 14:31:23 -0400447// Binds the classes to the JS
Kevin Lubick641bf872018-08-06 14:49:39 -0400448//
449// See https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/embind.html#non-member-functions-on-the-javascript-prototype
450// for more on binding non-member functions to the JS object, allowing us to rewire
451// various functions. That is, we can make the SkPath we expose appear to have methods
452// that the original SkPath does not, like rect(x, y, width, height) and toPath2D().
453//
454// An important detail for binding non-member functions is that the first argument
455// must be SkPath& (the reference part is very important).
Kevin Lubick22647d02018-07-06 14:31:23 -0400456EMSCRIPTEN_BINDINGS(skia) {
457 class_<SkPath>("SkPath")
458 .constructor<>()
Kevin Lubick084d9962018-08-15 13:28:27 -0400459 .constructor<const SkPath&>()
Kevin Lubick22647d02018-07-06 14:31:23 -0400460
Kevin Lubick641bf872018-08-06 14:49:39 -0400461 // Path2D API
462 .function("addPath",
463 select_overload<void(SkPath&, const SkPath&)>(&Path2DAddPath))
464 .function("addPath",
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400465 select_overload<void(SkPath&, const SkPath&, emscripten::val)>(&Path2DAddPath))
Kevin Lubick641bf872018-08-06 14:49:39 -0400466 .function("arc",
467 select_overload<void(SkPath&, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&Path2DAddArc))
468 .function("arc",
469 select_overload<void(SkPath&, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, bool)>(&Path2DAddArc))
470 .function("arcTo",
Kevin Lubick084d9962018-08-15 13:28:27 -0400471 select_overload<SkPath&(SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&SkPath::arcTo))
Kevin Lubick641bf872018-08-06 14:49:39 -0400472 .function("bezierCurveTo",
Kevin Lubick084d9962018-08-15 13:28:27 -0400473 select_overload<SkPath&(SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&SkPath::cubicTo))
Kevin Lubick641bf872018-08-06 14:49:39 -0400474 .function("closePath", &SkPath::close)
475 .function("ellipse",
476 select_overload<void(SkPath&, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&Path2DAddEllipse))
477 .function("ellipse",
478 select_overload<void(SkPath&, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, bool)>(&Path2DAddEllipse))
Kevin Lubick22647d02018-07-06 14:31:23 -0400479 .function("lineTo",
Kevin Lubick084d9962018-08-15 13:28:27 -0400480 select_overload<SkPath&(SkScalar, SkScalar)>(&SkPath::lineTo))
Kevin Lubick641bf872018-08-06 14:49:39 -0400481 .function("moveTo",
Kevin Lubick084d9962018-08-15 13:28:27 -0400482 select_overload<SkPath&(SkScalar, SkScalar)>(&SkPath::moveTo))
Kevin Lubick641bf872018-08-06 14:49:39 -0400483 .function("quadraticCurveTo",
Kevin Lubick084d9962018-08-15 13:28:27 -0400484 select_overload<SkPath&(SkScalar, SkScalar, SkScalar, SkScalar)>(&SkPath::quadTo))
Kevin Lubick641bf872018-08-06 14:49:39 -0400485 .function("rect", &Path2DAddRect)
486
487 // Some shorthand helpers, to mirror SkPath.cpp's API
488 .function("addPath",
489 select_overload<void(SkPath&, const SkPath&, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&Path2DAddPath))
490 .function("addPath",
491 select_overload<void(SkPath&, const SkPath&, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&Path2DAddPath))
492 .function("close", &SkPath::close)
Kevin Lubick97d6d982018-08-10 15:53:16 -0400493 .function("conicTo",
Kevin Lubick084d9962018-08-15 13:28:27 -0400494 select_overload<SkPath&(SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&SkPath::conicTo))
Kevin Lubick22647d02018-07-06 14:31:23 -0400495 .function("cubicTo",
Kevin Lubick084d9962018-08-15 13:28:27 -0400496 select_overload<SkPath&(SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&SkPath::cubicTo))
Kevin Lubick641bf872018-08-06 14:49:39 -0400497 .function("quadTo",
Kevin Lubick084d9962018-08-15 13:28:27 -0400498 select_overload<SkPath&(SkScalar, SkScalar, SkScalar, SkScalar)>(&SkPath::quadTo))
Kevin Lubick641bf872018-08-06 14:49:39 -0400499
Kevin Lubick644d8e72018-08-09 13:58:04 -0400500 // Extra features
501 .function("setFillType", &SkPath::setFillType)
502 .function("getFillType", &SkPath::getFillType)
Kevin Lubick084d9962018-08-15 13:28:27 -0400503 .function("getFillTypeString", &GetFillTypeString)
Kevin Lubick644d8e72018-08-09 13:58:04 -0400504 .function("getBounds", &SkPath::getBounds)
505 .function("computeTightBounds", &SkPath::computeTightBounds)
Kevin Lubick084d9962018-08-15 13:28:27 -0400506 .function("equals", &Equals)
507 .function("copy", &CopyPath)
Kevin Lubick644d8e72018-08-09 13:58:04 -0400508
Kevin Lubick97d6d982018-08-10 15:53:16 -0400509 // PathEffects
510 .function("dash", &PathEffectDash)
511 .function("trim", select_overload<SkPathOrNull(const SkPath&, SkScalar, SkScalar)>(&PathEffectTrim))
512 .function("trim", select_overload<SkPathOrNull(const SkPath&, SkScalar, SkScalar, bool)>(&PathEffectTrim))
513 .function("stroke", &PathEffectStroke)
514
Kevin Lubick084d9962018-08-15 13:28:27 -0400515 // Matrix
516 .function("transform", select_overload<SkPathOrNull(const SkPath& orig, const SimpleMatrix& sm)>(&PathTransform))
517 .function("transform", select_overload<SkPathOrNull(const SkPath& orig, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&PathTransform))
518
Kevin Lubick641bf872018-08-06 14:49:39 -0400519 // PathOps
520 .function("simplify", &SimplifyPath)
521 .function("op", &ApplyPathOp)
522
523 // Exporting
524 .function("toCmds", &ToCmds)
525 .function("toPath2D", &ToPath2D)
526 .function("toCanvas", &ToCanvas)
527 .function("toSVGString", &ToSVGString)
528
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400529#ifdef PATHKIT_TESTING
530 .function("dump", select_overload<void() const>(&SkPath::dump))
Kevin Lubick97d6d982018-08-10 15:53:16 -0400531 .function("dumpHex", select_overload<void() const>(&SkPath::dumpHex))
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400532#endif
533 ;
Kevin Lubick22647d02018-07-06 14:31:23 -0400534
535 class_<SkOpBuilder>("SkOpBuilder")
536 .constructor<>()
537
Kevin Lubick641bf872018-08-06 14:49:39 -0400538 .function("add", &SkOpBuilder::add)
539 .function("resolve", &ResolveBuilder);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400540
541 // Without these function() bindings, the function would be exposed but oblivious to
542 // our types (e.g. SkPath)
543
544 // Import
545 function("FromSVGString", &FromSVGString);
546 function("FromCmds", &FromCmds);
547 function("NewPath", &NewPath);
Kevin Lubick084d9962018-08-15 13:28:27 -0400548 function("NewPath", &CopyPath);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400549 // Path2D is opaque, so we can't read in from it.
550
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400551 // PathOps
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400552 function("ApplyPathOp", &ApplyPathOp);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400553
554 enum_<SkPathOp>("PathOp")
555 .value("DIFFERENCE", SkPathOp::kDifference_SkPathOp)
556 .value("INTERSECT", SkPathOp::kIntersect_SkPathOp)
557 .value("UNION", SkPathOp::kUnion_SkPathOp)
558 .value("XOR", SkPathOp::kXOR_SkPathOp)
559 .value("REVERSE_DIFFERENCE", SkPathOp::kReverseDifference_SkPathOp);
560
Kevin Lubick644d8e72018-08-09 13:58:04 -0400561 enum_<SkPath::FillType>("FillType")
562 .value("WINDING", SkPath::FillType::kWinding_FillType)
563 .value("EVENODD", SkPath::FillType::kEvenOdd_FillType)
564 .value("INVERSE_WINDING", SkPath::FillType::kInverseWinding_FillType)
565 .value("INVERSE_EVENODD", SkPath::FillType::kInverseEvenOdd_FillType);
566
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400567 constant("MOVE_VERB", MOVE);
568 constant("LINE_VERB", LINE);
569 constant("QUAD_VERB", QUAD);
Kevin Lubick97d6d982018-08-10 15:53:16 -0400570 constant("CONIC_VERB", CONIC);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400571 constant("CUBIC_VERB", CUBIC);
572 constant("CLOSE_VERB", CLOSE);
573
Kevin Lubick644d8e72018-08-09 13:58:04 -0400574 // A value object is much simpler than a class - it is returned as a JS
575 // object and does not require delete().
576 // https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/embind.html#value-types
577 value_object<SkRect>("SkRect")
578 .field("fLeft", &SkRect::fLeft)
579 .field("fTop", &SkRect::fTop)
580 .field("fRight", &SkRect::fRight)
581 .field("fBottom", &SkRect::fBottom);
582
583 function("MakeLTRBRect", &SkRect::MakeLTRB);
584
Kevin Lubick97d6d982018-08-10 15:53:16 -0400585 // Stroke
586 enum_<SkPaint::Join>("StrokeJoin")
587 .value("MITER", SkPaint::Join::kMiter_Join)
588 .value("ROUND", SkPaint::Join::kRound_Join)
589 .value("BEVEL", SkPaint::Join::kBevel_Join);
590
591 enum_<SkPaint::Cap>("StrokeCap")
592 .value("BUTT", SkPaint::Cap::kButt_Cap)
593 .value("ROUND", SkPaint::Cap::kRound_Cap)
594 .value("SQUARE", SkPaint::Cap::kSquare_Cap);
595
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400596
Kevin Lubick084d9962018-08-15 13:28:27 -0400597 // Matrix
598 // Allows clients to supply a 1D array of 9 elements and the bindings
599 // will automatically turn it into a 3x3 2D matrix.
600 // e.g. path.transform([0,1,2,3,4,5,6,7,8])
601 // This is likely simpler for the client than exposing SkMatrix
602 // directly and requiring them to do a lot of .delete().
603 value_array<SimpleMatrix>("SkMatrix")
604 .element(&SimpleMatrix::scaleX)
605 .element(&SimpleMatrix::skewX)
606 .element(&SimpleMatrix::transX)
607
608 .element(&SimpleMatrix::skewY)
609 .element(&SimpleMatrix::scaleY)
610 .element(&SimpleMatrix::transY)
611
612 .element(&SimpleMatrix::pers0)
613 .element(&SimpleMatrix::pers1)
614 .element(&SimpleMatrix::pers2);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400615
Kevin Lubick644d8e72018-08-09 13:58:04 -0400616 // Test Utils
617 function("SkBits2FloatUnsigned", &SkBits2FloatUnsigned);
Kevin Lubick22647d02018-07-06 14:31:23 -0400618}