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