blob: 40772d8b0cf4bde2fd4abd1955682e312e7d77c6 [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"
Kevin Lubick11194ab2018-08-17 13:52:56 -040014#include "SkStrokeRec.h"
Kevin Lubick22647d02018-07-06 14:31:23 -040015#include "SkPath.h"
16#include "SkPathOps.h"
Kevin Lubick641bf872018-08-06 14:49:39 -040017#include "SkRect.h"
Kevin Lubick11194ab2018-08-17 13:52:56 -040018#include "SkPaintDefaults.h"
Kevin Lubick22647d02018-07-06 14:31:23 -040019#include "SkString.h"
Kevin Lubick97d6d982018-08-10 15:53:16 -040020#include "SkTrimPathEffect.h"
Kevin Lubick22647d02018-07-06 14:31:23 -040021
22#include <emscripten/emscripten.h>
23#include <emscripten/bind.h>
24
25using namespace emscripten;
26
27static const int MOVE = 0;
28static const int LINE = 1;
29static const int QUAD = 2;
Kevin Lubick97d6d982018-08-10 15:53:16 -040030static const int CONIC = 3;
Kevin Lubick22647d02018-07-06 14:31:23 -040031static const int CUBIC = 4;
32static const int CLOSE = 5;
33
Kevin Lubick5f0e3a12018-08-07 11:30:12 -040034// Just for self-documenting purposes where the main thing being returned is an
Kevin Lubick97d6d982018-08-10 15:53:16 -040035// SkPath, but in an error case, something of type null (which is val) could also be
Kevin Lubick5f0e3a12018-08-07 11:30:12 -040036// returned;
Kevin Lubick97d6d982018-08-10 15:53:16 -040037using SkPathOrNull = emscripten::val;
38// Self-documenting for when we return a string
39using JSString = emscripten::val;
Kevin Lubick5f0e3a12018-08-07 11:30:12 -040040
Kevin Lubick22647d02018-07-06 14:31:23 -040041// =================================================================================
Kevin Lubick641bf872018-08-06 14:49:39 -040042// Creating/Exporting Paths with cmd arrays
Kevin Lubick22647d02018-07-06 14:31:23 -040043// =================================================================================
44
Florin Malitae1824da2018-07-12 10:33:39 -040045template <typename VisitFunc>
46void VisitPath(const SkPath& p, VisitFunc&& f) {
47 SkPath::RawIter iter(p);
Kevin Lubick22647d02018-07-06 14:31:23 -040048 SkPoint pts[4];
49 SkPath::Verb verb;
Florin Malitae1824da2018-07-12 10:33:39 -040050 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
Kevin Lubick641bf872018-08-06 14:49:39 -040051 f(verb, pts, iter);
Kevin Lubick22647d02018-07-06 14:31:23 -040052 }
53}
54
Kevin Lubick641bf872018-08-06 14:49:39 -040055emscripten::val EMSCRIPTEN_KEEPALIVE ToCmds(const SkPath& path) {
Kevin Lubick5f0e3a12018-08-07 11:30:12 -040056 emscripten::val cmds = emscripten::val::array();
Kevin Lubick22647d02018-07-06 14:31:23 -040057
Kevin Lubick641bf872018-08-06 14:49:39 -040058 VisitPath(path, [&cmds](SkPath::Verb verb, const SkPoint pts[4], SkPath::RawIter iter) {
Kevin Lubick5f0e3a12018-08-07 11:30:12 -040059 emscripten::val cmd = emscripten::val::array();
Kevin Lubick22647d02018-07-06 14:31:23 -040060 switch (verb) {
Florin Malitae1824da2018-07-12 10:33:39 -040061 case SkPath::kMove_Verb:
62 cmd.call<void>("push", MOVE, pts[0].x(), pts[0].y());
63 break;
64 case SkPath::kLine_Verb:
65 cmd.call<void>("push", LINE, pts[1].x(), pts[1].y());
66 break;
67 case SkPath::kQuad_Verb:
68 cmd.call<void>("push", QUAD, pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
69 break;
70 case SkPath::kConic_Verb:
Kevin Lubick97d6d982018-08-10 15:53:16 -040071 cmd.call<void>("push", CONIC,
72 pts[1].x(), pts[1].y(),
73 pts[2].x(), pts[2].y(), iter.conicWeight());
Florin Malitae1824da2018-07-12 10:33:39 -040074 break;
75 case SkPath::kCubic_Verb:
76 cmd.call<void>("push", CUBIC,
77 pts[1].x(), pts[1].y(),
78 pts[2].x(), pts[2].y(),
79 pts[3].x(), pts[3].y());
80 break;
81 case SkPath::kClose_Verb:
82 cmd.call<void>("push", CLOSE);
83 break;
84 case SkPath::kDone_Verb:
85 SkASSERT(false);
86 break;
Kevin Lubick22647d02018-07-06 14:31:23 -040087 }
88 cmds.call<void>("push", cmd);
Florin Malitae1824da2018-07-12 10:33:39 -040089 });
Kevin Lubick22647d02018-07-06 14:31:23 -040090 return cmds;
91}
92
93// This type signature is a mess, but it's necessary. See, we can't use "bind" (EMSCRIPTEN_BINDINGS)
94// and pointers to primitive types (Only bound types like SkPoint). We could if we used
95// cwrap (see https://becominghuman.ai/passing-and-returning-webassembly-array-parameters-a0f572c65d97)
96// but that requires us to stick to C code and, AFAIK, doesn't allow us to return nice things like
97// SkPath or SkOpBuilder.
98//
99// So, basically, if we are using C++ and EMSCRIPTEN_BINDINGS, we can't have primative pointers
100// in our function type signatures. (this gives an error message like "Cannot call foo due to unbound
101// types Pi, Pf"). But, we can just pretend they are numbers and cast them to be pointers and
102// the compiler is happy.
Kevin Lubick97d6d982018-08-10 15:53:16 -0400103SkPathOrNull EMSCRIPTEN_KEEPALIVE FromCmds(uintptr_t /* float* */ cptr, int numCmds) {
Florin Malitae1824da2018-07-12 10:33:39 -0400104 const auto* cmds = reinterpret_cast<const float*>(cptr);
Kevin Lubick22647d02018-07-06 14:31:23 -0400105 SkPath path;
106 float x1, y1, x2, y2, x3, y3;
107
108 // if there are not enough arguments, bail with the path we've constructed so far.
109 #define CHECK_NUM_ARGS(n) \
110 if ((i + n) > numCmds) { \
111 SkDebugf("Not enough args to match the verbs. Saw %d commands\n", numCmds); \
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400112 return emscripten::val::null(); \
Kevin Lubick22647d02018-07-06 14:31:23 -0400113 }
114
115 for(int i = 0; i < numCmds;){
116 switch (sk_float_floor2int(cmds[i++])) {
117 case MOVE:
118 CHECK_NUM_ARGS(2);
119 x1 = cmds[i++], y1 = cmds[i++];
120 path.moveTo(x1, y1);
121 break;
122 case LINE:
123 CHECK_NUM_ARGS(2);
124 x1 = cmds[i++], y1 = cmds[i++];
125 path.lineTo(x1, y1);
126 break;
127 case QUAD:
128 CHECK_NUM_ARGS(4);
129 x1 = cmds[i++], y1 = cmds[i++];
130 x2 = cmds[i++], y2 = cmds[i++];
131 path.quadTo(x1, y1, x2, y2);
132 break;
Kevin Lubickc623af22018-08-17 14:42:53 -0400133 case CONIC:
Kevin Lubickc6c48aa2018-08-17 15:00:43 -0400134 CHECK_NUM_ARGS(5);
Kevin Lubickc623af22018-08-17 14:42:53 -0400135 x1 = cmds[i++], y1 = cmds[i++];
136 x2 = cmds[i++], y2 = cmds[i++];
Kevin Lubickd9936482018-08-24 10:44:16 -0400137 x3 = cmds[i++]; // weight
Kevin Lubickc623af22018-08-17 14:42:53 -0400138 path.conicTo(x1, y1, x2, y2, x3);
139 break;
Kevin Lubick22647d02018-07-06 14:31:23 -0400140 case CUBIC:
141 CHECK_NUM_ARGS(6);
142 x1 = cmds[i++], y1 = cmds[i++];
143 x2 = cmds[i++], y2 = cmds[i++];
144 x3 = cmds[i++], y3 = cmds[i++];
145 path.cubicTo(x1, y1, x2, y2, x3, y3);
146 break;
147 case CLOSE:
148 path.close();
149 break;
150 default:
151 SkDebugf(" path: UNKNOWN command %f, aborting dump...\n", cmds[i-1]);
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400152 return emscripten::val::null();
Kevin Lubick22647d02018-07-06 14:31:23 -0400153 }
154 }
155
156 #undef CHECK_NUM_ARGS
157
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400158 return emscripten::val(path);
Kevin Lubick22647d02018-07-06 14:31:23 -0400159}
160
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400161SkPath EMSCRIPTEN_KEEPALIVE NewPath() {
162 return SkPath();
163}
164
Kevin Lubick084d9962018-08-15 13:28:27 -0400165SkPath EMSCRIPTEN_KEEPALIVE CopyPath(const SkPath& a) {
166 SkPath copy(a);
167 return copy;
168}
169
170bool EMSCRIPTEN_KEEPALIVE Equals(const SkPath& a, const SkPath& b) {
171 return a == b;
172}
173
Kevin Lubick22647d02018-07-06 14:31:23 -0400174//========================================================================================
Kevin Lubick11194ab2018-08-17 13:52:56 -0400175// Path things
176//========================================================================================
177
178// All these Apply* methods are simple wrappers to avoid returning an object.
179// The default WASM bindings produce code that will leak if a return value
180// isn't assigned to a JS variable and has delete() called on it.
181// These Apply methods, combined with the smarter binding code allow for chainable
182// commands that don't leak if the return value is ignored (i.e. when used intuitively).
183
184void ApplyArcTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
185 SkScalar radius) {
186 p.arcTo(x1, y1, x2, y2, radius);
187}
188
189void ApplyClose(SkPath& p) {
190 p.close();
191}
192
193void ApplyConicTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
194 SkScalar w) {
195 p.conicTo(x1, y1, x2, y2, w);
196}
197
198void ApplyCubicTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
199 SkScalar x3, SkScalar y3) {
200 p.cubicTo(x1, y1, x2, y2, x3, y3);
201}
202
203void ApplyLineTo(SkPath& p, SkScalar x, SkScalar y) {
204 p.lineTo(x, y);
205}
206
207void ApplyMoveTo(SkPath& p, SkScalar x, SkScalar y) {
208 p.moveTo(x, y);
209}
210
211void ApplyQuadTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
212 p.quadTo(x1, y1, x2, y2);
213}
214
215
216
217//========================================================================================
Kevin Lubick641bf872018-08-06 14:49:39 -0400218// SVG things
Kevin Lubick22647d02018-07-06 14:31:23 -0400219//========================================================================================
220
Kevin Lubick97d6d982018-08-10 15:53:16 -0400221JSString EMSCRIPTEN_KEEPALIVE ToSVGString(const SkPath& path) {
Kevin Lubick22647d02018-07-06 14:31:23 -0400222 SkString s;
223 SkParsePath::ToSVGString(path, &s);
224 // Wrapping it in val automatically turns it into a JS string.
225 // Not too sure on performance implications, but is is simpler than
226 // returning a raw pointer to const char * and then using
227 // Pointer_stringify() on the calling side.
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400228 return emscripten::val(s.c_str());
Kevin Lubick22647d02018-07-06 14:31:23 -0400229}
230
231
Kevin Lubick97d6d982018-08-10 15:53:16 -0400232SkPathOrNull EMSCRIPTEN_KEEPALIVE FromSVGString(std::string str) {
Kevin Lubick22647d02018-07-06 14:31:23 -0400233 SkPath path;
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400234 if (SkParsePath::FromSVGString(str.c_str(), &path)) {
235 return emscripten::val(path);
236 }
237 return emscripten::val::null();
Kevin Lubick22647d02018-07-06 14:31:23 -0400238}
239
240//========================================================================================
Kevin Lubick641bf872018-08-06 14:49:39 -0400241// PATHOP things
Kevin Lubick22647d02018-07-06 14:31:23 -0400242//========================================================================================
243
Kevin Lubick11194ab2018-08-17 13:52:56 -0400244bool EMSCRIPTEN_KEEPALIVE ApplySimplify(SkPath& path) {
245 return Simplify(path, &path);
Kevin Lubick22647d02018-07-06 14:31:23 -0400246}
247
Kevin Lubick11194ab2018-08-17 13:52:56 -0400248bool EMSCRIPTEN_KEEPALIVE ApplyPathOp(SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
249 return Op(pathOne, pathTwo, op, &pathOne);
Kevin Lubick22647d02018-07-06 14:31:23 -0400250}
251
Kevin Lubickd9936482018-08-24 10:44:16 -0400252SkPathOrNull EMSCRIPTEN_KEEPALIVE MakeFromOp(const SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
253 SkPath out;
254 if (Op(pathOne, pathTwo, op, &out)) {
255 return emscripten::val(out);
256 }
257 return emscripten::val::null();
258}
259
Kevin Lubick97d6d982018-08-10 15:53:16 -0400260SkPathOrNull EMSCRIPTEN_KEEPALIVE ResolveBuilder(SkOpBuilder& builder) {
Kevin Lubick22647d02018-07-06 14:31:23 -0400261 SkPath path;
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400262 if (builder.resolve(&path)) {
263 return emscripten::val(path);
264 }
265 return emscripten::val::null();
Kevin Lubick22647d02018-07-06 14:31:23 -0400266}
267
268//========================================================================================
Kevin Lubick641bf872018-08-06 14:49:39 -0400269// Canvas things
Kevin Lubick22647d02018-07-06 14:31:23 -0400270//========================================================================================
271
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400272void EMSCRIPTEN_KEEPALIVE ToCanvas(const SkPath& path, emscripten::val /* Path2D or Canvas*/ ctx) {
Kevin Lubick22647d02018-07-06 14:31:23 -0400273 SkPath::Iter iter(path, false);
274 SkPoint pts[4];
275 SkPath::Verb verb;
276 while ((verb = iter.next(pts, false)) != SkPath::kDone_Verb) {
277 switch (verb) {
278 case SkPath::kMove_Verb:
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400279 ctx.call<void>("moveTo", pts[0].x(), pts[0].y());
Kevin Lubick22647d02018-07-06 14:31:23 -0400280 break;
281 case SkPath::kLine_Verb:
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400282 ctx.call<void>("lineTo", pts[1].x(), pts[1].y());
Kevin Lubick22647d02018-07-06 14:31:23 -0400283 break;
284 case SkPath::kQuad_Verb:
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400285 ctx.call<void>("quadraticCurveTo", pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
Kevin Lubick22647d02018-07-06 14:31:23 -0400286 break;
287 case SkPath::kConic_Verb:
Kevin Lubick641bf872018-08-06 14:49:39 -0400288 SkPoint quads[5];
289 // approximate with 2^1=2 quads.
290 SkPath::ConvertConicToQuads(pts[0], pts[1], pts[2], iter.conicWeight(), quads, 1);
Kevin Lubick641bf872018-08-06 14:49:39 -0400291 ctx.call<void>("quadraticCurveTo", quads[1].x(), quads[1].y(), quads[2].x(), quads[2].y());
292 ctx.call<void>("quadraticCurveTo", quads[3].x(), quads[3].y(), quads[4].x(), quads[4].y());
Kevin Lubick22647d02018-07-06 14:31:23 -0400293 break;
294 case SkPath::kCubic_Verb:
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400295 ctx.call<void>("bezierCurveTo", pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y(),
Kevin Lubick22647d02018-07-06 14:31:23 -0400296 pts[3].x(), pts[3].y());
297 break;
298 case SkPath::kClose_Verb:
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400299 ctx.call<void>("closePath");
Kevin Lubick22647d02018-07-06 14:31:23 -0400300 break;
301 case SkPath::kDone_Verb:
302 break;
303 }
304 }
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400305}
306
307emscripten::val JSPath2D = emscripten::val::global("Path2D");
308
Kevin Lubick641bf872018-08-06 14:49:39 -0400309emscripten::val EMSCRIPTEN_KEEPALIVE ToPath2D(const SkPath& path) {
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400310 emscripten::val retVal = JSPath2D.new_();
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400311 ToCanvas(path, retVal);
Kevin Lubick22647d02018-07-06 14:31:23 -0400312 return retVal;
313}
314
Kevin Lubick641bf872018-08-06 14:49:39 -0400315// ======================================================================================
316// Path2D API things
317// ======================================================================================
Kevin Lubick11194ab2018-08-17 13:52:56 -0400318void ApplyAddRect(SkPath& path, SkScalar x, SkScalar y, SkScalar width, SkScalar height) {
Kevin Lubick641bf872018-08-06 14:49:39 -0400319 path.addRect(x, y, x+width, y+height);
320}
321
Kevin Lubick11194ab2018-08-17 13:52:56 -0400322void ApplyAddArc(SkPath& path, SkScalar x, SkScalar y, SkScalar radius,
323 SkScalar startAngle, SkScalar endAngle, bool ccw) {
Kevin Lubick641bf872018-08-06 14:49:39 -0400324 SkPath temp;
325 SkRect bounds = SkRect::MakeLTRB(x-radius, y-radius, x+radius, y+radius);
326 const auto sweep = SkRadiansToDegrees(endAngle - startAngle) - 360 * ccw;
327 temp.addArc(bounds, SkRadiansToDegrees(startAngle), sweep);
328 path.addPath(temp, SkPath::kExtend_AddPathMode);
329}
330
Kevin Lubick11194ab2018-08-17 13:52:56 -0400331void ApplyEllipse(SkPath& path, SkScalar x, SkScalar y, SkScalar radiusX, SkScalar radiusY,
Kevin Lubick641bf872018-08-06 14:49:39 -0400332 SkScalar rotation, SkScalar startAngle, SkScalar endAngle, bool ccw) {
333 // This is easiest to do by making a new path and then extending the current path
334 // (this properly catches the cases of if there's a moveTo before this call or not).
335 SkRect bounds = SkRect::MakeLTRB(x-radiusX, y-radiusY, x+radiusX, y+radiusY);
336 SkPath temp;
337 const auto sweep = SkRadiansToDegrees(endAngle - startAngle) - (360 * ccw);
338 temp.addArc(bounds, SkRadiansToDegrees(startAngle), sweep);
339
340 SkMatrix m;
341 m.setRotate(SkRadiansToDegrees(rotation), x, y);
342 path.addPath(temp, m, SkPath::kExtend_AddPathMode);
343}
344
Kevin Lubick641bf872018-08-06 14:49:39 -0400345// Allows for full matix control.
Kevin Lubick11194ab2018-08-17 13:52:56 -0400346void ApplyAddPath(SkPath& orig, const SkPath& newPath,
Kevin Lubick641bf872018-08-06 14:49:39 -0400347 SkScalar scaleX, SkScalar skewX, SkScalar transX,
348 SkScalar skewY, SkScalar scaleY, SkScalar transY,
349 SkScalar pers0, SkScalar pers1, SkScalar pers2) {
350 SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX,
351 skewY , scaleY, transY,
352 pers0 , pers1 , pers2);
353 orig.addPath(newPath, m);
354}
355
Kevin Lubick084d9962018-08-15 13:28:27 -0400356JSString GetFillTypeString(const SkPath& path) {
Kevin Lubick97d6d982018-08-10 15:53:16 -0400357 if (path.getFillType() == SkPath::FillType::kWinding_FillType) {
358 return emscripten::val("nonzero");
359 } else if (path.getFillType() == SkPath::FillType::kEvenOdd_FillType) {
360 return emscripten::val("evenodd");
361 } else {
362 SkDebugf("warning: can't translate inverted filltype to HTML Canvas\n");
363 return emscripten::val("nonzero"); //Use default
364 }
365}
366
367//========================================================================================
368// Path Effects
369//========================================================================================
370
Kevin Lubick11194ab2018-08-17 13:52:56 -0400371bool ApplyDash(SkPath& path, SkScalar on, SkScalar off, SkScalar phase) {
Kevin Lubick97d6d982018-08-10 15:53:16 -0400372 SkScalar intervals[] = { on, off };
373 auto pe = SkDashPathEffect::Make(intervals, 2, phase);
374 if (!pe) {
375 SkDebugf("Invalid args to dash()\n");
Kevin Lubick11194ab2018-08-17 13:52:56 -0400376 return false;
Kevin Lubick97d6d982018-08-10 15:53:16 -0400377 }
Kevin Lubick11194ab2018-08-17 13:52:56 -0400378 SkStrokeRec rec(SkStrokeRec::InitStyle::kHairline_InitStyle);
379 if (pe->filterPath(&path, path, &rec, nullptr)) {
380 return true;
Kevin Lubick97d6d982018-08-10 15:53:16 -0400381 }
382 SkDebugf("Could not make dashed path\n");
Kevin Lubick11194ab2018-08-17 13:52:56 -0400383 return false;
Kevin Lubick97d6d982018-08-10 15:53:16 -0400384}
385
Kevin Lubick11194ab2018-08-17 13:52:56 -0400386bool ApplyTrim(SkPath& path, SkScalar startT, SkScalar stopT, bool isComplement) {
Kevin Lubick97d6d982018-08-10 15:53:16 -0400387 auto mode = isComplement ? SkTrimPathEffect::Mode::kInverted : SkTrimPathEffect::Mode::kNormal;
388 auto pe = SkTrimPathEffect::Make(startT, stopT, mode);
389 if (!pe) {
390 SkDebugf("Invalid args to trim(): startT and stopT must be in [0,1]\n");
Kevin Lubick11194ab2018-08-17 13:52:56 -0400391 return false;
Kevin Lubick97d6d982018-08-10 15:53:16 -0400392 }
Kevin Lubick11194ab2018-08-17 13:52:56 -0400393 SkStrokeRec rec(SkStrokeRec::InitStyle::kHairline_InitStyle);
394 if (pe->filterPath(&path, path, &rec, nullptr)) {
395 return true;
Kevin Lubick97d6d982018-08-10 15:53:16 -0400396 }
397 SkDebugf("Could not trim path\n");
Kevin Lubick11194ab2018-08-17 13:52:56 -0400398 return false;
Kevin Lubick97d6d982018-08-10 15:53:16 -0400399}
400
Kevin Lubick11194ab2018-08-17 13:52:56 -0400401struct StrokeOpts {
402 // Default values are set in chaining.js which allows clients
403 // to set any number of them. Otherwise, the binding code complains if
404 // any are omitted.
405 SkScalar width;
406 SkScalar miter_limit;
407 SkPaint::Join join;
408 SkPaint::Cap cap;
409};
Kevin Lubick97d6d982018-08-10 15:53:16 -0400410
Kevin Lubick11194ab2018-08-17 13:52:56 -0400411bool ApplyStroke(SkPath& path, StrokeOpts opts) {
Kevin Lubick97d6d982018-08-10 15:53:16 -0400412 SkPaint p;
413 p.setStyle(SkPaint::kStroke_Style);
Kevin Lubick11194ab2018-08-17 13:52:56 -0400414 p.setStrokeCap(opts.cap);
415 p.setStrokeJoin(opts.join);
416 p.setStrokeWidth(opts.width);
417 p.setStrokeMiter(opts.miter_limit);
Kevin Lubick97d6d982018-08-10 15:53:16 -0400418
Kevin Lubick11194ab2018-08-17 13:52:56 -0400419 return p.getFillPath(path, &path);
Kevin Lubick97d6d982018-08-10 15:53:16 -0400420}
421
Kevin Lubick92eaa3c2018-07-16 21:00:52 -0400422//========================================================================================
Kevin Lubick084d9962018-08-15 13:28:27 -0400423// Matrix things
424//========================================================================================
425
426struct SimpleMatrix {
427 SkScalar scaleX, skewX, transX;
428 SkScalar skewY, scaleY, transY;
429 SkScalar pers0, pers1, pers2;
430};
431
432SkMatrix toSkMatrix(const SimpleMatrix& sm) {
433 return SkMatrix::MakeAll(sm.scaleX, sm.skewX , sm.transX,
434 sm.skewY , sm.scaleY, sm.transY,
435 sm.pers0 , sm.pers1 , sm.pers2);
436}
437
Kevin Lubick11194ab2018-08-17 13:52:56 -0400438void ApplyTransform(SkPath& orig, const SimpleMatrix& sm) {
439 orig.transform(toSkMatrix(sm));
Kevin Lubick084d9962018-08-15 13:28:27 -0400440}
441
Kevin Lubick11194ab2018-08-17 13:52:56 -0400442void ApplyTransform(SkPath& orig,
443 SkScalar scaleX, SkScalar skewX, SkScalar transX,
444 SkScalar skewY, SkScalar scaleY, SkScalar transY,
445 SkScalar pers0, SkScalar pers1, SkScalar pers2) {
Kevin Lubick084d9962018-08-15 13:28:27 -0400446 SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX,
447 skewY , scaleY, transY,
448 pers0 , pers1 , pers2);
Kevin Lubick11194ab2018-08-17 13:52:56 -0400449 orig.transform(m);
Kevin Lubick084d9962018-08-15 13:28:27 -0400450}
451
452//========================================================================================
Kevin Lubick644d8e72018-08-09 13:58:04 -0400453// Testing things
Kevin Lubick92eaa3c2018-07-16 21:00:52 -0400454//========================================================================================
455
Kevin Lubick644d8e72018-08-09 13:58:04 -0400456// The use case for this is on the JS side is something like:
457// PathKit.SkBits2FloatUnsigned(parseInt("0xc0a00000"))
458// to have precise float values for tests. In the C++ tests, we can use SkBits2Float because
459// it takes int32_t, but the JS parseInt basically returns an unsigned int. So, we add in
460// this helper which casts for us on the way to SkBits2Float.
461float SkBits2FloatUnsigned(uint32_t floatAsBits) {
462 return SkBits2Float((int32_t) floatAsBits);
Kevin Lubick92eaa3c2018-07-16 21:00:52 -0400463}
464
Kevin Lubick22647d02018-07-06 14:31:23 -0400465// Binds the classes to the JS
Kevin Lubick641bf872018-08-06 14:49:39 -0400466//
467// See https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/embind.html#non-member-functions-on-the-javascript-prototype
468// for more on binding non-member functions to the JS object, allowing us to rewire
469// various functions. That is, we can make the SkPath we expose appear to have methods
470// that the original SkPath does not, like rect(x, y, width, height) and toPath2D().
471//
472// An important detail for binding non-member functions is that the first argument
473// must be SkPath& (the reference part is very important).
Kevin Lubick11194ab2018-08-17 13:52:56 -0400474//
475// Note that we can't expose default or optional arguments, but we can have multiple
476// declarations of the same function that take different amounts of arguments.
477// For example, see _transform
478// Additionally, we are perfectly happy to handle default arguments and function
479// overloads in the JS glue code (see chaining.js::addPath() for an example).
Kevin Lubick22647d02018-07-06 14:31:23 -0400480EMSCRIPTEN_BINDINGS(skia) {
481 class_<SkPath>("SkPath")
482 .constructor<>()
Kevin Lubick084d9962018-08-15 13:28:27 -0400483 .constructor<const SkPath&>()
Kevin Lubick22647d02018-07-06 14:31:23 -0400484
Kevin Lubick641bf872018-08-06 14:49:39 -0400485 // Path2D API
Kevin Lubick11194ab2018-08-17 13:52:56 -0400486 .function("_addPath", &ApplyAddPath)
487 // 3 additional overloads of addPath are handled in JS bindings
488 .function("_arc", &ApplyAddArc)
489 .function("_arcTo", &ApplyArcTo)
490 //"bezierCurveTo" alias handled in JS bindings
491 .function("_close", &ApplyClose)
Kevin Lubickd9936482018-08-24 10:44:16 -0400492 //"closePath" alias handled in JS bindings
Kevin Lubick11194ab2018-08-17 13:52:56 -0400493 .function("_conicTo", &ApplyConicTo)
494 .function("_cubicTo", &ApplyCubicTo)
Kevin Lubick641bf872018-08-06 14:49:39 -0400495
Kevin Lubick11194ab2018-08-17 13:52:56 -0400496 .function("_ellipse", &ApplyEllipse)
497 .function("_lineTo", &ApplyLineTo)
498 .function("_moveTo", &ApplyMoveTo)
499 // "quadraticCurveTo" alias handled in JS bindings
500 .function("_quadTo", &ApplyQuadTo)
501 .function("_rect", &ApplyAddRect)
Kevin Lubick641bf872018-08-06 14:49:39 -0400502
Kevin Lubick644d8e72018-08-09 13:58:04 -0400503 // Extra features
504 .function("setFillType", &SkPath::setFillType)
505 .function("getFillType", &SkPath::getFillType)
Kevin Lubick084d9962018-08-15 13:28:27 -0400506 .function("getFillTypeString", &GetFillTypeString)
Kevin Lubick644d8e72018-08-09 13:58:04 -0400507 .function("getBounds", &SkPath::getBounds)
508 .function("computeTightBounds", &SkPath::computeTightBounds)
Kevin Lubick084d9962018-08-15 13:28:27 -0400509 .function("equals", &Equals)
510 .function("copy", &CopyPath)
Kevin Lubick644d8e72018-08-09 13:58:04 -0400511
Kevin Lubick97d6d982018-08-10 15:53:16 -0400512 // PathEffects
Kevin Lubick11194ab2018-08-17 13:52:56 -0400513 .function("_dash", &ApplyDash)
514 .function("_trim", &ApplyTrim)
515 .function("_stroke", &ApplyStroke)
Kevin Lubick97d6d982018-08-10 15:53:16 -0400516
Kevin Lubick084d9962018-08-15 13:28:27 -0400517 // Matrix
Kevin Lubick11194ab2018-08-17 13:52:56 -0400518 .function("_transform", select_overload<void(SkPath& orig, const SimpleMatrix& sm)>(&ApplyTransform))
519 .function("_transform", select_overload<void(SkPath& orig, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&ApplyTransform))
Kevin Lubick084d9962018-08-15 13:28:27 -0400520
Kevin Lubick641bf872018-08-06 14:49:39 -0400521 // PathOps
Kevin Lubick11194ab2018-08-17 13:52:56 -0400522 .function("_simplify", &ApplySimplify)
523 .function("_op", &ApplyPathOp)
Kevin Lubick641bf872018-08-06 14:49:39 -0400524
525 // Exporting
526 .function("toCmds", &ToCmds)
527 .function("toPath2D", &ToPath2D)
528 .function("toCanvas", &ToCanvas)
529 .function("toSVGString", &ToSVGString)
530
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400531#ifdef PATHKIT_TESTING
532 .function("dump", select_overload<void() const>(&SkPath::dump))
Kevin Lubick97d6d982018-08-10 15:53:16 -0400533 .function("dumpHex", select_overload<void() const>(&SkPath::dumpHex))
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400534#endif
535 ;
Kevin Lubick22647d02018-07-06 14:31:23 -0400536
537 class_<SkOpBuilder>("SkOpBuilder")
538 .constructor<>()
539
Kevin Lubick641bf872018-08-06 14:49:39 -0400540 .function("add", &SkOpBuilder::add)
Kevin Lubickd9936482018-08-24 10:44:16 -0400541 .function("make", &ResolveBuilder)
Kevin Lubick641bf872018-08-06 14:49:39 -0400542 .function("resolve", &ResolveBuilder);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400543
544 // Without these function() bindings, the function would be exposed but oblivious to
545 // our types (e.g. SkPath)
546
547 // Import
548 function("FromSVGString", &FromSVGString);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400549 function("NewPath", &NewPath);
Kevin Lubick084d9962018-08-15 13:28:27 -0400550 function("NewPath", &CopyPath);
Kevin Lubickd9936482018-08-24 10:44:16 -0400551 // FromCmds is defined in helper.js to make use of TypedArrays transparent.
552 function("_FromCmds", &FromCmds);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400553 // Path2D is opaque, so we can't read in from it.
554
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400555 // PathOps
Kevin Lubickd9936482018-08-24 10:44:16 -0400556 function("MakeFromOp", &MakeFromOp);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400557
558 enum_<SkPathOp>("PathOp")
559 .value("DIFFERENCE", SkPathOp::kDifference_SkPathOp)
560 .value("INTERSECT", SkPathOp::kIntersect_SkPathOp)
561 .value("UNION", SkPathOp::kUnion_SkPathOp)
562 .value("XOR", SkPathOp::kXOR_SkPathOp)
563 .value("REVERSE_DIFFERENCE", SkPathOp::kReverseDifference_SkPathOp);
564
Kevin Lubick644d8e72018-08-09 13:58:04 -0400565 enum_<SkPath::FillType>("FillType")
566 .value("WINDING", SkPath::FillType::kWinding_FillType)
567 .value("EVENODD", SkPath::FillType::kEvenOdd_FillType)
568 .value("INVERSE_WINDING", SkPath::FillType::kInverseWinding_FillType)
569 .value("INVERSE_EVENODD", SkPath::FillType::kInverseEvenOdd_FillType);
570
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400571 constant("MOVE_VERB", MOVE);
572 constant("LINE_VERB", LINE);
573 constant("QUAD_VERB", QUAD);
Kevin Lubick97d6d982018-08-10 15:53:16 -0400574 constant("CONIC_VERB", CONIC);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400575 constant("CUBIC_VERB", CUBIC);
576 constant("CLOSE_VERB", CLOSE);
577
Kevin Lubick644d8e72018-08-09 13:58:04 -0400578 // A value object is much simpler than a class - it is returned as a JS
579 // object and does not require delete().
580 // https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/embind.html#value-types
581 value_object<SkRect>("SkRect")
582 .field("fLeft", &SkRect::fLeft)
583 .field("fTop", &SkRect::fTop)
584 .field("fRight", &SkRect::fRight)
585 .field("fBottom", &SkRect::fBottom);
586
Kevin Lubickd9936482018-08-24 10:44:16 -0400587 function("LTRBRect", &SkRect::MakeLTRB);
Kevin Lubick644d8e72018-08-09 13:58:04 -0400588
Kevin Lubick97d6d982018-08-10 15:53:16 -0400589 // Stroke
590 enum_<SkPaint::Join>("StrokeJoin")
591 .value("MITER", SkPaint::Join::kMiter_Join)
592 .value("ROUND", SkPaint::Join::kRound_Join)
593 .value("BEVEL", SkPaint::Join::kBevel_Join);
594
595 enum_<SkPaint::Cap>("StrokeCap")
596 .value("BUTT", SkPaint::Cap::kButt_Cap)
597 .value("ROUND", SkPaint::Cap::kRound_Cap)
598 .value("SQUARE", SkPaint::Cap::kSquare_Cap);
599
Kevin Lubick11194ab2018-08-17 13:52:56 -0400600 value_object<StrokeOpts>("StrokeOpts")
601 .field("width", &StrokeOpts::width)
602 .field("miter_limit", &StrokeOpts::miter_limit)
603 .field("join", &StrokeOpts::join)
604 .field("cap", &StrokeOpts::cap);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400605
Kevin Lubick084d9962018-08-15 13:28:27 -0400606 // Matrix
607 // Allows clients to supply a 1D array of 9 elements and the bindings
608 // will automatically turn it into a 3x3 2D matrix.
609 // e.g. path.transform([0,1,2,3,4,5,6,7,8])
610 // This is likely simpler for the client than exposing SkMatrix
611 // directly and requiring them to do a lot of .delete().
612 value_array<SimpleMatrix>("SkMatrix")
613 .element(&SimpleMatrix::scaleX)
614 .element(&SimpleMatrix::skewX)
615 .element(&SimpleMatrix::transX)
616
617 .element(&SimpleMatrix::skewY)
618 .element(&SimpleMatrix::scaleY)
619 .element(&SimpleMatrix::transY)
620
621 .element(&SimpleMatrix::pers0)
622 .element(&SimpleMatrix::pers1)
623 .element(&SimpleMatrix::pers2);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400624
Kevin Lubick644d8e72018-08-09 13:58:04 -0400625 // Test Utils
626 function("SkBits2FloatUnsigned", &SkBits2FloatUnsigned);
Kevin Lubick22647d02018-07-06 14:31:23 -0400627}