blob: ab68b795daeef4cda1baaeb4ffe959608809cccb [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 Lubick92eaa3c2018-07-16 21:00:52 -04008#include "SkFloatBits.h"
Kevin Lubick22647d02018-07-06 14:31:23 -04009#include "SkFloatingPoint.h"
10#include "SkParsePath.h"
11#include "SkPath.h"
12#include "SkPathOps.h"
Kevin Lubick92eaa3c2018-07-16 21:00:52 -040013#include "SkRegion.h"
Kevin Lubick22647d02018-07-06 14:31:23 -040014#include "SkString.h"
15
16#include <emscripten/emscripten.h>
17#include <emscripten/bind.h>
18
19using namespace emscripten;
20
21static const int MOVE = 0;
22static const int LINE = 1;
23static const int QUAD = 2;
24static const int CUBIC = 4;
25static const int CLOSE = 5;
26
27// =================================================================================
28// Creating/Exporting Paths
29// =================================================================================
30
Florin Malitae1824da2018-07-12 10:33:39 -040031template <typename VisitFunc>
32void VisitPath(const SkPath& p, VisitFunc&& f) {
33 SkPath::RawIter iter(p);
Kevin Lubick22647d02018-07-06 14:31:23 -040034 SkPoint pts[4];
35 SkPath::Verb verb;
Florin Malitae1824da2018-07-12 10:33:39 -040036 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
37 f(verb, pts);
Kevin Lubick22647d02018-07-06 14:31:23 -040038 }
39}
40
Florin Malitae1824da2018-07-12 10:33:39 -040041void EMSCRIPTEN_KEEPALIVE SkPathToVerbsArgsArray(const SkPath& path,
42 emscripten::val /*Array*/ verbs,
43 emscripten::val /*Array*/ args) {
44 VisitPath(path, [&verbs, &args](SkPath::Verb verb, const SkPoint pts[4]) {
45 switch (verb) {
46 case SkPath::kMove_Verb:
47 verbs.call<void>("push", MOVE);
48 args.call<void>("push", pts[0].x(), pts[0].y());
49 break;
50 case SkPath::kLine_Verb:
51 verbs.call<void>("push", LINE);
52 args.call<void>("push", pts[1].x(), pts[1].y());
53 break;
54 case SkPath::kQuad_Verb:
55 verbs.call<void>("push", QUAD);
56 args.call<void>("push", pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
57 break;
58 case SkPath::kConic_Verb:
59 printf("unsupported conic verb\n");
60 // TODO(kjlubick): Port in the logic from SkParsePath::ToSVGString?
61 break;
62 case SkPath::kCubic_Verb:
63 verbs.call<void>("push", CUBIC);
64 args.call<void>("push",
65 pts[1].x(), pts[1].y(),
66 pts[2].x(), pts[2].y(),
67 pts[3].x(), pts[3].y());
68 break;
69 case SkPath::kClose_Verb:
70 verbs.call<void>("push", CLOSE);
71 break;
72 case SkPath::kDone_Verb:
73 SkASSERT(false);
74 break;
75 }
76 });
77}
78
Kevin Lubick22647d02018-07-06 14:31:23 -040079emscripten::val JSArray = emscripten::val::global("Array");
80
81emscripten::val EMSCRIPTEN_KEEPALIVE SkPathToCmdArray(SkPath path) {
82 val cmds = JSArray.new_();
83
Florin Malitae1824da2018-07-12 10:33:39 -040084 VisitPath(path, [&cmds](SkPath::Verb verb, const SkPoint pts[4]) {
Kevin Lubick22647d02018-07-06 14:31:23 -040085 val cmd = JSArray.new_();
86 switch (verb) {
Florin Malitae1824da2018-07-12 10:33:39 -040087 case SkPath::kMove_Verb:
88 cmd.call<void>("push", MOVE, pts[0].x(), pts[0].y());
89 break;
90 case SkPath::kLine_Verb:
91 cmd.call<void>("push", LINE, pts[1].x(), pts[1].y());
92 break;
93 case SkPath::kQuad_Verb:
94 cmd.call<void>("push", QUAD, pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
95 break;
96 case SkPath::kConic_Verb:
97 printf("unsupported conic verb\n");
98 // TODO(kjlubick): Port in the logic from SkParsePath::ToSVGString?
99 break;
100 case SkPath::kCubic_Verb:
101 cmd.call<void>("push", CUBIC,
102 pts[1].x(), pts[1].y(),
103 pts[2].x(), pts[2].y(),
104 pts[3].x(), pts[3].y());
105 break;
106 case SkPath::kClose_Verb:
107 cmd.call<void>("push", CLOSE);
108 break;
109 case SkPath::kDone_Verb:
110 SkASSERT(false);
111 break;
Kevin Lubick22647d02018-07-06 14:31:23 -0400112 }
113 cmds.call<void>("push", cmd);
Florin Malitae1824da2018-07-12 10:33:39 -0400114 });
Kevin Lubick22647d02018-07-06 14:31:23 -0400115 return cmds;
116}
117
118// This type signature is a mess, but it's necessary. See, we can't use "bind" (EMSCRIPTEN_BINDINGS)
119// and pointers to primitive types (Only bound types like SkPoint). We could if we used
120// cwrap (see https://becominghuman.ai/passing-and-returning-webassembly-array-parameters-a0f572c65d97)
121// but that requires us to stick to C code and, AFAIK, doesn't allow us to return nice things like
122// SkPath or SkOpBuilder.
123//
124// So, basically, if we are using C++ and EMSCRIPTEN_BINDINGS, we can't have primative pointers
125// in our function type signatures. (this gives an error message like "Cannot call foo due to unbound
126// types Pi, Pf"). But, we can just pretend they are numbers and cast them to be pointers and
127// the compiler is happy.
Florin Malitae1824da2018-07-12 10:33:39 -0400128SkPath EMSCRIPTEN_KEEPALIVE SkPathFromVerbsArgsTyped(uintptr_t /* uint8_t* */ vptr, int numVerbs,
129 uintptr_t /* float* */ aptr, int numArgs) {
130 const auto* verbs = reinterpret_cast<const uint8_t*>(vptr);
131 const auto* args = reinterpret_cast<const float*>(aptr);
Kevin Lubick22647d02018-07-06 14:31:23 -0400132 SkPath path;
133 int argsIndex = 0;
134 float x1, y1, x2, y2, x3, y3;
135
136 // if there are not enough arguments, bail with the path we've constructed so far.
137 #define CHECK_NUM_ARGS(n) \
138 if ((argsIndex + n) > numArgs) { \
139 SkDebugf("Not enough args to match the verbs. Saw %d args\n", numArgs); \
140 return path; \
141 }
142
143 for(int i = 0; i < numVerbs; i++){
144 switch (verbs[i]) {
145 case MOVE:
146 CHECK_NUM_ARGS(2);
147 x1 = args[argsIndex++], y1 = args[argsIndex++];
148 path.moveTo(x1, y1);
149 break;
150 case LINE:
151 CHECK_NUM_ARGS(2);
152 x1 = args[argsIndex++], y1 = args[argsIndex++];
153 path.lineTo(x1, y1);
154 break;
155 case QUAD:
156 CHECK_NUM_ARGS(4);
157 x1 = args[argsIndex++], y1 = args[argsIndex++];
158 x2 = args[argsIndex++], y2 = args[argsIndex++];
159 path.quadTo(x1, y1, x2, y2);
160 break;
161 case CUBIC:
162 CHECK_NUM_ARGS(6);
163 x1 = args[argsIndex++], y1 = args[argsIndex++];
164 x2 = args[argsIndex++], y2 = args[argsIndex++];
165 x3 = args[argsIndex++], y3 = args[argsIndex++];
166 path.cubicTo(x1, y1, x2, y2, x3, y3);
167 break;
168 case CLOSE:
169 path.close();
170 break;
171 default:
172 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verbs[i]);
173 return path;
174 }
175 }
176
177 #undef CHECK_NUM_ARGS
178
179 return path;
180}
181
182// See above comment for rational of pointer mess
Florin Malitae1824da2018-07-12 10:33:39 -0400183SkPath EMSCRIPTEN_KEEPALIVE SkPathFromCmdTyped(uintptr_t /* float* */ cptr, int numCmds) {
184 const auto* cmds = reinterpret_cast<const float*>(cptr);
Kevin Lubick22647d02018-07-06 14:31:23 -0400185 SkPath path;
186 float x1, y1, x2, y2, x3, y3;
187
188 // if there are not enough arguments, bail with the path we've constructed so far.
189 #define CHECK_NUM_ARGS(n) \
190 if ((i + n) > numCmds) { \
191 SkDebugf("Not enough args to match the verbs. Saw %d commands\n", numCmds); \
192 return path; \
193 }
194
195 for(int i = 0; i < numCmds;){
196 switch (sk_float_floor2int(cmds[i++])) {
197 case MOVE:
198 CHECK_NUM_ARGS(2);
199 x1 = cmds[i++], y1 = cmds[i++];
200 path.moveTo(x1, y1);
201 break;
202 case LINE:
203 CHECK_NUM_ARGS(2);
204 x1 = cmds[i++], y1 = cmds[i++];
205 path.lineTo(x1, y1);
206 break;
207 case QUAD:
208 CHECK_NUM_ARGS(4);
209 x1 = cmds[i++], y1 = cmds[i++];
210 x2 = cmds[i++], y2 = cmds[i++];
211 path.quadTo(x1, y1, x2, y2);
212 break;
213 case CUBIC:
214 CHECK_NUM_ARGS(6);
215 x1 = cmds[i++], y1 = cmds[i++];
216 x2 = cmds[i++], y2 = cmds[i++];
217 x3 = cmds[i++], y3 = cmds[i++];
218 path.cubicTo(x1, y1, x2, y2, x3, y3);
219 break;
220 case CLOSE:
221 path.close();
222 break;
223 default:
224 SkDebugf(" path: UNKNOWN command %f, aborting dump...\n", cmds[i-1]);
225 return path;
226 }
227 }
228
229 #undef CHECK_NUM_ARGS
230
231 return path;
232}
233
234//========================================================================================
235// SVG THINGS
236//========================================================================================
237
238val EMSCRIPTEN_KEEPALIVE ToSVGString(SkPath path) {
239 SkString s;
240 SkParsePath::ToSVGString(path, &s);
241 // Wrapping it in val automatically turns it into a JS string.
242 // Not too sure on performance implications, but is is simpler than
243 // returning a raw pointer to const char * and then using
244 // Pointer_stringify() on the calling side.
245 return val(s.c_str());
246}
247
248
249SkPath EMSCRIPTEN_KEEPALIVE FromSVGString(std::string str) {
250 SkPath path;
251 SkParsePath::FromSVGString(str.c_str(), &path);
252 return path;
253}
254
255//========================================================================================
256// PATHOP THINGS
257//========================================================================================
258
259SkPath EMSCRIPTEN_KEEPALIVE SimplifyPath(SkPath path) {
260 SkPath simple;
261 Simplify(path, &simple);
262 return simple;
263}
264
265SkPath EMSCRIPTEN_KEEPALIVE ApplyPathOp(SkPath pathOne, SkPath pathTwo, SkPathOp op) {
266 SkPath path;
267 Op(pathOne, pathTwo, op, &path);
268 return path;
269}
270
271SkPath EMSCRIPTEN_KEEPALIVE ResolveBuilder(SkOpBuilder builder) {
272 SkPath path;
273 builder.resolve(&path);
274 return path;
275}
276
277//========================================================================================
278// Canvas THINGS
279//========================================================================================
280
281emscripten::val EMSCRIPTEN_KEEPALIVE ToPath2D(SkPath path, val/* Path2D&*/ retVal) {
282 SkPath::Iter iter(path, false);
283 SkPoint pts[4];
284 SkPath::Verb verb;
285 while ((verb = iter.next(pts, false)) != SkPath::kDone_Verb) {
286 switch (verb) {
287 case SkPath::kMove_Verb:
288 retVal.call<void>("moveTo", pts[0].x(), pts[0].y());
289 break;
290 case SkPath::kLine_Verb:
291 retVal.call<void>("lineTo", pts[1].x(), pts[1].y());
292 break;
293 case SkPath::kQuad_Verb:
294 retVal.call<void>("quadraticCurveTo", pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
295 break;
296 case SkPath::kConic_Verb:
297 printf("unsupported conic verb\n");
298 // TODO(kjlubick): Port in the logic from SkParsePath::ToSVGString?
299 break;
300 case SkPath::kCubic_Verb:
301 retVal.call<void>("bezierCurveTo", pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y(),
302 pts[3].x(), pts[3].y());
303 break;
304 case SkPath::kClose_Verb:
305 retVal.call<void>("closePath");
306 break;
307 case SkPath::kDone_Verb:
308 break;
309 }
310 }
311 return retVal;
312}
313
Kevin Lubick92eaa3c2018-07-16 21:00:52 -0400314//========================================================================================
315// Region things
316//========================================================================================
317
318SkPath GetBoundaryPathFromRegion(SkRegion region) {
319 SkPath p;
320 region.getBoundaryPath(&p);
321 return p;
322}
323
Kevin Lubick22647d02018-07-06 14:31:23 -0400324// Binds the classes to the JS
325EMSCRIPTEN_BINDINGS(skia) {
326 class_<SkPath>("SkPath")
327 .constructor<>()
328
329 .function("moveTo",
330 select_overload<void(SkScalar, SkScalar)>(&SkPath::moveTo))
331 .function("lineTo",
332 select_overload<void(SkScalar, SkScalar)>(&SkPath::lineTo))
333 .function("quadTo",
334 select_overload<void(SkScalar, SkScalar, SkScalar, SkScalar)>(&SkPath::quadTo))
335 .function("cubicTo",
336 select_overload<void(SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&SkPath::cubicTo))
Kevin Lubick92eaa3c2018-07-16 21:00:52 -0400337 .function("close", &SkPath::close)
Kevin Lubick22647d02018-07-06 14:31:23 -0400338 // Uncomment below for debugging.
Kevin Lubick92eaa3c2018-07-16 21:00:52 -0400339 .function("dump", select_overload<void() const>(&SkPath::dump));
Kevin Lubick22647d02018-07-06 14:31:23 -0400340
341 class_<SkOpBuilder>("SkOpBuilder")
342 .constructor<>()
343
344 .function("add", &SkOpBuilder::add);
345
Kevin Lubick92eaa3c2018-07-16 21:00:52 -0400346 class_<SkRegion>("SkRegion")
347 .constructor<>()
348
349 .function("setRect",
350 select_overload<bool(int32_t, int32_t, int32_t, int32_t)>(&SkRegion::setRect))
351 .function("setPath", &SkRegion::setPath)
352 .function("opLTRB",
353 select_overload<bool(int32_t, int32_t, int32_t, int32_t, SkRegion::Op)>(&SkRegion::op))
354 .function("opRegion",
355 select_overload<bool(const SkRegion&, SkRegion::Op)>(&SkRegion::op))
356 .function("opRegionAB",
357 select_overload<bool(const SkRegion&, const SkRegion&, SkRegion::Op)>(&SkRegion::op))
358 ;
359
360
Kevin Lubick22647d02018-07-06 14:31:23 -0400361 // Without this, module._ToPath2D (yes with an underscore)
362 // would be exposed, but be unable to correctly handle the SkPath type.
363 function("ToPath2D", &ToPath2D);
364 function("ToSVGString", &ToSVGString);
365 function("FromSVGString", &FromSVGString);
366
367 function("SkPathToVerbsArgsArray", &SkPathToVerbsArgsArray);
368 function("SkPathFromVerbsArgsTyped", &SkPathFromVerbsArgsTyped);
369
370 function("SkPathFromCmdTyped", &SkPathFromCmdTyped);
371 function("SkPathToCmdArray", &SkPathToCmdArray);
372
373 function("SimplifyPath", &SimplifyPath);
374 function("ApplyPathOp", &ApplyPathOp);
375 function("ResolveBuilder", &ResolveBuilder);
376
Kevin Lubick92eaa3c2018-07-16 21:00:52 -0400377 function("SkBits2Float", &SkBits2Float);
378
379 function("GetBoundaryPathFromRegion", &GetBoundaryPathFromRegion);
380
Kevin Lubick22647d02018-07-06 14:31:23 -0400381 enum_<SkPathOp>("PathOp")
382 .value("DIFFERENCE", SkPathOp::kDifference_SkPathOp)
383 .value("INTERSECT", SkPathOp::kIntersect_SkPathOp)
384 .value("UNION", SkPathOp::kUnion_SkPathOp)
385 .value("XOR", SkPathOp::kXOR_SkPathOp)
386 .value("REVERSE_DIFFERENCE", SkPathOp::kReverseDifference_SkPathOp);
387
Kevin Lubick92eaa3c2018-07-16 21:00:52 -0400388 enum_<SkRegion::Op>("RegionOp")
389 .value("DIFFERENCE", SkRegion::Op::kDifference_Op)
390 .value("INTERSECT", SkRegion::Op::kIntersect_Op)
391 .value("UNION", SkRegion::Op::kUnion_Op)
392 .value("XOR", SkRegion::Op::kXOR_Op)
393 .value("REVERSE_DIFFERENCE", SkRegion::Op::kReverseDifference_Op)
394 .value("REPLACE", SkRegion::Op::kReplace_Op);
395
Kevin Lubick22647d02018-07-06 14:31:23 -0400396 constant("MOVE_VERB", MOVE);
397 constant("LINE_VERB", LINE);
398 constant("QUAD_VERB", QUAD);
399 constant("CUBIC_VERB", CUBIC);
400 constant("CLOSE_VERB", CLOSE);
401}