1cb93a386Sopenharmony_ci/*
2cb93a386Sopenharmony_ci * Copyright 2018 Google LLC
3cb93a386Sopenharmony_ci *
4cb93a386Sopenharmony_ci * Use of this source code is governed by a BSD-style license that can be
5cb93a386Sopenharmony_ci * found in the LICENSE file.
6cb93a386Sopenharmony_ci */
7cb93a386Sopenharmony_ci
8cb93a386Sopenharmony_ci#include "include/core/SkCubicMap.h"
9cb93a386Sopenharmony_ci#include "include/core/SkMatrix.h"
10cb93a386Sopenharmony_ci#include "include/core/SkPaint.h"
11cb93a386Sopenharmony_ci#include "include/core/SkPath.h"
12cb93a386Sopenharmony_ci#include "include/core/SkRect.h"
13cb93a386Sopenharmony_ci#include "include/core/SkString.h"
14cb93a386Sopenharmony_ci#include "include/core/SkStrokeRec.h"
15cb93a386Sopenharmony_ci#include "include/effects/SkDashPathEffect.h"
16cb93a386Sopenharmony_ci#include "include/effects/SkTrimPathEffect.h"
17cb93a386Sopenharmony_ci#include "include/pathops/SkPathOps.h"
18cb93a386Sopenharmony_ci#include "include/private/SkFloatBits.h"
19cb93a386Sopenharmony_ci#include "include/private/SkFloatingPoint.h"
20cb93a386Sopenharmony_ci#include "include/utils/SkParsePath.h"
21cb93a386Sopenharmony_ci#include "src/core/SkPaintDefaults.h"
22cb93a386Sopenharmony_ci#include "src/core/SkPathPriv.h"
23cb93a386Sopenharmony_ci
24cb93a386Sopenharmony_ci#include <emscripten/emscripten.h>
25cb93a386Sopenharmony_ci#include <emscripten/bind.h>
26cb93a386Sopenharmony_ci
27cb93a386Sopenharmony_ciusing namespace emscripten;
28cb93a386Sopenharmony_ci
29cb93a386Sopenharmony_cistatic const int MOVE = 0;
30cb93a386Sopenharmony_cistatic const int LINE = 1;
31cb93a386Sopenharmony_cistatic const int QUAD = 2;
32cb93a386Sopenharmony_cistatic const int CONIC = 3;
33cb93a386Sopenharmony_cistatic const int CUBIC = 4;
34cb93a386Sopenharmony_cistatic const int CLOSE = 5;
35cb93a386Sopenharmony_ci
36cb93a386Sopenharmony_ci// Just for self-documenting purposes where the main thing being returned is an
37cb93a386Sopenharmony_ci// SkPath, but in an error case, something of type null (which is val) could also be
38cb93a386Sopenharmony_ci// returned;
39cb93a386Sopenharmony_ciusing SkPathOrNull = emscripten::val;
40cb93a386Sopenharmony_ci// Self-documenting for when we return a string
41cb93a386Sopenharmony_ciusing JSString = emscripten::val;
42cb93a386Sopenharmony_ciusing JSArray = emscripten::val;
43cb93a386Sopenharmony_ci
44cb93a386Sopenharmony_ci// =================================================================================
45cb93a386Sopenharmony_ci// Creating/Exporting Paths with cmd arrays
46cb93a386Sopenharmony_ci// =================================================================================
47cb93a386Sopenharmony_ci
48cb93a386Sopenharmony_ciJSArray EMSCRIPTEN_KEEPALIVE ToCmds(const SkPath& path) {
49cb93a386Sopenharmony_ci    JSArray cmds = emscripten::val::array();
50cb93a386Sopenharmony_ci    for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
51cb93a386Sopenharmony_ci        JSArray cmd = emscripten::val::array();
52cb93a386Sopenharmony_ci        switch (verb) {
53cb93a386Sopenharmony_ci        case SkPathVerb::kMove:
54cb93a386Sopenharmony_ci            cmd.call<void>("push", MOVE, pts[0].x(), pts[0].y());
55cb93a386Sopenharmony_ci            break;
56cb93a386Sopenharmony_ci        case SkPathVerb::kLine:
57cb93a386Sopenharmony_ci            cmd.call<void>("push", LINE, pts[1].x(), pts[1].y());
58cb93a386Sopenharmony_ci            break;
59cb93a386Sopenharmony_ci        case SkPathVerb::kQuad:
60cb93a386Sopenharmony_ci            cmd.call<void>("push", QUAD, pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
61cb93a386Sopenharmony_ci            break;
62cb93a386Sopenharmony_ci        case SkPathVerb::kConic:
63cb93a386Sopenharmony_ci            cmd.call<void>("push", CONIC,
64cb93a386Sopenharmony_ci                           pts[1].x(), pts[1].y(),
65cb93a386Sopenharmony_ci                           pts[2].x(), pts[2].y(), *w);
66cb93a386Sopenharmony_ci            break;
67cb93a386Sopenharmony_ci        case SkPathVerb::kCubic:
68cb93a386Sopenharmony_ci            cmd.call<void>("push", CUBIC,
69cb93a386Sopenharmony_ci                           pts[1].x(), pts[1].y(),
70cb93a386Sopenharmony_ci                           pts[2].x(), pts[2].y(),
71cb93a386Sopenharmony_ci                           pts[3].x(), pts[3].y());
72cb93a386Sopenharmony_ci            break;
73cb93a386Sopenharmony_ci        case SkPathVerb::kClose:
74cb93a386Sopenharmony_ci            cmd.call<void>("push", CLOSE);
75cb93a386Sopenharmony_ci            break;
76cb93a386Sopenharmony_ci        }
77cb93a386Sopenharmony_ci        cmds.call<void>("push", cmd);
78cb93a386Sopenharmony_ci    }
79cb93a386Sopenharmony_ci    return cmds;
80cb93a386Sopenharmony_ci}
81cb93a386Sopenharmony_ci
82cb93a386Sopenharmony_ci// This type signature is a mess, but it's necessary. See, we can't use "bind" (EMSCRIPTEN_BINDINGS)
83cb93a386Sopenharmony_ci// and pointers to primitive types (Only bound types like SkPoint). We could if we used
84cb93a386Sopenharmony_ci// cwrap (see https://becominghuman.ai/passing-and-returning-webassembly-array-parameters-a0f572c65d97)
85cb93a386Sopenharmony_ci// but that requires us to stick to C code and, AFAIK, doesn't allow us to return nice things like
86cb93a386Sopenharmony_ci// SkPath or SkOpBuilder.
87cb93a386Sopenharmony_ci//
88cb93a386Sopenharmony_ci// So, basically, if we are using C++ and EMSCRIPTEN_BINDINGS, we can't have primative pointers
89cb93a386Sopenharmony_ci// in our function type signatures. (this gives an error message like "Cannot call foo due to unbound
90cb93a386Sopenharmony_ci// types Pi, Pf").  But, we can just pretend they are numbers and cast them to be pointers and
91cb93a386Sopenharmony_ci// the compiler is happy.
92cb93a386Sopenharmony_ciSkPathOrNull EMSCRIPTEN_KEEPALIVE FromCmds(uintptr_t /* float* */ cptr, int numCmds) {
93cb93a386Sopenharmony_ci    const auto* cmds = reinterpret_cast<const float*>(cptr);
94cb93a386Sopenharmony_ci    SkPath path;
95cb93a386Sopenharmony_ci    float x1, y1, x2, y2, x3, y3;
96cb93a386Sopenharmony_ci
97cb93a386Sopenharmony_ci    // if there are not enough arguments, bail with the path we've constructed so far.
98cb93a386Sopenharmony_ci    #define CHECK_NUM_ARGS(n) \
99cb93a386Sopenharmony_ci        if ((i + n) > numCmds) { \
100cb93a386Sopenharmony_ci            SkDebugf("Not enough args to match the verbs. Saw %d commands\n", numCmds); \
101cb93a386Sopenharmony_ci            return emscripten::val::null(); \
102cb93a386Sopenharmony_ci        }
103cb93a386Sopenharmony_ci
104cb93a386Sopenharmony_ci    for(int i = 0; i < numCmds;){
105cb93a386Sopenharmony_ci         switch (sk_float_floor2int(cmds[i++])) {
106cb93a386Sopenharmony_ci            case MOVE:
107cb93a386Sopenharmony_ci                CHECK_NUM_ARGS(2);
108cb93a386Sopenharmony_ci                x1 = cmds[i++], y1 = cmds[i++];
109cb93a386Sopenharmony_ci                path.moveTo(x1, y1);
110cb93a386Sopenharmony_ci                break;
111cb93a386Sopenharmony_ci            case LINE:
112cb93a386Sopenharmony_ci                CHECK_NUM_ARGS(2);
113cb93a386Sopenharmony_ci                x1 = cmds[i++], y1 = cmds[i++];
114cb93a386Sopenharmony_ci                path.lineTo(x1, y1);
115cb93a386Sopenharmony_ci                break;
116cb93a386Sopenharmony_ci            case QUAD:
117cb93a386Sopenharmony_ci                CHECK_NUM_ARGS(4);
118cb93a386Sopenharmony_ci                x1 = cmds[i++], y1 = cmds[i++];
119cb93a386Sopenharmony_ci                x2 = cmds[i++], y2 = cmds[i++];
120cb93a386Sopenharmony_ci                path.quadTo(x1, y1, x2, y2);
121cb93a386Sopenharmony_ci                break;
122cb93a386Sopenharmony_ci            case CONIC:
123cb93a386Sopenharmony_ci                CHECK_NUM_ARGS(5);
124cb93a386Sopenharmony_ci                x1 = cmds[i++], y1 = cmds[i++];
125cb93a386Sopenharmony_ci                x2 = cmds[i++], y2 = cmds[i++];
126cb93a386Sopenharmony_ci                x3 = cmds[i++]; // weight
127cb93a386Sopenharmony_ci                path.conicTo(x1, y1, x2, y2, x3);
128cb93a386Sopenharmony_ci                break;
129cb93a386Sopenharmony_ci            case CUBIC:
130cb93a386Sopenharmony_ci                CHECK_NUM_ARGS(6);
131cb93a386Sopenharmony_ci                x1 = cmds[i++], y1 = cmds[i++];
132cb93a386Sopenharmony_ci                x2 = cmds[i++], y2 = cmds[i++];
133cb93a386Sopenharmony_ci                x3 = cmds[i++], y3 = cmds[i++];
134cb93a386Sopenharmony_ci                path.cubicTo(x1, y1, x2, y2, x3, y3);
135cb93a386Sopenharmony_ci                break;
136cb93a386Sopenharmony_ci            case CLOSE:
137cb93a386Sopenharmony_ci                path.close();
138cb93a386Sopenharmony_ci                break;
139cb93a386Sopenharmony_ci            default:
140cb93a386Sopenharmony_ci                SkDebugf("  path: UNKNOWN command %f, aborting dump...\n", cmds[i-1]);
141cb93a386Sopenharmony_ci                return emscripten::val::null();
142cb93a386Sopenharmony_ci        }
143cb93a386Sopenharmony_ci    }
144cb93a386Sopenharmony_ci
145cb93a386Sopenharmony_ci    #undef CHECK_NUM_ARGS
146cb93a386Sopenharmony_ci
147cb93a386Sopenharmony_ci    return emscripten::val(path);
148cb93a386Sopenharmony_ci}
149cb93a386Sopenharmony_ci
150cb93a386Sopenharmony_ciSkPath EMSCRIPTEN_KEEPALIVE NewPath() {
151cb93a386Sopenharmony_ci    return SkPath();
152cb93a386Sopenharmony_ci}
153cb93a386Sopenharmony_ci
154cb93a386Sopenharmony_ciSkPath EMSCRIPTEN_KEEPALIVE CopyPath(const SkPath& a) {
155cb93a386Sopenharmony_ci    SkPath copy(a);
156cb93a386Sopenharmony_ci    return copy;
157cb93a386Sopenharmony_ci}
158cb93a386Sopenharmony_ci
159cb93a386Sopenharmony_cibool EMSCRIPTEN_KEEPALIVE Equals(const SkPath& a, const SkPath& b) {
160cb93a386Sopenharmony_ci    return a == b;
161cb93a386Sopenharmony_ci}
162cb93a386Sopenharmony_ci
163cb93a386Sopenharmony_ci//========================================================================================
164cb93a386Sopenharmony_ci// Path things
165cb93a386Sopenharmony_ci//========================================================================================
166cb93a386Sopenharmony_ci
167cb93a386Sopenharmony_ci// All these Apply* methods are simple wrappers to avoid returning an object.
168cb93a386Sopenharmony_ci// The default WASM bindings produce code that will leak if a return value
169cb93a386Sopenharmony_ci// isn't assigned to a JS variable and has delete() called on it.
170cb93a386Sopenharmony_ci// These Apply methods, combined with the smarter binding code allow for chainable
171cb93a386Sopenharmony_ci// commands that don't leak if the return value is ignored (i.e. when used intuitively).
172cb93a386Sopenharmony_ci
173cb93a386Sopenharmony_civoid ApplyArcTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
174cb93a386Sopenharmony_ci                SkScalar radius) {
175cb93a386Sopenharmony_ci    p.arcTo(x1, y1, x2, y2, radius);
176cb93a386Sopenharmony_ci}
177cb93a386Sopenharmony_ci
178cb93a386Sopenharmony_civoid ApplyClose(SkPath& p) {
179cb93a386Sopenharmony_ci    p.close();
180cb93a386Sopenharmony_ci}
181cb93a386Sopenharmony_ci
182cb93a386Sopenharmony_civoid ApplyConicTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
183cb93a386Sopenharmony_ci                  SkScalar w) {
184cb93a386Sopenharmony_ci    p.conicTo(x1, y1, x2, y2, w);
185cb93a386Sopenharmony_ci}
186cb93a386Sopenharmony_ci
187cb93a386Sopenharmony_civoid ApplyCubicTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
188cb93a386Sopenharmony_ci                  SkScalar x3, SkScalar y3) {
189cb93a386Sopenharmony_ci    p.cubicTo(x1, y1, x2, y2, x3, y3);
190cb93a386Sopenharmony_ci}
191cb93a386Sopenharmony_ci
192cb93a386Sopenharmony_civoid ApplyLineTo(SkPath& p, SkScalar x, SkScalar y) {
193cb93a386Sopenharmony_ci    p.lineTo(x, y);
194cb93a386Sopenharmony_ci}
195cb93a386Sopenharmony_ci
196cb93a386Sopenharmony_civoid ApplyMoveTo(SkPath& p, SkScalar x, SkScalar y) {
197cb93a386Sopenharmony_ci    p.moveTo(x, y);
198cb93a386Sopenharmony_ci}
199cb93a386Sopenharmony_ci
200cb93a386Sopenharmony_civoid ApplyQuadTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
201cb93a386Sopenharmony_ci    p.quadTo(x1, y1, x2, y2);
202cb93a386Sopenharmony_ci}
203cb93a386Sopenharmony_ci
204cb93a386Sopenharmony_ci
205cb93a386Sopenharmony_ci
206cb93a386Sopenharmony_ci//========================================================================================
207cb93a386Sopenharmony_ci// SVG things
208cb93a386Sopenharmony_ci//========================================================================================
209cb93a386Sopenharmony_ci
210cb93a386Sopenharmony_ciJSString EMSCRIPTEN_KEEPALIVE ToSVGString(const SkPath& path) {
211cb93a386Sopenharmony_ci    SkString s;
212cb93a386Sopenharmony_ci    SkParsePath::ToSVGString(path, &s);
213cb93a386Sopenharmony_ci    // Wrapping it in val automatically turns it into a JS string.
214cb93a386Sopenharmony_ci    // Not too sure on performance implications, but is is simpler than
215cb93a386Sopenharmony_ci    // returning a raw pointer to const char * and then using
216cb93a386Sopenharmony_ci    // UTF8ToString() on the calling side.
217cb93a386Sopenharmony_ci    return emscripten::val(s.c_str());
218cb93a386Sopenharmony_ci}
219cb93a386Sopenharmony_ci
220cb93a386Sopenharmony_ci
221cb93a386Sopenharmony_ciSkPathOrNull EMSCRIPTEN_KEEPALIVE FromSVGString(std::string str) {
222cb93a386Sopenharmony_ci    SkPath path;
223cb93a386Sopenharmony_ci    if (SkParsePath::FromSVGString(str.c_str(), &path)) {
224cb93a386Sopenharmony_ci        return emscripten::val(path);
225cb93a386Sopenharmony_ci    }
226cb93a386Sopenharmony_ci    return emscripten::val::null();
227cb93a386Sopenharmony_ci}
228cb93a386Sopenharmony_ci
229cb93a386Sopenharmony_ci//========================================================================================
230cb93a386Sopenharmony_ci// PATHOP things
231cb93a386Sopenharmony_ci//========================================================================================
232cb93a386Sopenharmony_ci
233cb93a386Sopenharmony_cibool EMSCRIPTEN_KEEPALIVE ApplySimplify(SkPath& path) {
234cb93a386Sopenharmony_ci    return Simplify(path, &path);
235cb93a386Sopenharmony_ci}
236cb93a386Sopenharmony_ci
237cb93a386Sopenharmony_cibool EMSCRIPTEN_KEEPALIVE ApplyPathOp(SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
238cb93a386Sopenharmony_ci    return Op(pathOne, pathTwo, op, &pathOne);
239cb93a386Sopenharmony_ci}
240cb93a386Sopenharmony_ci
241cb93a386Sopenharmony_ciSkPathOrNull EMSCRIPTEN_KEEPALIVE MakeFromOp(const SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
242cb93a386Sopenharmony_ci    SkPath out;
243cb93a386Sopenharmony_ci    if (Op(pathOne, pathTwo, op, &out)) {
244cb93a386Sopenharmony_ci        return emscripten::val(out);
245cb93a386Sopenharmony_ci    }
246cb93a386Sopenharmony_ci    return emscripten::val::null();
247cb93a386Sopenharmony_ci}
248cb93a386Sopenharmony_ci
249cb93a386Sopenharmony_ciSkPathOrNull EMSCRIPTEN_KEEPALIVE ResolveBuilder(SkOpBuilder& builder) {
250cb93a386Sopenharmony_ci    SkPath path;
251cb93a386Sopenharmony_ci    if (builder.resolve(&path)) {
252cb93a386Sopenharmony_ci        return emscripten::val(path);
253cb93a386Sopenharmony_ci    }
254cb93a386Sopenharmony_ci    return emscripten::val::null();
255cb93a386Sopenharmony_ci}
256cb93a386Sopenharmony_ci
257cb93a386Sopenharmony_ci//========================================================================================
258cb93a386Sopenharmony_ci// Canvas things
259cb93a386Sopenharmony_ci//========================================================================================
260cb93a386Sopenharmony_ci
261cb93a386Sopenharmony_civoid EMSCRIPTEN_KEEPALIVE ToCanvas(const SkPath& path, emscripten::val /* Path2D or Canvas*/ ctx) {
262cb93a386Sopenharmony_ci    SkPath::Iter iter(path, false);
263cb93a386Sopenharmony_ci    SkPoint pts[4];
264cb93a386Sopenharmony_ci    SkPath::Verb verb;
265cb93a386Sopenharmony_ci    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
266cb93a386Sopenharmony_ci        switch (verb) {
267cb93a386Sopenharmony_ci            case SkPath::kMove_Verb:
268cb93a386Sopenharmony_ci                ctx.call<void>("moveTo", pts[0].x(), pts[0].y());
269cb93a386Sopenharmony_ci                break;
270cb93a386Sopenharmony_ci            case SkPath::kLine_Verb:
271cb93a386Sopenharmony_ci                ctx.call<void>("lineTo", pts[1].x(), pts[1].y());
272cb93a386Sopenharmony_ci                break;
273cb93a386Sopenharmony_ci            case SkPath::kQuad_Verb:
274cb93a386Sopenharmony_ci                ctx.call<void>("quadraticCurveTo", pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
275cb93a386Sopenharmony_ci                break;
276cb93a386Sopenharmony_ci            case SkPath::kConic_Verb:
277cb93a386Sopenharmony_ci                SkPoint quads[5];
278cb93a386Sopenharmony_ci                // approximate with 2^1=2 quads.
279cb93a386Sopenharmony_ci                SkPath::ConvertConicToQuads(pts[0], pts[1], pts[2], iter.conicWeight(), quads, 1);
280cb93a386Sopenharmony_ci                ctx.call<void>("quadraticCurveTo", quads[1].x(), quads[1].y(), quads[2].x(), quads[2].y());
281cb93a386Sopenharmony_ci                ctx.call<void>("quadraticCurveTo", quads[3].x(), quads[3].y(), quads[4].x(), quads[4].y());
282cb93a386Sopenharmony_ci                break;
283cb93a386Sopenharmony_ci            case SkPath::kCubic_Verb:
284cb93a386Sopenharmony_ci                ctx.call<void>("bezierCurveTo", pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y(),
285cb93a386Sopenharmony_ci                                                   pts[3].x(), pts[3].y());
286cb93a386Sopenharmony_ci                break;
287cb93a386Sopenharmony_ci            case SkPath::kClose_Verb:
288cb93a386Sopenharmony_ci                ctx.call<void>("closePath");
289cb93a386Sopenharmony_ci                break;
290cb93a386Sopenharmony_ci            case SkPath::kDone_Verb:
291cb93a386Sopenharmony_ci                break;
292cb93a386Sopenharmony_ci        }
293cb93a386Sopenharmony_ci    }
294cb93a386Sopenharmony_ci}
295cb93a386Sopenharmony_ci
296cb93a386Sopenharmony_ciemscripten::val JSPath2D = emscripten::val::global("Path2D");
297cb93a386Sopenharmony_ci
298cb93a386Sopenharmony_ciemscripten::val EMSCRIPTEN_KEEPALIVE ToPath2D(const SkPath& path) {
299cb93a386Sopenharmony_ci    emscripten::val retVal = JSPath2D.new_();
300cb93a386Sopenharmony_ci    ToCanvas(path, retVal);
301cb93a386Sopenharmony_ci    return retVal;
302cb93a386Sopenharmony_ci}
303cb93a386Sopenharmony_ci
304cb93a386Sopenharmony_ci// ======================================================================================
305cb93a386Sopenharmony_ci// Path2D API things
306cb93a386Sopenharmony_ci// ======================================================================================
307cb93a386Sopenharmony_civoid ApplyAddRect(SkPath& path, SkScalar x, SkScalar y, SkScalar width, SkScalar height) {
308cb93a386Sopenharmony_ci    path.addRect(x, y, x+width, y+height);
309cb93a386Sopenharmony_ci}
310cb93a386Sopenharmony_ci
311cb93a386Sopenharmony_civoid ApplyAddArc(SkPath& path, SkScalar x, SkScalar y, SkScalar radius,
312cb93a386Sopenharmony_ci              SkScalar startAngle, SkScalar endAngle, bool ccw) {
313cb93a386Sopenharmony_ci    SkPath temp;
314cb93a386Sopenharmony_ci    SkRect bounds = SkRect::MakeLTRB(x-radius, y-radius, x+radius, y+radius);
315cb93a386Sopenharmony_ci    const auto sweep = SkRadiansToDegrees(endAngle - startAngle) - 360 * ccw;
316cb93a386Sopenharmony_ci    temp.addArc(bounds, SkRadiansToDegrees(startAngle), sweep);
317cb93a386Sopenharmony_ci    path.addPath(temp, SkPath::kExtend_AddPathMode);
318cb93a386Sopenharmony_ci}
319cb93a386Sopenharmony_ci
320cb93a386Sopenharmony_civoid ApplyEllipse(SkPath& path, SkScalar x, SkScalar y, SkScalar radiusX, SkScalar radiusY,
321cb93a386Sopenharmony_ci                     SkScalar rotation, SkScalar startAngle, SkScalar endAngle, bool ccw) {
322cb93a386Sopenharmony_ci    // This is easiest to do by making a new path and then extending the current path
323cb93a386Sopenharmony_ci    // (this properly catches the cases of if there's a moveTo before this call or not).
324cb93a386Sopenharmony_ci    SkRect bounds = SkRect::MakeLTRB(x-radiusX, y-radiusY, x+radiusX, y+radiusY);
325cb93a386Sopenharmony_ci    SkPath temp;
326cb93a386Sopenharmony_ci    const auto sweep = SkRadiansToDegrees(endAngle - startAngle) - (360 * ccw);
327cb93a386Sopenharmony_ci    temp.addArc(bounds, SkRadiansToDegrees(startAngle), sweep);
328cb93a386Sopenharmony_ci
329cb93a386Sopenharmony_ci    SkMatrix m;
330cb93a386Sopenharmony_ci    m.setRotate(SkRadiansToDegrees(rotation), x, y);
331cb93a386Sopenharmony_ci    path.addPath(temp, m, SkPath::kExtend_AddPathMode);
332cb93a386Sopenharmony_ci}
333cb93a386Sopenharmony_ci
334cb93a386Sopenharmony_ci// Allows for full matix control.
335cb93a386Sopenharmony_civoid ApplyAddPath(SkPath& orig, const SkPath& newPath,
336cb93a386Sopenharmony_ci                   SkScalar scaleX, SkScalar skewX,  SkScalar transX,
337cb93a386Sopenharmony_ci                   SkScalar skewY,  SkScalar scaleY, SkScalar transY,
338cb93a386Sopenharmony_ci                   SkScalar pers0, SkScalar pers1, SkScalar pers2) {
339cb93a386Sopenharmony_ci    SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX,
340cb93a386Sopenharmony_ci                                   skewY , scaleY, transY,
341cb93a386Sopenharmony_ci                                   pers0 , pers1 , pers2);
342cb93a386Sopenharmony_ci    orig.addPath(newPath, m);
343cb93a386Sopenharmony_ci}
344cb93a386Sopenharmony_ci
345cb93a386Sopenharmony_ciJSString GetFillTypeString(const SkPath& path) {
346cb93a386Sopenharmony_ci    if (path.getFillType() == SkPathFillType::kWinding) {
347cb93a386Sopenharmony_ci        return emscripten::val("nonzero");
348cb93a386Sopenharmony_ci    } else if (path.getFillType() == SkPathFillType::kEvenOdd) {
349cb93a386Sopenharmony_ci        return emscripten::val("evenodd");
350cb93a386Sopenharmony_ci    } else {
351cb93a386Sopenharmony_ci        SkDebugf("warning: can't translate inverted filltype to HTML Canvas\n");
352cb93a386Sopenharmony_ci        return emscripten::val("nonzero"); //Use default
353cb93a386Sopenharmony_ci    }
354cb93a386Sopenharmony_ci}
355cb93a386Sopenharmony_ci
356cb93a386Sopenharmony_ci//========================================================================================
357cb93a386Sopenharmony_ci// Path Effects
358cb93a386Sopenharmony_ci//========================================================================================
359cb93a386Sopenharmony_ci
360cb93a386Sopenharmony_cibool ApplyDash(SkPath& path, SkScalar on, SkScalar off, SkScalar phase) {
361cb93a386Sopenharmony_ci    SkScalar intervals[] = { on, off };
362cb93a386Sopenharmony_ci    auto pe = SkDashPathEffect::Make(intervals, 2, phase);
363cb93a386Sopenharmony_ci    if (!pe) {
364cb93a386Sopenharmony_ci        SkDebugf("Invalid args to dash()\n");
365cb93a386Sopenharmony_ci        return false;
366cb93a386Sopenharmony_ci    }
367cb93a386Sopenharmony_ci    SkStrokeRec rec(SkStrokeRec::InitStyle::kHairline_InitStyle);
368cb93a386Sopenharmony_ci    if (pe->filterPath(&path, path, &rec, nullptr)) {
369cb93a386Sopenharmony_ci        return true;
370cb93a386Sopenharmony_ci    }
371cb93a386Sopenharmony_ci    SkDebugf("Could not make dashed path\n");
372cb93a386Sopenharmony_ci    return false;
373cb93a386Sopenharmony_ci}
374cb93a386Sopenharmony_ci
375cb93a386Sopenharmony_cibool ApplyTrim(SkPath& path, SkScalar startT, SkScalar stopT, bool isComplement) {
376cb93a386Sopenharmony_ci    auto mode = isComplement ? SkTrimPathEffect::Mode::kInverted : SkTrimPathEffect::Mode::kNormal;
377cb93a386Sopenharmony_ci    auto pe = SkTrimPathEffect::Make(startT, stopT, mode);
378cb93a386Sopenharmony_ci    if (!pe) {
379cb93a386Sopenharmony_ci        SkDebugf("Invalid args to trim(): startT and stopT must be in [0,1]\n");
380cb93a386Sopenharmony_ci        return false;
381cb93a386Sopenharmony_ci    }
382cb93a386Sopenharmony_ci    SkStrokeRec rec(SkStrokeRec::InitStyle::kHairline_InitStyle);
383cb93a386Sopenharmony_ci    if (pe->filterPath(&path, path, &rec, nullptr)) {
384cb93a386Sopenharmony_ci        return true;
385cb93a386Sopenharmony_ci    }
386cb93a386Sopenharmony_ci    SkDebugf("Could not trim path\n");
387cb93a386Sopenharmony_ci    return false;
388cb93a386Sopenharmony_ci}
389cb93a386Sopenharmony_ci
390cb93a386Sopenharmony_cistruct StrokeOpts {
391cb93a386Sopenharmony_ci    // Default values are set in chaining.js which allows clients
392cb93a386Sopenharmony_ci    // to set any number of them. Otherwise, the binding code complains if
393cb93a386Sopenharmony_ci    // any are omitted.
394cb93a386Sopenharmony_ci    SkScalar width;
395cb93a386Sopenharmony_ci    SkScalar miter_limit;
396cb93a386Sopenharmony_ci    SkPaint::Join join;
397cb93a386Sopenharmony_ci    SkPaint::Cap cap;
398cb93a386Sopenharmony_ci};
399cb93a386Sopenharmony_ci
400cb93a386Sopenharmony_cibool ApplyStroke(SkPath& path, StrokeOpts opts) {
401cb93a386Sopenharmony_ci    SkPaint p;
402cb93a386Sopenharmony_ci    p.setStyle(SkPaint::kStroke_Style);
403cb93a386Sopenharmony_ci    p.setStrokeCap(opts.cap);
404cb93a386Sopenharmony_ci    p.setStrokeJoin(opts.join);
405cb93a386Sopenharmony_ci    p.setStrokeWidth(opts.width);
406cb93a386Sopenharmony_ci    p.setStrokeMiter(opts.miter_limit);
407cb93a386Sopenharmony_ci
408cb93a386Sopenharmony_ci    return p.getFillPath(path, &path);
409cb93a386Sopenharmony_ci}
410cb93a386Sopenharmony_ci
411cb93a386Sopenharmony_ci//========================================================================================
412cb93a386Sopenharmony_ci// Matrix things
413cb93a386Sopenharmony_ci//========================================================================================
414cb93a386Sopenharmony_ci
415cb93a386Sopenharmony_cistruct SimpleMatrix {
416cb93a386Sopenharmony_ci    SkScalar scaleX, skewX,  transX;
417cb93a386Sopenharmony_ci    SkScalar skewY,  scaleY, transY;
418cb93a386Sopenharmony_ci    SkScalar pers0,  pers1,  pers2;
419cb93a386Sopenharmony_ci};
420cb93a386Sopenharmony_ci
421cb93a386Sopenharmony_ciSkMatrix toSkMatrix(const SimpleMatrix& sm) {
422cb93a386Sopenharmony_ci    return SkMatrix::MakeAll(sm.scaleX, sm.skewX , sm.transX,
423cb93a386Sopenharmony_ci                             sm.skewY , sm.scaleY, sm.transY,
424cb93a386Sopenharmony_ci                             sm.pers0 , sm.pers1 , sm.pers2);
425cb93a386Sopenharmony_ci}
426cb93a386Sopenharmony_ci
427cb93a386Sopenharmony_civoid ApplyTransform(SkPath& orig, const SimpleMatrix& sm) {
428cb93a386Sopenharmony_ci    orig.transform(toSkMatrix(sm));
429cb93a386Sopenharmony_ci}
430cb93a386Sopenharmony_ci
431cb93a386Sopenharmony_civoid ApplyTransform(SkPath& orig,
432cb93a386Sopenharmony_ci                    SkScalar scaleX, SkScalar skewX,  SkScalar transX,
433cb93a386Sopenharmony_ci                    SkScalar skewY,  SkScalar scaleY, SkScalar transY,
434cb93a386Sopenharmony_ci                    SkScalar pers0, SkScalar pers1, SkScalar pers2) {
435cb93a386Sopenharmony_ci    SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX,
436cb93a386Sopenharmony_ci                                   skewY , scaleY, transY,
437cb93a386Sopenharmony_ci                                   pers0 , pers1 , pers2);
438cb93a386Sopenharmony_ci    orig.transform(m);
439cb93a386Sopenharmony_ci}
440cb93a386Sopenharmony_ci
441cb93a386Sopenharmony_ci//========================================================================================
442cb93a386Sopenharmony_ci// Testing things
443cb93a386Sopenharmony_ci//========================================================================================
444cb93a386Sopenharmony_ci
445cb93a386Sopenharmony_ci// The use case for this is on the JS side is something like:
446cb93a386Sopenharmony_ci//     PathKit.SkBits2FloatUnsigned(parseInt("0xc0a00000"))
447cb93a386Sopenharmony_ci// to have precise float values for tests. In the C++ tests, we can use SkBits2Float because
448cb93a386Sopenharmony_ci// it takes int32_t, but the JS parseInt basically returns an unsigned int. So, we add in
449cb93a386Sopenharmony_ci// this helper which casts for us on the way to SkBits2Float.
450cb93a386Sopenharmony_cifloat SkBits2FloatUnsigned(uint32_t floatAsBits) {
451cb93a386Sopenharmony_ci    return SkBits2Float((int32_t) floatAsBits);
452cb93a386Sopenharmony_ci}
453cb93a386Sopenharmony_ci
454cb93a386Sopenharmony_ci// Binds the classes to the JS
455cb93a386Sopenharmony_ci//
456cb93a386Sopenharmony_ci// See https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/embind.html#non-member-functions-on-the-javascript-prototype
457cb93a386Sopenharmony_ci// for more on binding non-member functions to the JS object, allowing us to rewire
458cb93a386Sopenharmony_ci// various functions.  That is, we can make the SkPath we expose appear to have methods
459cb93a386Sopenharmony_ci// that the original SkPath does not, like rect(x, y, width, height) and toPath2D().
460cb93a386Sopenharmony_ci//
461cb93a386Sopenharmony_ci// An important detail for binding non-member functions is that the first argument
462cb93a386Sopenharmony_ci// must be SkPath& (the reference part is very important).
463cb93a386Sopenharmony_ci//
464cb93a386Sopenharmony_ci// Note that we can't expose default or optional arguments, but we can have multiple
465cb93a386Sopenharmony_ci// declarations of the same function that take different amounts of arguments.
466cb93a386Sopenharmony_ci// For example, see _transform
467cb93a386Sopenharmony_ci// Additionally, we are perfectly happy to handle default arguments and function
468cb93a386Sopenharmony_ci// overloads in the JS glue code (see chaining.js::addPath() for an example).
469cb93a386Sopenharmony_ciEMSCRIPTEN_BINDINGS(skia) {
470cb93a386Sopenharmony_ci    class_<SkPath>("SkPath")
471cb93a386Sopenharmony_ci        .constructor<>()
472cb93a386Sopenharmony_ci        .constructor<const SkPath&>()
473cb93a386Sopenharmony_ci
474cb93a386Sopenharmony_ci        // Path2D API
475cb93a386Sopenharmony_ci        .function("_addPath", &ApplyAddPath)
476cb93a386Sopenharmony_ci        // 3 additional overloads of addPath are handled in JS bindings
477cb93a386Sopenharmony_ci        .function("_arc", &ApplyAddArc)
478cb93a386Sopenharmony_ci        .function("_arcTo", &ApplyArcTo)
479cb93a386Sopenharmony_ci        //"bezierCurveTo" alias handled in JS bindings
480cb93a386Sopenharmony_ci        .function("_close", &ApplyClose)
481cb93a386Sopenharmony_ci        //"closePath" alias handled in JS bindings
482cb93a386Sopenharmony_ci        .function("_conicTo", &ApplyConicTo)
483cb93a386Sopenharmony_ci        .function("_cubicTo", &ApplyCubicTo)
484cb93a386Sopenharmony_ci
485cb93a386Sopenharmony_ci        .function("_ellipse", &ApplyEllipse)
486cb93a386Sopenharmony_ci        .function("_lineTo", &ApplyLineTo)
487cb93a386Sopenharmony_ci        .function("_moveTo", &ApplyMoveTo)
488cb93a386Sopenharmony_ci        // "quadraticCurveTo" alias handled in JS bindings
489cb93a386Sopenharmony_ci        .function("_quadTo", &ApplyQuadTo)
490cb93a386Sopenharmony_ci        .function("_rect", &ApplyAddRect)
491cb93a386Sopenharmony_ci
492cb93a386Sopenharmony_ci        // Extra features
493cb93a386Sopenharmony_ci        .function("setFillType", select_overload<void(SkPathFillType)>(&SkPath::setFillType))
494cb93a386Sopenharmony_ci        .function("getFillType", &SkPath::getFillType)
495cb93a386Sopenharmony_ci        .function("getFillTypeString", &GetFillTypeString)
496cb93a386Sopenharmony_ci        .function("getBounds", &SkPath::getBounds)
497cb93a386Sopenharmony_ci        .function("computeTightBounds", &SkPath::computeTightBounds)
498cb93a386Sopenharmony_ci        .function("equals", &Equals)
499cb93a386Sopenharmony_ci        .function("copy", &CopyPath)
500cb93a386Sopenharmony_ci
501cb93a386Sopenharmony_ci        // PathEffects
502cb93a386Sopenharmony_ci        .function("_dash", &ApplyDash)
503cb93a386Sopenharmony_ci        .function("_trim", &ApplyTrim)
504cb93a386Sopenharmony_ci        .function("_stroke", &ApplyStroke)
505cb93a386Sopenharmony_ci
506cb93a386Sopenharmony_ci        // Matrix
507cb93a386Sopenharmony_ci        .function("_transform", select_overload<void(SkPath& orig, const SimpleMatrix& sm)>(&ApplyTransform))
508cb93a386Sopenharmony_ci        .function("_transform", select_overload<void(SkPath& orig, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&ApplyTransform))
509cb93a386Sopenharmony_ci
510cb93a386Sopenharmony_ci        // PathOps
511cb93a386Sopenharmony_ci        .function("_simplify", &ApplySimplify)
512cb93a386Sopenharmony_ci        .function("_op", &ApplyPathOp)
513cb93a386Sopenharmony_ci
514cb93a386Sopenharmony_ci        // Exporting
515cb93a386Sopenharmony_ci        .function("toCmds", &ToCmds)
516cb93a386Sopenharmony_ci        .function("toPath2D", &ToPath2D)
517cb93a386Sopenharmony_ci        .function("toCanvas", &ToCanvas)
518cb93a386Sopenharmony_ci        .function("toSVGString", &ToSVGString)
519cb93a386Sopenharmony_ci
520cb93a386Sopenharmony_ci#ifdef PATHKIT_TESTING
521cb93a386Sopenharmony_ci        .function("dump", select_overload<void() const>(&SkPath::dump))
522cb93a386Sopenharmony_ci        .function("dumpHex", select_overload<void() const>(&SkPath::dumpHex))
523cb93a386Sopenharmony_ci#endif
524cb93a386Sopenharmony_ci        ;
525cb93a386Sopenharmony_ci
526cb93a386Sopenharmony_ci    class_<SkOpBuilder>("SkOpBuilder")
527cb93a386Sopenharmony_ci        .constructor<>()
528cb93a386Sopenharmony_ci
529cb93a386Sopenharmony_ci        .function("add", &SkOpBuilder::add)
530cb93a386Sopenharmony_ci        .function("make", &ResolveBuilder)
531cb93a386Sopenharmony_ci        .function("resolve", &ResolveBuilder);
532cb93a386Sopenharmony_ci
533cb93a386Sopenharmony_ci    // Without these function() bindings, the function would be exposed but oblivious to
534cb93a386Sopenharmony_ci    // our types (e.g. SkPath)
535cb93a386Sopenharmony_ci
536cb93a386Sopenharmony_ci    // Import
537cb93a386Sopenharmony_ci    function("FromSVGString", &FromSVGString);
538cb93a386Sopenharmony_ci    function("NewPath", &NewPath);
539cb93a386Sopenharmony_ci    function("NewPath", &CopyPath);
540cb93a386Sopenharmony_ci    // FromCmds is defined in helper.js to make use of TypedArrays transparent.
541cb93a386Sopenharmony_ci    function("_FromCmds", &FromCmds);
542cb93a386Sopenharmony_ci    // Path2D is opaque, so we can't read in from it.
543cb93a386Sopenharmony_ci
544cb93a386Sopenharmony_ci    // PathOps
545cb93a386Sopenharmony_ci    function("MakeFromOp", &MakeFromOp);
546cb93a386Sopenharmony_ci
547cb93a386Sopenharmony_ci    enum_<SkPathOp>("PathOp")
548cb93a386Sopenharmony_ci        .value("DIFFERENCE",         SkPathOp::kDifference_SkPathOp)
549cb93a386Sopenharmony_ci        .value("INTERSECT",          SkPathOp::kIntersect_SkPathOp)
550cb93a386Sopenharmony_ci        .value("UNION",              SkPathOp::kUnion_SkPathOp)
551cb93a386Sopenharmony_ci        .value("XOR",                SkPathOp::kXOR_SkPathOp)
552cb93a386Sopenharmony_ci        .value("REVERSE_DIFFERENCE", SkPathOp::kReverseDifference_SkPathOp);
553cb93a386Sopenharmony_ci
554cb93a386Sopenharmony_ci    enum_<SkPathFillType>("FillType")
555cb93a386Sopenharmony_ci        .value("WINDING",            SkPathFillType::kWinding)
556cb93a386Sopenharmony_ci        .value("EVENODD",            SkPathFillType::kEvenOdd)
557cb93a386Sopenharmony_ci        .value("INVERSE_WINDING",    SkPathFillType::kInverseWinding)
558cb93a386Sopenharmony_ci        .value("INVERSE_EVENODD",    SkPathFillType::kInverseEvenOdd);
559cb93a386Sopenharmony_ci
560cb93a386Sopenharmony_ci    constant("MOVE_VERB",  MOVE);
561cb93a386Sopenharmony_ci    constant("LINE_VERB",  LINE);
562cb93a386Sopenharmony_ci    constant("QUAD_VERB",  QUAD);
563cb93a386Sopenharmony_ci    constant("CONIC_VERB", CONIC);
564cb93a386Sopenharmony_ci    constant("CUBIC_VERB", CUBIC);
565cb93a386Sopenharmony_ci    constant("CLOSE_VERB", CLOSE);
566cb93a386Sopenharmony_ci
567cb93a386Sopenharmony_ci    // A value object is much simpler than a class - it is returned as a JS
568cb93a386Sopenharmony_ci    // object and does not require delete().
569cb93a386Sopenharmony_ci    // https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/embind.html#value-types
570cb93a386Sopenharmony_ci    value_object<SkRect>("SkRect")
571cb93a386Sopenharmony_ci        .field("fLeft",   &SkRect::fLeft)
572cb93a386Sopenharmony_ci        .field("fTop",    &SkRect::fTop)
573cb93a386Sopenharmony_ci        .field("fRight",  &SkRect::fRight)
574cb93a386Sopenharmony_ci        .field("fBottom", &SkRect::fBottom);
575cb93a386Sopenharmony_ci
576cb93a386Sopenharmony_ci    function("LTRBRect", &SkRect::MakeLTRB);
577cb93a386Sopenharmony_ci
578cb93a386Sopenharmony_ci    // Stroke
579cb93a386Sopenharmony_ci    enum_<SkPaint::Join>("StrokeJoin")
580cb93a386Sopenharmony_ci        .value("MITER", SkPaint::Join::kMiter_Join)
581cb93a386Sopenharmony_ci        .value("ROUND", SkPaint::Join::kRound_Join)
582cb93a386Sopenharmony_ci        .value("BEVEL", SkPaint::Join::kBevel_Join);
583cb93a386Sopenharmony_ci
584cb93a386Sopenharmony_ci    enum_<SkPaint::Cap>("StrokeCap")
585cb93a386Sopenharmony_ci        .value("BUTT",   SkPaint::Cap::kButt_Cap)
586cb93a386Sopenharmony_ci        .value("ROUND",  SkPaint::Cap::kRound_Cap)
587cb93a386Sopenharmony_ci        .value("SQUARE", SkPaint::Cap::kSquare_Cap);
588cb93a386Sopenharmony_ci
589cb93a386Sopenharmony_ci    value_object<StrokeOpts>("StrokeOpts")
590cb93a386Sopenharmony_ci        .field("width",       &StrokeOpts::width)
591cb93a386Sopenharmony_ci        .field("miter_limit", &StrokeOpts::miter_limit)
592cb93a386Sopenharmony_ci        .field("join",        &StrokeOpts::join)
593cb93a386Sopenharmony_ci        .field("cap",         &StrokeOpts::cap);
594cb93a386Sopenharmony_ci
595cb93a386Sopenharmony_ci    // Matrix
596cb93a386Sopenharmony_ci    // Allows clients to supply a 1D array of 9 elements and the bindings
597cb93a386Sopenharmony_ci    // will automatically turn it into a 3x3 2D matrix.
598cb93a386Sopenharmony_ci    // e.g. path.transform([0,1,2,3,4,5,6,7,8])
599cb93a386Sopenharmony_ci    // This is likely simpler for the client than exposing SkMatrix
600cb93a386Sopenharmony_ci    // directly and requiring them to do a lot of .delete().
601cb93a386Sopenharmony_ci    value_array<SimpleMatrix>("SkMatrix")
602cb93a386Sopenharmony_ci        .element(&SimpleMatrix::scaleX)
603cb93a386Sopenharmony_ci        .element(&SimpleMatrix::skewX)
604cb93a386Sopenharmony_ci        .element(&SimpleMatrix::transX)
605cb93a386Sopenharmony_ci
606cb93a386Sopenharmony_ci        .element(&SimpleMatrix::skewY)
607cb93a386Sopenharmony_ci        .element(&SimpleMatrix::scaleY)
608cb93a386Sopenharmony_ci        .element(&SimpleMatrix::transY)
609cb93a386Sopenharmony_ci
610cb93a386Sopenharmony_ci        .element(&SimpleMatrix::pers0)
611cb93a386Sopenharmony_ci        .element(&SimpleMatrix::pers1)
612cb93a386Sopenharmony_ci        .element(&SimpleMatrix::pers2);
613cb93a386Sopenharmony_ci
614cb93a386Sopenharmony_ci    value_array<SkPoint>("SkPoint")
615cb93a386Sopenharmony_ci        .element(&SkPoint::fX)
616cb93a386Sopenharmony_ci        .element(&SkPoint::fY);
617cb93a386Sopenharmony_ci
618cb93a386Sopenharmony_ci    // Not intended for external clients to call directly.
619cb93a386Sopenharmony_ci    // See helper.js for the client-facing implementation.
620cb93a386Sopenharmony_ci    class_<SkCubicMap>("_SkCubicMap")
621cb93a386Sopenharmony_ci        .constructor<SkPoint, SkPoint>()
622cb93a386Sopenharmony_ci
623cb93a386Sopenharmony_ci        .function("computeYFromX", &SkCubicMap::computeYFromX)
624cb93a386Sopenharmony_ci        .function("computePtFromT", &SkCubicMap::computeFromT);
625cb93a386Sopenharmony_ci
626cb93a386Sopenharmony_ci
627cb93a386Sopenharmony_ci    // Test Utils
628cb93a386Sopenharmony_ci    function("SkBits2FloatUnsigned", &SkBits2FloatUnsigned);
629cb93a386Sopenharmony_ci}
630