* Copyright 2018 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkCubicMap.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPath.h"
#include "include/core/SkRect.h"
#include "include/core/SkString.h"
#include "include/core/SkStrokeRec.h"
#include "include/effects/SkDashPathEffect.h"
#include "include/effects/SkTrimPathEffect.h"
#include "include/pathops/SkPathOps.h"
#include "include/private/SkFloatBits.h"
#include "include/private/SkFloatingPoint.h"
#include "include/utils/SkParsePath.h"
#include "src/core/SkPaintDefaults.h"
#include <emscripten/emscripten.h>
#include <emscripten/bind.h>
using namespace emscripten;
static const int MOVE = 0;
static const int LINE = 1;
static const int QUAD = 2;
static const int CONIC = 3;
static const int CUBIC = 4;
static const int CLOSE = 5;
using SkPathOrNull = emscripten::val;
using JSString = emscripten::val;
using JSArray = emscripten::val;
template <typename VisitFunc>
void VisitPath(const SkPath& p, VisitFunc&& f) {
SkPath::RawIter iter(p);
SkPoint pts[4];
SkPath::Verb verb;
while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
f(verb, pts, iter);
}
}
JSArray EMSCRIPTEN_KEEPALIVE ToCmds(const SkPath& path) {
JSArray cmds = emscripten::val::array();
VisitPath(path, [&cmds](SkPath::Verb verb, const SkPoint pts[4], SkPath::RawIter iter) {
JSArray cmd = emscripten::val::array();
switch (verb) {
case SkPath::kMove_Verb:
cmd.call<void>("push", MOVE, pts[0].x(), pts[0].y());
break;
case SkPath::kLine_Verb:
cmd.call<void>("push", LINE, pts[1].x(), pts[1].y());
break;
case SkPath::kQuad_Verb:
cmd.call<void>("push", QUAD, pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
break;
case SkPath::kConic_Verb:
cmd.call<void>("push", CONIC,
pts[1].x(), pts[1].y(),
pts[2].x(), pts[2].y(), iter.conicWeight());
break;
case SkPath::kCubic_Verb:
cmd.call<void>("push", CUBIC,
pts[1].x(), pts[1].y(),
pts[2].x(), pts[2].y(),
pts[3].x(), pts[3].y());
break;
case SkPath::kClose_Verb:
cmd.call<void>("push", CLOSE);
break;
case SkPath::kDone_Verb:
SkASSERT(false);
break;
}
cmds.call<void>("push", cmd);
});
return cmds;
}
SkPathOrNull EMSCRIPTEN_KEEPALIVE FromCmds(uintptr_t cptr, int numCmds) {
const auto* cmds = reinterpret_cast<const float*>(cptr);
SkPath path;
float x1, y1, x2, y2, x3, y3;
#define CHECK_NUM_ARGS(n) \
if ((i + n) > numCmds) { \
SkDebugf("Not enough args to match the verbs. Saw %d commands\n", numCmds); \
return emscripten::val::null(); \
}
for(int i = 0; i < numCmds;){
switch (sk_float_floor2int(cmds[i++])) {
case MOVE:
CHECK_NUM_ARGS(2);
x1 = cmds[i++], y1 = cmds[i++];
path.moveTo(x1, y1);
break;
case LINE:
CHECK_NUM_ARGS(2);
x1 = cmds[i++], y1 = cmds[i++];
path.lineTo(x1, y1);
break;
case QUAD:
CHECK_NUM_ARGS(4);
x1 = cmds[i++], y1 = cmds[i++];
x2 = cmds[i++], y2 = cmds[i++];
path.quadTo(x1, y1, x2, y2);
break;
case CONIC:
CHECK_NUM_ARGS(5);
x1 = cmds[i++], y1 = cmds[i++];
x2 = cmds[i++], y2 = cmds[i++];
x3 = cmds[i++];
path.conicTo(x1, y1, x2, y2, x3);
break;
case CUBIC:
CHECK_NUM_ARGS(6);
x1 = cmds[i++], y1 = cmds[i++];
x2 = cmds[i++], y2 = cmds[i++];
x3 = cmds[i++], y3 = cmds[i++];
path.cubicTo(x1, y1, x2, y2, x3, y3);
break;
case CLOSE:
path.close();
break;
default:
SkDebugf(" path: UNKNOWN command %f, aborting dump...\n", cmds[i-1]);
return emscripten::val::null();
}
}
#undef CHECK_NUM_ARGS
return emscripten::val(path);
}
SkPath EMSCRIPTEN_KEEPALIVE NewPath() {
return SkPath();
}
SkPath EMSCRIPTEN_KEEPALIVE CopyPath(const SkPath& a) {
SkPath copy(a);
return copy;
}
bool EMSCRIPTEN_KEEPALIVE Equals(const SkPath& a, const SkPath& b) {
return a == b;
}
void ApplyArcTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
SkScalar radius) {
p.arcTo(x1, y1, x2, y2, radius);
}
void ApplyClose(SkPath& p) {
p.close();
}
void ApplyConicTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
SkScalar w) {
p.conicTo(x1, y1, x2, y2, w);
}
void ApplyCubicTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
SkScalar x3, SkScalar y3) {
p.cubicTo(x1, y1, x2, y2, x3, y3);
}
void ApplyLineTo(SkPath& p, SkScalar x, SkScalar y) {
p.lineTo(x, y);
}
void ApplyMoveTo(SkPath& p, SkScalar x, SkScalar y) {
p.moveTo(x, y);
}
void ApplyQuadTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
p.quadTo(x1, y1, x2, y2);
}
JSString EMSCRIPTEN_KEEPALIVE ToSVGString(const SkPath& path) {
SkString s;
SkParsePath::ToSVGString(path, &s);
return emscripten::val(s.c_str());
}
SkPathOrNull EMSCRIPTEN_KEEPALIVE FromSVGString(std::string str) {
SkPath path;
if (SkParsePath::FromSVGString(str.c_str(), &path)) {
return emscripten::val(path);
}
return emscripten::val::null();
}
bool EMSCRIPTEN_KEEPALIVE ApplySimplify(SkPath& path) {
return Simplify(path, &path);
}
bool EMSCRIPTEN_KEEPALIVE ApplyPathOp(SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
return Op(pathOne, pathTwo, op, &pathOne);
}
SkPathOrNull EMSCRIPTEN_KEEPALIVE MakeFromOp(const SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
SkPath out;
if (Op(pathOne, pathTwo, op, &out)) {
return emscripten::val(out);
}
return emscripten::val::null();
}
SkPathOrNull EMSCRIPTEN_KEEPALIVE ResolveBuilder(SkOpBuilder& builder) {
SkPath path;
if (builder.resolve(&path)) {
return emscripten::val(path);
}
return emscripten::val::null();
}
void EMSCRIPTEN_KEEPALIVE ToCanvas(const SkPath& path, emscripten::val ctx) {
SkPath::Iter iter(path, false);
SkPoint pts[4];
SkPath::Verb verb;
while ((verb = iter.next(pts, false)) != SkPath::kDone_Verb) {
switch (verb) {
case SkPath::kMove_Verb:
ctx.call<void>("moveTo", pts[0].x(), pts[0].y());
break;
case SkPath::kLine_Verb:
ctx.call<void>("lineTo", pts[1].x(), pts[1].y());
break;
case SkPath::kQuad_Verb:
ctx.call<void>("quadraticCurveTo", pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
break;
case SkPath::kConic_Verb:
SkPoint quads[5];
SkPath::ConvertConicToQuads(pts[0], pts[1], pts[2], iter.conicWeight(), quads, 1);
ctx.call<void>("quadraticCurveTo", quads[1].x(), quads[1].y(), quads[2].x(), quads[2].y());
ctx.call<void>("quadraticCurveTo", quads[3].x(), quads[3].y(), quads[4].x(), quads[4].y());
break;
case SkPath::kCubic_Verb:
ctx.call<void>("bezierCurveTo", pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y(),
pts[3].x(), pts[3].y());
break;
case SkPath::kClose_Verb:
ctx.call<void>("closePath");
break;
case SkPath::kDone_Verb:
break;
}
}
}
emscripten::val JSPath2D = emscripten::val::global("Path2D");
emscripten::val EMSCRIPTEN_KEEPALIVE ToPath2D(const SkPath& path) {
emscripten::val retVal = JSPath2D.new_();
ToCanvas(path, retVal);
return retVal;
}
void ApplyAddRect(SkPath& path, SkScalar x, SkScalar y, SkScalar width, SkScalar height) {
path.addRect(x, y, x+width, y+height);
}
void ApplyAddArc(SkPath& path, SkScalar x, SkScalar y, SkScalar radius,
SkScalar startAngle, SkScalar endAngle, bool ccw) {
SkPath temp;
SkRect bounds = SkRect::MakeLTRB(x-radius, y-radius, x+radius, y+radius);
const auto sweep = SkRadiansToDegrees(endAngle - startAngle) - 360 * ccw;
temp.addArc(bounds, SkRadiansToDegrees(startAngle), sweep);
path.addPath(temp, SkPath::kExtend_AddPathMode);
}
void ApplyEllipse(SkPath& path, SkScalar x, SkScalar y, SkScalar radiusX, SkScalar radiusY,
SkScalar rotation, SkScalar startAngle, SkScalar endAngle, bool ccw) {
SkRect bounds = SkRect::MakeLTRB(x-radiusX, y-radiusY, x+radiusX, y+radiusY);
SkPath temp;
const auto sweep = SkRadiansToDegrees(endAngle - startAngle) - (360 * ccw);
temp.addArc(bounds, SkRadiansToDegrees(startAngle), sweep);
SkMatrix m;
m.setRotate(SkRadiansToDegrees(rotation), x, y);
path.addPath(temp, m, SkPath::kExtend_AddPathMode);
}
void ApplyAddPath(SkPath& orig, const SkPath& newPath,
SkScalar scaleX, SkScalar skewX, SkScalar transX,
SkScalar skewY, SkScalar scaleY, SkScalar transY,
SkScalar pers0, SkScalar pers1, SkScalar pers2) {
SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX,
skewY , scaleY, transY,
pers0 , pers1 , pers2);
orig.addPath(newPath, m);
}
JSString GetFillTypeString(const SkPath& path) {
if (path.getFillType() == SkPath::FillType::kWinding_FillType) {
return emscripten::val("nonzero");
} else if (path.getFillType() == SkPath::FillType::kEvenOdd_FillType) {
return emscripten::val("evenodd");
} else {
SkDebugf("warning: can't translate inverted filltype to HTML Canvas\n");
return emscripten::val("nonzero");
}
}
bool ApplyDash(SkPath& path, SkScalar on, SkScalar off, SkScalar phase) {
SkScalar intervals[] = { on, off };
auto pe = SkDashPathEffect::Make(intervals, 2, phase);
if (!pe) {
SkDebugf("Invalid args to dash()\n");
return false;
}
SkStrokeRec rec(SkStrokeRec::InitStyle::kHairline_InitStyle);
if (pe->filterPath(&path, path, &rec, nullptr)) {
return true;
}
SkDebugf("Could not make dashed path\n");
return false;
}
bool ApplyTrim(SkPath& path, SkScalar startT, SkScalar stopT, bool isComplement) {
auto mode = isComplement ? SkTrimPathEffect::Mode::kInverted : SkTrimPathEffect::Mode::kNormal;
auto pe = SkTrimPathEffect::Make(startT, stopT, mode);
if (!pe) {
SkDebugf("Invalid args to trim(): startT and stopT must be in [0,1]\n");
return false;
}
SkStrokeRec rec(SkStrokeRec::InitStyle::kHairline_InitStyle);
if (pe->filterPath(&path, path, &rec, nullptr)) {
return true;
}
SkDebugf("Could not trim path\n");
return false;
}
struct StrokeOpts {
SkScalar width;
SkScalar miter_limit;
SkPaint::Join join;
SkPaint::Cap cap;
};
bool ApplyStroke(SkPath& path, StrokeOpts opts) {
SkPaint p;
p.setStyle(SkPaint::kStroke_Style);
p.setStrokeCap(opts.cap);
p.setStrokeJoin(opts.join);
p.setStrokeWidth(opts.width);
p.setStrokeMiter(opts.miter_limit);
return p.getFillPath(path, &path);
}
struct SimpleMatrix {
SkScalar scaleX, skewX, transX;
SkScalar skewY, scaleY, transY;
SkScalar pers0, pers1, pers2;
};
SkMatrix toSkMatrix(const SimpleMatrix& sm) {
return SkMatrix::MakeAll(sm.scaleX, sm.skewX , sm.transX,
sm.skewY , sm.scaleY, sm.transY,
sm.pers0 , sm.pers1 , sm.pers2);
}
void ApplyTransform(SkPath& orig, const SimpleMatrix& sm) {
orig.transform(toSkMatrix(sm));
}
void ApplyTransform(SkPath& orig,
SkScalar scaleX, SkScalar skewX, SkScalar transX,
SkScalar skewY, SkScalar scaleY, SkScalar transY,
SkScalar pers0, SkScalar pers1, SkScalar pers2) {
SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX,
skewY , scaleY, transY,
pers0 , pers1 , pers2);
orig.transform(m);
}
float SkBits2FloatUnsigned(uint32_t floatAsBits) {
return SkBits2Float((int32_t) floatAsBits);
}
EMSCRIPTEN_BINDINGS(skia) {
class_<SkPath>("SkPath")
.constructor<>()
.constructor<const SkPath&>()
.function("_addPath", &ApplyAddPath)
.function("_arc", &ApplyAddArc)
.function("_arcTo", &ApplyArcTo)
.function("_close", &ApplyClose)
.function("_conicTo", &ApplyConicTo)
.function("_cubicTo", &ApplyCubicTo)
.function("_ellipse", &ApplyEllipse)
.function("_lineTo", &ApplyLineTo)
.function("_moveTo", &ApplyMoveTo)
.function("_quadTo", &ApplyQuadTo)
.function("_rect", &ApplyAddRect)
.function("setFillType", &SkPath::setFillType)
.function("getFillType", &SkPath::getFillType)
.function("getFillTypeString", &GetFillTypeString)
.function("getBounds", &SkPath::getBounds)
.function("computeTightBounds", &SkPath::computeTightBounds)
.function("equals", &Equals)
.function("copy", &CopyPath)
.function("_dash", &ApplyDash)
.function("_trim", &ApplyTrim)
.function("_stroke", &ApplyStroke)
.function("_transform", select_overload<void(SkPath& orig, const SimpleMatrix& sm)>(&ApplyTransform))
.function("_transform", select_overload<void(SkPath& orig, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&ApplyTransform))
.function("_simplify", &ApplySimplify)
.function("_op", &ApplyPathOp)
.function("toCmds", &ToCmds)
.function("toPath2D", &ToPath2D)
.function("toCanvas", &ToCanvas)
.function("toSVGString", &ToSVGString)
#ifdef PATHKIT_TESTING
.function("dump", select_overload<void() const>(&SkPath::dump))
.function("dumpHex", select_overload<void() const>(&SkPath::dumpHex))
#endif
;
class_<SkOpBuilder>("SkOpBuilder")
.constructor<>()
.function("add", &SkOpBuilder::add)
.function("make", &ResolveBuilder)
.function("resolve", &ResolveBuilder);
function("FromSVGString", &FromSVGString);
function("NewPath", &NewPath);
function("NewPath", &CopyPath);
function("_FromCmds", &FromCmds);
function("MakeFromOp", &MakeFromOp);
enum_<SkPathOp>("PathOp")
.value("DIFFERENCE", SkPathOp::kDifference_SkPathOp)
.value("INTERSECT", SkPathOp::kIntersect_SkPathOp)
.value("UNION", SkPathOp::kUnion_SkPathOp)
.value("XOR", SkPathOp::kXOR_SkPathOp)
.value("REVERSE_DIFFERENCE", SkPathOp::kReverseDifference_SkPathOp);
enum_<SkPath::FillType>("FillType")
.value("WINDING", SkPath::FillType::kWinding_FillType)
.value("EVENODD", SkPath::FillType::kEvenOdd_FillType)
.value("INVERSE_WINDING", SkPath::FillType::kInverseWinding_FillType)
.value("INVERSE_EVENODD", SkPath::FillType::kInverseEvenOdd_FillType);
constant("MOVE_VERB", MOVE);
constant("LINE_VERB", LINE);
constant("QUAD_VERB", QUAD);
constant("CONIC_VERB", CONIC);
constant("CUBIC_VERB", CUBIC);
constant("CLOSE_VERB", CLOSE);
value_object<SkRect>("SkRect")
.field("fLeft", &SkRect::fLeft)
.field("fTop", &SkRect::fTop)
.field("fRight", &SkRect::fRight)
.field("fBottom", &SkRect::fBottom);
function("LTRBRect", &SkRect::MakeLTRB);
enum_<SkPaint::Join>("StrokeJoin")
.value("MITER", SkPaint::Join::kMiter_Join)
.value("ROUND", SkPaint::Join::kRound_Join)
.value("BEVEL", SkPaint::Join::kBevel_Join);
enum_<SkPaint::Cap>("StrokeCap")
.value("BUTT", SkPaint::Cap::kButt_Cap)
.value("ROUND", SkPaint::Cap::kRound_Cap)
.value("SQUARE", SkPaint::Cap::kSquare_Cap);
value_object<StrokeOpts>("StrokeOpts")
.field("width", &StrokeOpts::width)
.field("miter_limit", &StrokeOpts::miter_limit)
.field("join", &StrokeOpts::join)
.field("cap", &StrokeOpts::cap);
value_array<SimpleMatrix>("SkMatrix")
.element(&SimpleMatrix::scaleX)
.element(&SimpleMatrix::skewX)
.element(&SimpleMatrix::transX)
.element(&SimpleMatrix::skewY)
.element(&SimpleMatrix::scaleY)
.element(&SimpleMatrix::transY)
.element(&SimpleMatrix::pers0)
.element(&SimpleMatrix::pers1)
.element(&SimpleMatrix::pers2);
value_array<SkPoint>("SkPoint")
.element(&SkPoint::fX)
.element(&SkPoint::fY);
class_<SkCubicMap>("_SkCubicMap")
.constructor<SkPoint, SkPoint>()
.function("computeYFromX", &SkCubicMap::computeYFromX)
.function("computePtFromT", &SkCubicMap::computeFromT);
function("SkBits2FloatUnsigned", &SkBits2FloatUnsigned);
}