blob: 38af2c60711857b2f12d7e118e16c625f5d45176 [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 Lubick774be5a2018-09-07 14:00:41 -040017#include "SkCubicMap.h"
Kevin Lubick641bf872018-08-06 14:49:39 -040018#include "SkRect.h"
Kevin Lubick11194ab2018-08-17 13:52:56 -040019#include "SkPaintDefaults.h"
Kevin Lubick22647d02018-07-06 14:31:23 -040020#include "SkString.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 Lubick5f0e3a12018-08-07 11:30:12 -040041
Kevin Lubick22647d02018-07-06 14:31:23 -040042// =================================================================================
Kevin Lubick641bf872018-08-06 14:49:39 -040043// Creating/Exporting Paths with cmd arrays
Kevin Lubick22647d02018-07-06 14:31:23 -040044// =================================================================================
45
Florin Malitae1824da2018-07-12 10:33:39 -040046template <typename VisitFunc>
47void VisitPath(const SkPath& p, VisitFunc&& f) {
48 SkPath::RawIter iter(p);
Kevin Lubick22647d02018-07-06 14:31:23 -040049 SkPoint pts[4];
50 SkPath::Verb verb;
Florin Malitae1824da2018-07-12 10:33:39 -040051 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
Kevin Lubick641bf872018-08-06 14:49:39 -040052 f(verb, pts, iter);
Kevin Lubick22647d02018-07-06 14:31:23 -040053 }
54}
55
Kevin Lubick641bf872018-08-06 14:49:39 -040056emscripten::val EMSCRIPTEN_KEEPALIVE ToCmds(const SkPath& path) {
Kevin Lubick5f0e3a12018-08-07 11:30:12 -040057 emscripten::val cmds = emscripten::val::array();
Kevin Lubick22647d02018-07-06 14:31:23 -040058
Kevin Lubick641bf872018-08-06 14:49:39 -040059 VisitPath(path, [&cmds](SkPath::Verb verb, const SkPoint pts[4], SkPath::RawIter iter) {
Kevin Lubick5f0e3a12018-08-07 11:30:12 -040060 emscripten::val cmd = emscripten::val::array();
Kevin Lubick22647d02018-07-06 14:31:23 -040061 switch (verb) {
Florin Malitae1824da2018-07-12 10:33:39 -040062 case SkPath::kMove_Verb:
63 cmd.call<void>("push", MOVE, pts[0].x(), pts[0].y());
64 break;
65 case SkPath::kLine_Verb:
66 cmd.call<void>("push", LINE, pts[1].x(), pts[1].y());
67 break;
68 case SkPath::kQuad_Verb:
69 cmd.call<void>("push", QUAD, pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
70 break;
71 case SkPath::kConic_Verb:
Kevin Lubick97d6d982018-08-10 15:53:16 -040072 cmd.call<void>("push", CONIC,
73 pts[1].x(), pts[1].y(),
74 pts[2].x(), pts[2].y(), iter.conicWeight());
Florin Malitae1824da2018-07-12 10:33:39 -040075 break;
76 case SkPath::kCubic_Verb:
77 cmd.call<void>("push", CUBIC,
78 pts[1].x(), pts[1].y(),
79 pts[2].x(), pts[2].y(),
80 pts[3].x(), pts[3].y());
81 break;
82 case SkPath::kClose_Verb:
83 cmd.call<void>("push", CLOSE);
84 break;
85 case SkPath::kDone_Verb:
86 SkASSERT(false);
87 break;
Kevin Lubick22647d02018-07-06 14:31:23 -040088 }
89 cmds.call<void>("push", cmd);
Florin Malitae1824da2018-07-12 10:33:39 -040090 });
Kevin Lubick22647d02018-07-06 14:31:23 -040091 return cmds;
92}
93
94// This type signature is a mess, but it's necessary. See, we can't use "bind" (EMSCRIPTEN_BINDINGS)
95// and pointers to primitive types (Only bound types like SkPoint). We could if we used
96// cwrap (see https://becominghuman.ai/passing-and-returning-webassembly-array-parameters-a0f572c65d97)
97// but that requires us to stick to C code and, AFAIK, doesn't allow us to return nice things like
98// SkPath or SkOpBuilder.
99//
100// So, basically, if we are using C++ and EMSCRIPTEN_BINDINGS, we can't have primative pointers
101// in our function type signatures. (this gives an error message like "Cannot call foo due to unbound
102// types Pi, Pf"). But, we can just pretend they are numbers and cast them to be pointers and
103// the compiler is happy.
Kevin Lubick97d6d982018-08-10 15:53:16 -0400104SkPathOrNull EMSCRIPTEN_KEEPALIVE FromCmds(uintptr_t /* float* */ cptr, int numCmds) {
Florin Malitae1824da2018-07-12 10:33:39 -0400105 const auto* cmds = reinterpret_cast<const float*>(cptr);
Kevin Lubick22647d02018-07-06 14:31:23 -0400106 SkPath path;
107 float x1, y1, x2, y2, x3, y3;
108
109 // if there are not enough arguments, bail with the path we've constructed so far.
110 #define CHECK_NUM_ARGS(n) \
111 if ((i + n) > numCmds) { \
112 SkDebugf("Not enough args to match the verbs. Saw %d commands\n", numCmds); \
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400113 return emscripten::val::null(); \
Kevin Lubick22647d02018-07-06 14:31:23 -0400114 }
115
116 for(int i = 0; i < numCmds;){
117 switch (sk_float_floor2int(cmds[i++])) {
118 case MOVE:
119 CHECK_NUM_ARGS(2);
120 x1 = cmds[i++], y1 = cmds[i++];
121 path.moveTo(x1, y1);
122 break;
123 case LINE:
124 CHECK_NUM_ARGS(2);
125 x1 = cmds[i++], y1 = cmds[i++];
126 path.lineTo(x1, y1);
127 break;
128 case QUAD:
129 CHECK_NUM_ARGS(4);
130 x1 = cmds[i++], y1 = cmds[i++];
131 x2 = cmds[i++], y2 = cmds[i++];
132 path.quadTo(x1, y1, x2, y2);
133 break;
Kevin Lubickc623af22018-08-17 14:42:53 -0400134 case CONIC:
Kevin Lubickc6c48aa2018-08-17 15:00:43 -0400135 CHECK_NUM_ARGS(5);
Kevin Lubickc623af22018-08-17 14:42:53 -0400136 x1 = cmds[i++], y1 = cmds[i++];
137 x2 = cmds[i++], y2 = cmds[i++];
Kevin Lubickd9936482018-08-24 10:44:16 -0400138 x3 = cmds[i++]; // weight
Kevin Lubickc623af22018-08-17 14:42:53 -0400139 path.conicTo(x1, y1, x2, y2, x3);
140 break;
Kevin Lubick22647d02018-07-06 14:31:23 -0400141 case CUBIC:
142 CHECK_NUM_ARGS(6);
143 x1 = cmds[i++], y1 = cmds[i++];
144 x2 = cmds[i++], y2 = cmds[i++];
145 x3 = cmds[i++], y3 = cmds[i++];
146 path.cubicTo(x1, y1, x2, y2, x3, y3);
147 break;
148 case CLOSE:
149 path.close();
150 break;
151 default:
152 SkDebugf(" path: UNKNOWN command %f, aborting dump...\n", cmds[i-1]);
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400153 return emscripten::val::null();
Kevin Lubick22647d02018-07-06 14:31:23 -0400154 }
155 }
156
157 #undef CHECK_NUM_ARGS
158
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400159 return emscripten::val(path);
Kevin Lubick22647d02018-07-06 14:31:23 -0400160}
161
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400162SkPath EMSCRIPTEN_KEEPALIVE NewPath() {
163 return SkPath();
164}
165
Kevin Lubick084d9962018-08-15 13:28:27 -0400166SkPath EMSCRIPTEN_KEEPALIVE CopyPath(const SkPath& a) {
167 SkPath copy(a);
168 return copy;
169}
170
171bool EMSCRIPTEN_KEEPALIVE Equals(const SkPath& a, const SkPath& b) {
172 return a == b;
173}
174
Kevin Lubick22647d02018-07-06 14:31:23 -0400175//========================================================================================
Kevin Lubick11194ab2018-08-17 13:52:56 -0400176// Path things
177//========================================================================================
178
179// All these Apply* methods are simple wrappers to avoid returning an object.
180// The default WASM bindings produce code that will leak if a return value
181// isn't assigned to a JS variable and has delete() called on it.
182// These Apply methods, combined with the smarter binding code allow for chainable
183// commands that don't leak if the return value is ignored (i.e. when used intuitively).
184
185void ApplyArcTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
186 SkScalar radius) {
187 p.arcTo(x1, y1, x2, y2, radius);
188}
189
190void ApplyClose(SkPath& p) {
191 p.close();
192}
193
194void ApplyConicTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
195 SkScalar w) {
196 p.conicTo(x1, y1, x2, y2, w);
197}
198
199void ApplyCubicTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
200 SkScalar x3, SkScalar y3) {
201 p.cubicTo(x1, y1, x2, y2, x3, y3);
202}
203
204void ApplyLineTo(SkPath& p, SkScalar x, SkScalar y) {
205 p.lineTo(x, y);
206}
207
208void ApplyMoveTo(SkPath& p, SkScalar x, SkScalar y) {
209 p.moveTo(x, y);
210}
211
212void ApplyQuadTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
213 p.quadTo(x1, y1, x2, y2);
214}
215
216
217
218//========================================================================================
Kevin Lubick641bf872018-08-06 14:49:39 -0400219// SVG things
Kevin Lubick22647d02018-07-06 14:31:23 -0400220//========================================================================================
221
Kevin Lubick97d6d982018-08-10 15:53:16 -0400222JSString EMSCRIPTEN_KEEPALIVE ToSVGString(const SkPath& path) {
Kevin Lubick22647d02018-07-06 14:31:23 -0400223 SkString s;
224 SkParsePath::ToSVGString(path, &s);
225 // Wrapping it in val automatically turns it into a JS string.
226 // Not too sure on performance implications, but is is simpler than
227 // returning a raw pointer to const char * and then using
228 // Pointer_stringify() on the calling side.
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400229 return emscripten::val(s.c_str());
Kevin Lubick22647d02018-07-06 14:31:23 -0400230}
231
232
Kevin Lubick97d6d982018-08-10 15:53:16 -0400233SkPathOrNull EMSCRIPTEN_KEEPALIVE FromSVGString(std::string str) {
Kevin Lubick22647d02018-07-06 14:31:23 -0400234 SkPath path;
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400235 if (SkParsePath::FromSVGString(str.c_str(), &path)) {
236 return emscripten::val(path);
237 }
238 return emscripten::val::null();
Kevin Lubick22647d02018-07-06 14:31:23 -0400239}
240
241//========================================================================================
Kevin Lubick641bf872018-08-06 14:49:39 -0400242// PATHOP things
Kevin Lubick22647d02018-07-06 14:31:23 -0400243//========================================================================================
244
Kevin Lubick11194ab2018-08-17 13:52:56 -0400245bool EMSCRIPTEN_KEEPALIVE ApplySimplify(SkPath& path) {
246 return Simplify(path, &path);
Kevin Lubick22647d02018-07-06 14:31:23 -0400247}
248
Kevin Lubick11194ab2018-08-17 13:52:56 -0400249bool EMSCRIPTEN_KEEPALIVE ApplyPathOp(SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
250 return Op(pathOne, pathTwo, op, &pathOne);
Kevin Lubick22647d02018-07-06 14:31:23 -0400251}
252
Kevin Lubickd9936482018-08-24 10:44:16 -0400253SkPathOrNull EMSCRIPTEN_KEEPALIVE MakeFromOp(const SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
254 SkPath out;
255 if (Op(pathOne, pathTwo, op, &out)) {
256 return emscripten::val(out);
257 }
258 return emscripten::val::null();
259}
260
Kevin Lubick97d6d982018-08-10 15:53:16 -0400261SkPathOrNull EMSCRIPTEN_KEEPALIVE ResolveBuilder(SkOpBuilder& builder) {
Kevin Lubick22647d02018-07-06 14:31:23 -0400262 SkPath path;
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400263 if (builder.resolve(&path)) {
264 return emscripten::val(path);
265 }
266 return emscripten::val::null();
Kevin Lubick22647d02018-07-06 14:31:23 -0400267}
268
269//========================================================================================
Kevin Lubick641bf872018-08-06 14:49:39 -0400270// Canvas things
Kevin Lubick22647d02018-07-06 14:31:23 -0400271//========================================================================================
272
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400273void EMSCRIPTEN_KEEPALIVE ToCanvas(const SkPath& path, emscripten::val /* Path2D or Canvas*/ ctx) {
Kevin Lubick22647d02018-07-06 14:31:23 -0400274 SkPath::Iter iter(path, false);
275 SkPoint pts[4];
276 SkPath::Verb verb;
277 while ((verb = iter.next(pts, false)) != SkPath::kDone_Verb) {
278 switch (verb) {
279 case SkPath::kMove_Verb:
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400280 ctx.call<void>("moveTo", pts[0].x(), pts[0].y());
Kevin Lubick22647d02018-07-06 14:31:23 -0400281 break;
282 case SkPath::kLine_Verb:
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400283 ctx.call<void>("lineTo", pts[1].x(), pts[1].y());
Kevin Lubick22647d02018-07-06 14:31:23 -0400284 break;
285 case SkPath::kQuad_Verb:
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400286 ctx.call<void>("quadraticCurveTo", pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
Kevin Lubick22647d02018-07-06 14:31:23 -0400287 break;
288 case SkPath::kConic_Verb:
Kevin Lubick641bf872018-08-06 14:49:39 -0400289 SkPoint quads[5];
290 // approximate with 2^1=2 quads.
291 SkPath::ConvertConicToQuads(pts[0], pts[1], pts[2], iter.conicWeight(), quads, 1);
Kevin Lubick641bf872018-08-06 14:49:39 -0400292 ctx.call<void>("quadraticCurveTo", quads[1].x(), quads[1].y(), quads[2].x(), quads[2].y());
293 ctx.call<void>("quadraticCurveTo", quads[3].x(), quads[3].y(), quads[4].x(), quads[4].y());
Kevin Lubick22647d02018-07-06 14:31:23 -0400294 break;
295 case SkPath::kCubic_Verb:
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400296 ctx.call<void>("bezierCurveTo", pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y(),
Kevin Lubick22647d02018-07-06 14:31:23 -0400297 pts[3].x(), pts[3].y());
298 break;
299 case SkPath::kClose_Verb:
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400300 ctx.call<void>("closePath");
Kevin Lubick22647d02018-07-06 14:31:23 -0400301 break;
302 case SkPath::kDone_Verb:
303 break;
304 }
305 }
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400306}
307
308emscripten::val JSPath2D = emscripten::val::global("Path2D");
309
Kevin Lubick641bf872018-08-06 14:49:39 -0400310emscripten::val EMSCRIPTEN_KEEPALIVE ToPath2D(const SkPath& path) {
Kevin Lubick5f0e3a12018-08-07 11:30:12 -0400311 emscripten::val retVal = JSPath2D.new_();
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400312 ToCanvas(path, retVal);
Kevin Lubick22647d02018-07-06 14:31:23 -0400313 return retVal;
314}
315
Kevin Lubick641bf872018-08-06 14:49:39 -0400316// ======================================================================================
317// Path2D API things
318// ======================================================================================
Kevin Lubick11194ab2018-08-17 13:52:56 -0400319void ApplyAddRect(SkPath& path, SkScalar x, SkScalar y, SkScalar width, SkScalar height) {
Kevin Lubick641bf872018-08-06 14:49:39 -0400320 path.addRect(x, y, x+width, y+height);
321}
322
Kevin Lubick11194ab2018-08-17 13:52:56 -0400323void ApplyAddArc(SkPath& path, SkScalar x, SkScalar y, SkScalar radius,
324 SkScalar startAngle, SkScalar endAngle, bool ccw) {
Kevin Lubick641bf872018-08-06 14:49:39 -0400325 SkPath temp;
326 SkRect bounds = SkRect::MakeLTRB(x-radius, y-radius, x+radius, y+radius);
327 const auto sweep = SkRadiansToDegrees(endAngle - startAngle) - 360 * ccw;
328 temp.addArc(bounds, SkRadiansToDegrees(startAngle), sweep);
329 path.addPath(temp, SkPath::kExtend_AddPathMode);
330}
331
Kevin Lubick11194ab2018-08-17 13:52:56 -0400332void ApplyEllipse(SkPath& path, SkScalar x, SkScalar y, SkScalar radiusX, SkScalar radiusY,
Kevin Lubick641bf872018-08-06 14:49:39 -0400333 SkScalar rotation, SkScalar startAngle, SkScalar endAngle, bool ccw) {
334 // This is easiest to do by making a new path and then extending the current path
335 // (this properly catches the cases of if there's a moveTo before this call or not).
336 SkRect bounds = SkRect::MakeLTRB(x-radiusX, y-radiusY, x+radiusX, y+radiusY);
337 SkPath temp;
338 const auto sweep = SkRadiansToDegrees(endAngle - startAngle) - (360 * ccw);
339 temp.addArc(bounds, SkRadiansToDegrees(startAngle), sweep);
340
341 SkMatrix m;
342 m.setRotate(SkRadiansToDegrees(rotation), x, y);
343 path.addPath(temp, m, SkPath::kExtend_AddPathMode);
344}
345
Kevin Lubick641bf872018-08-06 14:49:39 -0400346// Allows for full matix control.
Kevin Lubick11194ab2018-08-17 13:52:56 -0400347void ApplyAddPath(SkPath& orig, const SkPath& newPath,
Kevin Lubick641bf872018-08-06 14:49:39 -0400348 SkScalar scaleX, SkScalar skewX, SkScalar transX,
349 SkScalar skewY, SkScalar scaleY, SkScalar transY,
350 SkScalar pers0, SkScalar pers1, SkScalar pers2) {
351 SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX,
352 skewY , scaleY, transY,
353 pers0 , pers1 , pers2);
354 orig.addPath(newPath, m);
355}
356
Kevin Lubick084d9962018-08-15 13:28:27 -0400357JSString GetFillTypeString(const SkPath& path) {
Kevin Lubick97d6d982018-08-10 15:53:16 -0400358 if (path.getFillType() == SkPath::FillType::kWinding_FillType) {
359 return emscripten::val("nonzero");
360 } else if (path.getFillType() == SkPath::FillType::kEvenOdd_FillType) {
361 return emscripten::val("evenodd");
362 } else {
363 SkDebugf("warning: can't translate inverted filltype to HTML Canvas\n");
364 return emscripten::val("nonzero"); //Use default
365 }
366}
367
368//========================================================================================
369// Path Effects
370//========================================================================================
371
Kevin Lubick11194ab2018-08-17 13:52:56 -0400372bool ApplyDash(SkPath& path, SkScalar on, SkScalar off, SkScalar phase) {
Kevin Lubick97d6d982018-08-10 15:53:16 -0400373 SkScalar intervals[] = { on, off };
374 auto pe = SkDashPathEffect::Make(intervals, 2, phase);
375 if (!pe) {
376 SkDebugf("Invalid args to dash()\n");
Kevin Lubick11194ab2018-08-17 13:52:56 -0400377 return false;
Kevin Lubick97d6d982018-08-10 15:53:16 -0400378 }
Kevin Lubick11194ab2018-08-17 13:52:56 -0400379 SkStrokeRec rec(SkStrokeRec::InitStyle::kHairline_InitStyle);
380 if (pe->filterPath(&path, path, &rec, nullptr)) {
381 return true;
Kevin Lubick97d6d982018-08-10 15:53:16 -0400382 }
383 SkDebugf("Could not make dashed path\n");
Kevin Lubick11194ab2018-08-17 13:52:56 -0400384 return false;
Kevin Lubick97d6d982018-08-10 15:53:16 -0400385}
386
Kevin Lubick11194ab2018-08-17 13:52:56 -0400387bool ApplyTrim(SkPath& path, SkScalar startT, SkScalar stopT, bool isComplement) {
Kevin Lubick97d6d982018-08-10 15:53:16 -0400388 auto mode = isComplement ? SkTrimPathEffect::Mode::kInverted : SkTrimPathEffect::Mode::kNormal;
389 auto pe = SkTrimPathEffect::Make(startT, stopT, mode);
390 if (!pe) {
391 SkDebugf("Invalid args to trim(): startT and stopT must be in [0,1]\n");
Kevin Lubick11194ab2018-08-17 13:52:56 -0400392 return false;
Kevin Lubick97d6d982018-08-10 15:53:16 -0400393 }
Kevin Lubick11194ab2018-08-17 13:52:56 -0400394 SkStrokeRec rec(SkStrokeRec::InitStyle::kHairline_InitStyle);
395 if (pe->filterPath(&path, path, &rec, nullptr)) {
396 return true;
Kevin Lubick97d6d982018-08-10 15:53:16 -0400397 }
398 SkDebugf("Could not trim path\n");
Kevin Lubick11194ab2018-08-17 13:52:56 -0400399 return false;
Kevin Lubick97d6d982018-08-10 15:53:16 -0400400}
401
Kevin Lubick11194ab2018-08-17 13:52:56 -0400402struct StrokeOpts {
403 // Default values are set in chaining.js which allows clients
404 // to set any number of them. Otherwise, the binding code complains if
405 // any are omitted.
406 SkScalar width;
407 SkScalar miter_limit;
408 SkPaint::Join join;
409 SkPaint::Cap cap;
410};
Kevin Lubick97d6d982018-08-10 15:53:16 -0400411
Kevin Lubick11194ab2018-08-17 13:52:56 -0400412bool ApplyStroke(SkPath& path, StrokeOpts opts) {
Kevin Lubick97d6d982018-08-10 15:53:16 -0400413 SkPaint p;
414 p.setStyle(SkPaint::kStroke_Style);
Kevin Lubick11194ab2018-08-17 13:52:56 -0400415 p.setStrokeCap(opts.cap);
416 p.setStrokeJoin(opts.join);
417 p.setStrokeWidth(opts.width);
418 p.setStrokeMiter(opts.miter_limit);
Kevin Lubick97d6d982018-08-10 15:53:16 -0400419
Kevin Lubick11194ab2018-08-17 13:52:56 -0400420 return p.getFillPath(path, &path);
Kevin Lubick97d6d982018-08-10 15:53:16 -0400421}
422
Kevin Lubick92eaa3c2018-07-16 21:00:52 -0400423//========================================================================================
Kevin Lubick084d9962018-08-15 13:28:27 -0400424// Matrix things
425//========================================================================================
426
427struct SimpleMatrix {
428 SkScalar scaleX, skewX, transX;
429 SkScalar skewY, scaleY, transY;
430 SkScalar pers0, pers1, pers2;
431};
432
433SkMatrix toSkMatrix(const SimpleMatrix& sm) {
434 return SkMatrix::MakeAll(sm.scaleX, sm.skewX , sm.transX,
435 sm.skewY , sm.scaleY, sm.transY,
436 sm.pers0 , sm.pers1 , sm.pers2);
437}
438
Kevin Lubick11194ab2018-08-17 13:52:56 -0400439void ApplyTransform(SkPath& orig, const SimpleMatrix& sm) {
440 orig.transform(toSkMatrix(sm));
Kevin Lubick084d9962018-08-15 13:28:27 -0400441}
442
Kevin Lubick11194ab2018-08-17 13:52:56 -0400443void ApplyTransform(SkPath& orig,
444 SkScalar scaleX, SkScalar skewX, SkScalar transX,
445 SkScalar skewY, SkScalar scaleY, SkScalar transY,
446 SkScalar pers0, SkScalar pers1, SkScalar pers2) {
Kevin Lubick084d9962018-08-15 13:28:27 -0400447 SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX,
448 skewY , scaleY, transY,
449 pers0 , pers1 , pers2);
Kevin Lubick11194ab2018-08-17 13:52:56 -0400450 orig.transform(m);
Kevin Lubick084d9962018-08-15 13:28:27 -0400451}
452
453//========================================================================================
Kevin Lubick644d8e72018-08-09 13:58:04 -0400454// Testing things
Kevin Lubick92eaa3c2018-07-16 21:00:52 -0400455//========================================================================================
456
Kevin Lubick644d8e72018-08-09 13:58:04 -0400457// The use case for this is on the JS side is something like:
458// PathKit.SkBits2FloatUnsigned(parseInt("0xc0a00000"))
459// to have precise float values for tests. In the C++ tests, we can use SkBits2Float because
460// it takes int32_t, but the JS parseInt basically returns an unsigned int. So, we add in
461// this helper which casts for us on the way to SkBits2Float.
462float SkBits2FloatUnsigned(uint32_t floatAsBits) {
463 return SkBits2Float((int32_t) floatAsBits);
Kevin Lubick92eaa3c2018-07-16 21:00:52 -0400464}
465
Kevin Lubick22647d02018-07-06 14:31:23 -0400466// Binds the classes to the JS
Kevin Lubick641bf872018-08-06 14:49:39 -0400467//
468// See https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/embind.html#non-member-functions-on-the-javascript-prototype
469// for more on binding non-member functions to the JS object, allowing us to rewire
470// various functions. That is, we can make the SkPath we expose appear to have methods
471// that the original SkPath does not, like rect(x, y, width, height) and toPath2D().
472//
473// An important detail for binding non-member functions is that the first argument
474// must be SkPath& (the reference part is very important).
Kevin Lubick11194ab2018-08-17 13:52:56 -0400475//
476// Note that we can't expose default or optional arguments, but we can have multiple
477// declarations of the same function that take different amounts of arguments.
478// For example, see _transform
479// Additionally, we are perfectly happy to handle default arguments and function
480// overloads in the JS glue code (see chaining.js::addPath() for an example).
Kevin Lubick22647d02018-07-06 14:31:23 -0400481EMSCRIPTEN_BINDINGS(skia) {
482 class_<SkPath>("SkPath")
483 .constructor<>()
Kevin Lubick084d9962018-08-15 13:28:27 -0400484 .constructor<const SkPath&>()
Kevin Lubick22647d02018-07-06 14:31:23 -0400485
Kevin Lubick641bf872018-08-06 14:49:39 -0400486 // Path2D API
Kevin Lubick11194ab2018-08-17 13:52:56 -0400487 .function("_addPath", &ApplyAddPath)
488 // 3 additional overloads of addPath are handled in JS bindings
489 .function("_arc", &ApplyAddArc)
490 .function("_arcTo", &ApplyArcTo)
491 //"bezierCurveTo" alias handled in JS bindings
492 .function("_close", &ApplyClose)
Kevin Lubickd9936482018-08-24 10:44:16 -0400493 //"closePath" alias handled in JS bindings
Kevin Lubick11194ab2018-08-17 13:52:56 -0400494 .function("_conicTo", &ApplyConicTo)
495 .function("_cubicTo", &ApplyCubicTo)
Kevin Lubick641bf872018-08-06 14:49:39 -0400496
Kevin Lubick11194ab2018-08-17 13:52:56 -0400497 .function("_ellipse", &ApplyEllipse)
498 .function("_lineTo", &ApplyLineTo)
499 .function("_moveTo", &ApplyMoveTo)
500 // "quadraticCurveTo" alias handled in JS bindings
501 .function("_quadTo", &ApplyQuadTo)
502 .function("_rect", &ApplyAddRect)
Kevin Lubick641bf872018-08-06 14:49:39 -0400503
Kevin Lubick644d8e72018-08-09 13:58:04 -0400504 // Extra features
505 .function("setFillType", &SkPath::setFillType)
506 .function("getFillType", &SkPath::getFillType)
Kevin Lubick084d9962018-08-15 13:28:27 -0400507 .function("getFillTypeString", &GetFillTypeString)
Kevin Lubick644d8e72018-08-09 13:58:04 -0400508 .function("getBounds", &SkPath::getBounds)
509 .function("computeTightBounds", &SkPath::computeTightBounds)
Kevin Lubick084d9962018-08-15 13:28:27 -0400510 .function("equals", &Equals)
511 .function("copy", &CopyPath)
Kevin Lubick644d8e72018-08-09 13:58:04 -0400512
Kevin Lubick97d6d982018-08-10 15:53:16 -0400513 // PathEffects
Kevin Lubick11194ab2018-08-17 13:52:56 -0400514 .function("_dash", &ApplyDash)
515 .function("_trim", &ApplyTrim)
516 .function("_stroke", &ApplyStroke)
Kevin Lubick97d6d982018-08-10 15:53:16 -0400517
Kevin Lubick084d9962018-08-15 13:28:27 -0400518 // Matrix
Kevin Lubick11194ab2018-08-17 13:52:56 -0400519 .function("_transform", select_overload<void(SkPath& orig, const SimpleMatrix& sm)>(&ApplyTransform))
520 .function("_transform", select_overload<void(SkPath& orig, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&ApplyTransform))
Kevin Lubick084d9962018-08-15 13:28:27 -0400521
Kevin Lubick641bf872018-08-06 14:49:39 -0400522 // PathOps
Kevin Lubick11194ab2018-08-17 13:52:56 -0400523 .function("_simplify", &ApplySimplify)
524 .function("_op", &ApplyPathOp)
Kevin Lubick641bf872018-08-06 14:49:39 -0400525
526 // Exporting
527 .function("toCmds", &ToCmds)
528 .function("toPath2D", &ToPath2D)
529 .function("toCanvas", &ToCanvas)
530 .function("toSVGString", &ToSVGString)
531
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400532#ifdef PATHKIT_TESTING
533 .function("dump", select_overload<void() const>(&SkPath::dump))
Kevin Lubick97d6d982018-08-10 15:53:16 -0400534 .function("dumpHex", select_overload<void() const>(&SkPath::dumpHex))
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400535#endif
536 ;
Kevin Lubick22647d02018-07-06 14:31:23 -0400537
538 class_<SkOpBuilder>("SkOpBuilder")
539 .constructor<>()
540
Kevin Lubick641bf872018-08-06 14:49:39 -0400541 .function("add", &SkOpBuilder::add)
Kevin Lubickd9936482018-08-24 10:44:16 -0400542 .function("make", &ResolveBuilder)
Kevin Lubick641bf872018-08-06 14:49:39 -0400543 .function("resolve", &ResolveBuilder);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400544
545 // Without these function() bindings, the function would be exposed but oblivious to
546 // our types (e.g. SkPath)
547
548 // Import
549 function("FromSVGString", &FromSVGString);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400550 function("NewPath", &NewPath);
Kevin Lubick084d9962018-08-15 13:28:27 -0400551 function("NewPath", &CopyPath);
Kevin Lubickd9936482018-08-24 10:44:16 -0400552 // FromCmds is defined in helper.js to make use of TypedArrays transparent.
553 function("_FromCmds", &FromCmds);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400554 // Path2D is opaque, so we can't read in from it.
555
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400556 // PathOps
Kevin Lubickd9936482018-08-24 10:44:16 -0400557 function("MakeFromOp", &MakeFromOp);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400558
559 enum_<SkPathOp>("PathOp")
560 .value("DIFFERENCE", SkPathOp::kDifference_SkPathOp)
561 .value("INTERSECT", SkPathOp::kIntersect_SkPathOp)
562 .value("UNION", SkPathOp::kUnion_SkPathOp)
563 .value("XOR", SkPathOp::kXOR_SkPathOp)
564 .value("REVERSE_DIFFERENCE", SkPathOp::kReverseDifference_SkPathOp);
565
Kevin Lubick644d8e72018-08-09 13:58:04 -0400566 enum_<SkPath::FillType>("FillType")
567 .value("WINDING", SkPath::FillType::kWinding_FillType)
568 .value("EVENODD", SkPath::FillType::kEvenOdd_FillType)
569 .value("INVERSE_WINDING", SkPath::FillType::kInverseWinding_FillType)
570 .value("INVERSE_EVENODD", SkPath::FillType::kInverseEvenOdd_FillType);
571
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400572 constant("MOVE_VERB", MOVE);
573 constant("LINE_VERB", LINE);
574 constant("QUAD_VERB", QUAD);
Kevin Lubick97d6d982018-08-10 15:53:16 -0400575 constant("CONIC_VERB", CONIC);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400576 constant("CUBIC_VERB", CUBIC);
577 constant("CLOSE_VERB", CLOSE);
578
Kevin Lubick644d8e72018-08-09 13:58:04 -0400579 // A value object is much simpler than a class - it is returned as a JS
580 // object and does not require delete().
581 // https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/embind.html#value-types
582 value_object<SkRect>("SkRect")
583 .field("fLeft", &SkRect::fLeft)
584 .field("fTop", &SkRect::fTop)
585 .field("fRight", &SkRect::fRight)
586 .field("fBottom", &SkRect::fBottom);
587
Kevin Lubickd9936482018-08-24 10:44:16 -0400588 function("LTRBRect", &SkRect::MakeLTRB);
Kevin Lubick644d8e72018-08-09 13:58:04 -0400589
Kevin Lubick97d6d982018-08-10 15:53:16 -0400590 // Stroke
591 enum_<SkPaint::Join>("StrokeJoin")
592 .value("MITER", SkPaint::Join::kMiter_Join)
593 .value("ROUND", SkPaint::Join::kRound_Join)
594 .value("BEVEL", SkPaint::Join::kBevel_Join);
595
596 enum_<SkPaint::Cap>("StrokeCap")
597 .value("BUTT", SkPaint::Cap::kButt_Cap)
598 .value("ROUND", SkPaint::Cap::kRound_Cap)
599 .value("SQUARE", SkPaint::Cap::kSquare_Cap);
600
Kevin Lubick11194ab2018-08-17 13:52:56 -0400601 value_object<StrokeOpts>("StrokeOpts")
602 .field("width", &StrokeOpts::width)
603 .field("miter_limit", &StrokeOpts::miter_limit)
604 .field("join", &StrokeOpts::join)
605 .field("cap", &StrokeOpts::cap);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400606
Kevin Lubick084d9962018-08-15 13:28:27 -0400607 // Matrix
608 // Allows clients to supply a 1D array of 9 elements and the bindings
609 // will automatically turn it into a 3x3 2D matrix.
610 // e.g. path.transform([0,1,2,3,4,5,6,7,8])
611 // This is likely simpler for the client than exposing SkMatrix
612 // directly and requiring them to do a lot of .delete().
613 value_array<SimpleMatrix>("SkMatrix")
614 .element(&SimpleMatrix::scaleX)
615 .element(&SimpleMatrix::skewX)
616 .element(&SimpleMatrix::transX)
617
618 .element(&SimpleMatrix::skewY)
619 .element(&SimpleMatrix::scaleY)
620 .element(&SimpleMatrix::transY)
621
622 .element(&SimpleMatrix::pers0)
623 .element(&SimpleMatrix::pers1)
624 .element(&SimpleMatrix::pers2);
Kevin Lubicke1b36fe2018-08-02 11:30:33 -0400625
Kevin Lubick774be5a2018-09-07 14:00:41 -0400626 value_array<SkPoint>("SkPoint")
627 .element(&SkPoint::fX)
628 .element(&SkPoint::fY);
629
630 // Not intended for external clients to call directly.
631 // See helper.js for the client-facing implementation.
632 class_<SkCubicMap>("_SkCubicMap")
633 .constructor<SkPoint, SkPoint>()
634
635 .function("computeYFromX", &SkCubicMap::computeYFromX)
636 .function("computePtFromT", &SkCubicMap::computeFromT);
637
638
Kevin Lubick644d8e72018-08-09 13:58:04 -0400639 // Test Utils
640 function("SkBits2FloatUnsigned", &SkBits2FloatUnsigned);
Kevin Lubick22647d02018-07-06 14:31:23 -0400641}