@shopify/react-native-skia
Version:
High-performance React Native Graphics using Skia
440 lines (430 loc) • 14.5 kB
JavaScript
import { PathVerb } from "../types";
import { getEnum, HostObject, optEnum } from "./Host";
import { JsiSkPoint } from "./JsiSkPoint";
import { JsiSkRect } from "./JsiSkRect";
import { JsiSkRRect } from "./JsiSkRRect";
import { JsiSkMatrix } from "./JsiSkMatrix";
const CommandCount = {
[PathVerb.Move]: 3,
[PathVerb.Line]: 3,
[PathVerb.Quad]: 5,
[PathVerb.Conic]: 6,
[PathVerb.Cubic]: 7,
[PathVerb.Close]: 1
};
// Track which deprecation warnings have been shown to avoid spam
const shownDeprecationWarnings = new Set();
const warnDeprecatedPathMethod = (methodName, suggestion) => {
if (shownDeprecationWarnings.has(methodName)) {
return;
}
shownDeprecationWarnings.add(methodName);
console.warn(`[react-native-skia] SkPath.${methodName}() is deprecated and will be removed in a future release. ${suggestion} See migration guide: https://shopify.github.io/react-native-skia/docs/shapes/path-migration`);
};
export const toMatrix3x3 = m => {
let matrix = m instanceof JsiSkMatrix ? Array.from(JsiSkMatrix.fromValue(m)) : m;
if (matrix.length === 16) {
matrix = [matrix[0], matrix[1], matrix[3], matrix[4], matrix[5], matrix[7], matrix[12], matrix[13], matrix[15]];
} else if (matrix.length !== 9) {
throw new Error(`Invalid matrix length: ${matrix.length}`);
}
return matrix;
};
/**
* SkPath wraps a CK PathBuilder internally, providing both mutable building
* methods and immutable query methods. Use snapshot() internally to get
* an immutable CK Path for read-only operations.
*/
export class JsiSkPath extends HostObject {
constructor(CanvasKit, ref) {
super(CanvasKit, ref, "Path");
}
/** Returns an immutable CK Path snapshot for read-only operations. */
asPath() {
return this.ref.snapshot();
}
/** Extract an immutable CK Path from a JsiSkPath value (for CK interop). */
static pathFromValue(value) {
return JsiSkPath.fromValue(value).snapshot();
}
// ---- Mutable building methods (deprecated) ----
addPath(src, matrix, extend = false) {
warnDeprecatedPathMethod("addPath", "Use Skia.PathBuilder.Make().addPath() instead.");
const srcBuilder = JsiSkPath.fromValue(src);
const srcPath = srcBuilder.snapshot();
const args = [srcPath, ...(matrix ? JsiSkMatrix.fromValue(matrix) : []), extend];
this.ref.addPath(...args);
srcPath.delete();
return this;
}
addArc(oval, startAngleInDegrees, sweepAngleInDegrees) {
warnDeprecatedPathMethod("addArc", "Use Skia.PathBuilder.Make().addArc() instead.");
this.ref.addArc(JsiSkRect.fromValue(this.CanvasKit, oval), startAngleInDegrees, sweepAngleInDegrees);
return this;
}
addOval(oval, isCCW, startIndex) {
warnDeprecatedPathMethod("addOval", "Use Skia.Path.Oval() or Skia.PathBuilder.Make().addOval() instead.");
this.ref.addOval(JsiSkRect.fromValue(this.CanvasKit, oval), isCCW, startIndex);
return this;
}
addPoly(points, close) {
warnDeprecatedPathMethod("addPoly", "Use Skia.Path.Polygon() or Skia.PathBuilder.Make().addPoly() instead.");
this.ref.addPolygon(points.map(p => Array.from(JsiSkPoint.fromValue(p))).flat(), close);
return this;
}
addRect(rect, isCCW) {
warnDeprecatedPathMethod("addRect", "Use Skia.Path.Rect() or Skia.PathBuilder.Make().addRect() instead.");
this.ref.addRect(JsiSkRect.fromValue(this.CanvasKit, rect), isCCW);
return this;
}
addRRect(rrect, isCCW) {
warnDeprecatedPathMethod("addRRect", "Use Skia.Path.RRect() or Skia.PathBuilder.Make().addRRect() instead.");
this.ref.addRRect(JsiSkRRect.fromValue(this.CanvasKit, rrect), isCCW);
return this;
}
addCircle(x, y, r) {
warnDeprecatedPathMethod("addCircle", "Use Skia.Path.Circle() or Skia.PathBuilder.Make().addCircle() instead.");
this.ref.addCircle(x, y, r);
return this;
}
moveTo(x, y) {
warnDeprecatedPathMethod("moveTo", "Use Skia.PathBuilder.Make().moveTo() instead.");
this.ref.moveTo(x, y);
return this;
}
rMoveTo(x, y) {
warnDeprecatedPathMethod("rMoveTo", "Use Skia.PathBuilder.Make().rMoveTo() instead.");
this.ref.rMoveTo(x, y);
return this;
}
lineTo(x, y) {
warnDeprecatedPathMethod("lineTo", "Use Skia.PathBuilder.Make().lineTo() instead.");
this.ref.lineTo(x, y);
return this;
}
rLineTo(x, y) {
warnDeprecatedPathMethod("rLineTo", "Use Skia.PathBuilder.Make().rLineTo() instead.");
this.ref.rLineTo(x, y);
return this;
}
quadTo(x1, y1, x2, y2) {
warnDeprecatedPathMethod("quadTo", "Use Skia.PathBuilder.Make().quadTo() instead.");
this.ref.quadTo(x1, y1, x2, y2);
return this;
}
rQuadTo(x1, y1, x2, y2) {
warnDeprecatedPathMethod("rQuadTo", "Use Skia.PathBuilder.Make().rQuadTo() instead.");
this.ref.rQuadTo(x1, y1, x2, y2);
return this;
}
conicTo(x1, y1, x2, y2, w) {
warnDeprecatedPathMethod("conicTo", "Use Skia.PathBuilder.Make().conicTo() instead.");
this.ref.conicTo(x1, y1, x2, y2, w);
return this;
}
rConicTo(x1, y1, x2, y2, w) {
warnDeprecatedPathMethod("rConicTo", "Use Skia.PathBuilder.Make().rConicTo() instead.");
this.ref.rConicTo(x1, y1, x2, y2, w);
return this;
}
cubicTo(cpx1, cpy1, cpx2, cpy2, x, y) {
warnDeprecatedPathMethod("cubicTo", "Use Skia.PathBuilder.Make().cubicTo() instead.");
this.ref.cubicTo(cpx1, cpy1, cpx2, cpy2, x, y);
return this;
}
rCubicTo(cpx1, cpy1, cpx2, cpy2, x, y) {
warnDeprecatedPathMethod("rCubicTo", "Use Skia.PathBuilder.Make().rCubicTo() instead.");
this.ref.rCubicTo(cpx1, cpy1, cpx2, cpy2, x, y);
return this;
}
close() {
warnDeprecatedPathMethod("close", "Use Skia.PathBuilder.Make().close() instead.");
this.ref.close();
return this;
}
reset() {
warnDeprecatedPathMethod("reset", "Use Skia.PathBuilder.Make().reset() instead.");
// CK PathBuilder has no reset — recreate
const newBuilder = new this.CanvasKit.PathBuilder();
if (this.ref !== null && typeof this.ref === "object" && "delete" in this.ref && typeof this.ref.delete === "function") {
this.ref.delete();
}
this.ref = newBuilder;
return this;
}
rewind() {
warnDeprecatedPathMethod("rewind", "Use Skia.PathBuilder.Make().reset() instead.");
return this.reset();
}
arcToOval(oval, startAngleInDegrees, sweepAngleInDegrees, forceMoveTo) {
warnDeprecatedPathMethod("arcToOval", "Use Skia.PathBuilder.Make().arcToOval() instead.");
this.ref.arcToOval(JsiSkRect.fromValue(this.CanvasKit, oval), startAngleInDegrees, sweepAngleInDegrees, forceMoveTo);
return this;
}
arcToRotated(rx, ry, xAxisRotateInDegrees, useSmallArc, isCCW, x, y) {
warnDeprecatedPathMethod("arcToRotated", "Use Skia.PathBuilder.Make().arcToRotated() instead.");
this.ref.arcToRotated(rx, ry, xAxisRotateInDegrees, useSmallArc, isCCW, x, y);
return this;
}
rArcTo(rx, ry, xAxisRotateInDegrees, useSmallArc, isCCW, dx, dy) {
warnDeprecatedPathMethod("rArcTo", "Use Skia.PathBuilder.Make().rArcTo() instead.");
this.ref.rArcTo(rx, ry, xAxisRotateInDegrees, useSmallArc, isCCW, dx, dy);
return this;
}
arcToTangent(x1, y1, x2, y2, radius) {
warnDeprecatedPathMethod("arcToTangent", "Use Skia.PathBuilder.Make().arcToTangent() instead.");
this.ref.arcToTangent(x1, y1, x2, y2, radius);
return this;
}
setFillType(fill) {
warnDeprecatedPathMethod("setFillType", "Use Skia.PathBuilder.Make().setFillType() instead.");
this.ref.setFillType(getEnum(this.CanvasKit, "FillType", fill));
return this;
}
setIsVolatile(_volatile) {
warnDeprecatedPathMethod("setIsVolatile", "Use Skia.PathBuilder.Make().setIsVolatile() instead.");
// Not supported in CK PathBuilder — no-op
return this;
}
// ---- Mutable path operations (deprecated) ----
offset(dx, dy) {
warnDeprecatedPathMethod("offset", "Use Skia.PathBuilder.Make().offset() instead.");
this.ref.offset(dx, dy);
return this;
}
transform(m) {
warnDeprecatedPathMethod("transform", "Use Skia.PathBuilder.Make().transform() instead.");
const matrix = toMatrix3x3(m);
this.ref.transform(matrix);
return this;
}
makeAsWinding() {
warnDeprecatedPathMethod("makeAsWinding", "Use Skia.Path.AsWinding(path) instead.");
const path = this.asPath();
const result = path.makeAsWinding();
path.delete();
if (result === null) {
return null;
}
const old = this.ref;
this.ref = new this.CanvasKit.PathBuilder(result);
result.delete();
old.delete();
return this;
}
simplify() {
warnDeprecatedPathMethod("simplify", "Use Skia.Path.Simplify(path) instead.");
const path = this.asPath();
const result = path.makeSimplified();
path.delete();
if (result === null) {
return false;
}
const old = this.ref;
this.ref = new this.CanvasKit.PathBuilder(result);
result.delete();
old.delete();
return true;
}
op(path, op) {
warnDeprecatedPathMethod("op", "Use Skia.Path.MakeFromOp() instead.");
const self = this.asPath();
const other = JsiSkPath.fromValue(path).snapshot();
const result = self.makeCombined(other, getEnum(this.CanvasKit, "PathOp", op));
self.delete();
other.delete();
if (result === null) {
return false;
}
const old = this.ref;
this.ref = new this.CanvasKit.PathBuilder(result);
result.delete();
old.delete();
return true;
}
dash(on, off, phase) {
warnDeprecatedPathMethod("dash", "Use Skia.Path.Dash(path, on, off, phase) instead.");
const path = this.asPath();
const result = path.makeDashed(on, off, phase);
path.delete();
if (result === null) {
return false;
}
const old = this.ref;
this.ref = new this.CanvasKit.PathBuilder(result);
result.delete();
old.delete();
return true;
}
stroke(opts) {
warnDeprecatedPathMethod("stroke", "Use Skia.Path.Stroke(path, opts) instead.");
const path = this.asPath();
const result = path.makeStroked(opts === undefined ? undefined : {
width: opts.width,
// eslint-disable-next-line camelcase
miter_limit: opts.miter_limit,
precision: opts.precision,
join: optEnum(this.CanvasKit, "StrokeJoin", opts.join),
cap: optEnum(this.CanvasKit, "StrokeCap", opts.cap)
});
path.delete();
if (result === null) {
return null;
}
const old = this.ref;
this.ref = new this.CanvasKit.PathBuilder(result);
result.delete();
old.delete();
return this;
}
trim(start, stop, isComplement) {
warnDeprecatedPathMethod("trim", "Use Skia.Path.Trim(path, start, end, isComplement) instead.");
const startT = Math.min(Math.max(start, 0), 1);
const stopT = Math.min(Math.max(stop, 0), 1);
if (startT === 0 && stopT === 1 && !isComplement) {
return this;
}
const path = this.asPath();
const result = path.makeTrimmed(startT, stopT, isComplement);
path.delete();
if (result === null) {
return null;
}
const old = this.ref;
this.ref = new this.CanvasKit.PathBuilder(result);
result.delete();
old.delete();
return this;
}
// ---- Query methods (use snapshot for read-only) ----
countPoints() {
return this.ref.countPoints();
}
computeTightBounds() {
const path = this.asPath();
const result = new JsiSkRect(this.CanvasKit, path.computeTightBounds());
path.delete();
return result;
}
contains(x, y) {
const path = this.asPath();
const result = path.contains(x, y);
path.delete();
return result;
}
copy() {
const path = this.asPath();
const result = new JsiSkPath(this.CanvasKit, new this.CanvasKit.PathBuilder(path));
path.delete();
return result;
}
equals(other) {
const p1 = this.asPath();
const p2 = JsiSkPath.fromValue(other).snapshot();
const result = p1.equals(p2);
p1.delete();
p2.delete();
return result;
}
getBounds() {
return new JsiSkRect(this.CanvasKit, this.ref.getBounds());
}
getFillType() {
const path = this.asPath();
const result = path.getFillType().value;
path.delete();
return result;
}
getPoint(index) {
const path = this.asPath();
const result = new JsiSkPoint(this.CanvasKit, path.getPoint(index));
path.delete();
return result;
}
isEmpty() {
return this.ref.isEmpty();
}
isVolatile() {
return false;
}
getLastPt() {
const count = this.ref.countPoints();
if (count === 0) {
return {
x: 0,
y: 0
};
}
const path = this.asPath();
const pt = path.getPoint(count - 1);
path.delete();
return {
x: pt[0],
y: pt[1]
};
}
toSVGString() {
const path = this.asPath();
const result = path.toSVGString();
path.delete();
return result;
}
isInterpolatable(path2) {
const p1 = this.asPath();
const p2 = JsiSkPath.fromValue(path2).snapshot();
const result = this.CanvasKit.Path.CanInterpolate(p1, p2);
p1.delete();
p2.delete();
return result;
}
interpolate(end, weight, output) {
const p1 = this.asPath();
const p2 = JsiSkPath.fromValue(end).snapshot();
const path = this.CanvasKit.Path.MakeFromPathInterpolation(p1, p2, weight);
p1.delete();
p2.delete();
if (path === null) {
return null;
}
if (output) {
const outRef = output;
const old = outRef.ref;
outRef.ref = new this.CanvasKit.PathBuilder(path);
path.delete();
old.delete();
return output;
}
const result = new JsiSkPath(this.CanvasKit, new this.CanvasKit.PathBuilder(path));
path.delete();
return result;
}
toCmds() {
const path = this.asPath();
const cmds = path.toCmds();
path.delete();
const result = cmds.reduce((acc, cmd, i) => {
if (i === 0) {
acc.push([]);
}
const current = acc[acc.length - 1];
if (current.length === 0) {
current.push(cmd);
const length = CommandCount[current[0]];
if (current.length === length && i !== cmds.length - 1) {
acc.push([]);
}
} else {
const length = CommandCount[current[0]];
if (current.length < length) {
current.push(cmd);
}
if (current.length === length && i !== cmds.length - 1) {
acc.push([]);
}
}
return acc;
}, []);
return result;
}
}
//# sourceMappingURL=JsiSkPath.js.map