svg-path-d
Version:
SVG path data (path[d] attribute content) manipulation library.
140 lines • 6.95 kB
JavaScript
;
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createTransformed = exports.transformedNode = exports.transformedEllipse = exports.transformedPoint = exports.transformedY = exports.transformedX = void 0;
var command_assertion_1 = require("./command-assertion");
var path_node_1 = require("./path-node");
function transformedX(m, x, y) {
return m.a * x + m.c * y + m.e;
}
exports.transformedX = transformedX;
function transformedY(m, x, y) {
return m.b * x + m.d * y + m.f;
}
exports.transformedY = transformedY;
function transformedPoint(m, point) {
return { x: transformedX(m, point.x, point.y), y: transformedY(m, point.x, point.y) };
}
exports.transformedPoint = transformedPoint;
function transformedEllipse(m, ellipse) {
// Step 1. Rotate ellipse.
// The standard equation for an ellipse:
// x^2 / rx^2 + y^2 / ry^2 = 1
// It represents an ellipse centered at the origin and with axes lying along the coordinate axes.
// Applying rotation matrix to it
// | x | = | cos(phi) sin(phi) | * | x0 |
// | y | = |-sin(phi) cos(phi) | | y0 |
// leads to the following equation for a standard ellipse which has been rotated through an angle phi:
// (x * cos(phi) + y * sin(phi))^2 / rx^2 + (-x * sin(phi) + y * cos(phi))^2 / ry^2 = 1
// Which gives:
// (cos(phi)^2 / rx^2 + sin(phi)^2 / ry^2) * x^2
// + 2 * cos(phi) * sin(phi) * (1 / rx^2 - 1 / ry^2) * x * y
// + (sin(phi)^2 / rx^2 + cos(phi)^2 / ry^2) * y^2
// = 1
// Which is the general conic form: A * x^2 + B * x * y + C * y^2 = 1
var phi = (ellipse.angle * Math.PI) / 180;
var sinPhi = Math.sin(phi);
var cosPhi = Math.cos(phi);
var curveX = 1 / (ellipse.rx * ellipse.rx);
var curveY = 1 / (ellipse.ry * ellipse.ry);
// A = cos(phi)^2 / rx^2 + sin(phi)^2 / ry^2
var A = cosPhi * cosPhi * curveX + sinPhi * sinPhi * curveY;
// B = 2 * cos(phi) * sin(phi) * (1 / rx^2 - 1 / ry^2)
var B = 2 * cosPhi * sinPhi * (curveX - curveY);
// C = sin(phi)^2 / rx^2 + cos(phi)^2 / ry^2
var C = sinPhi * sinPhi * curveX + cosPhi * cosPhi * curveY;
// Step 2. Apply ellipse shape transformation matrix.
// | x1 | = | a c | * | x |
// | y1 | = | b d | | y |
// We ignore e and f, since translations don't affect the shape of the ellipse.
// A′*x^2 + B′*x*y + C′*y^2 = D′
var A_ = A * m.d * m.d - B * m.b * m.d + C * m.b * m.b;
var B_ = B * (m.a * m.d + m.b * m.c) - 2 * (A * m.c * m.d + C * m.a * m.b);
var C_ = A * m.c * m.c - B * m.a * m.c + C * m.a * m.a;
var D_ = m.a * m.d - m.b * m.c;
// Step 3. Get back to axis-aligned ellipse equation.
// | x1 | = | cos(phi′) -sin(phi′) | * | x2 |
// | y1 | = | sin(phi′) cos(phi′) | | y2 |
var phi_ = ((Math.atan2(B_, A_ - C_) + Math.PI) % Math.PI) / 2;
// Note: For any integer n, (atan2(B1, A1 - C1) + n*pi)/2 is a solution to the above.
// Incrementing n (rotating an ellipse by pi/2) just swaps the x and y radii computed below.
// Choosing the rotation between 0 and pi/2 eliminates the ambiguity and leads to more predictable output.
// Finally, we get rx′ and ry′ from the same-zeroes relationship that gave us phi′
var sinPhi_ = Math.sin(phi_);
var cosPhi_ = Math.cos(phi_);
var rx_ = Math.abs(D_) / Math.sqrt(A_ * cosPhi_ * cosPhi_ + B_ * sinPhi_ * cosPhi_ + C_ * sinPhi_ * sinPhi_);
var ry_ = Math.abs(D_) / Math.sqrt(A_ * sinPhi_ * sinPhi_ - B_ * sinPhi_ * cosPhi_ + C_ * cosPhi_ * cosPhi_);
var angle_ = (phi_ * 180) / Math.PI;
// sweepFlag needs to be inverted for a reflection transformation
var sweepFlag_ = 0 > D_ ? !ellipse.sweepFlag : ellipse.sweepFlag;
return { rx: rx_ || 0, ry: ry_ || 0, angle: angle_ || 0, largeArcFlag: ellipse.largeArcFlag, sweepFlag: sweepFlag_ };
}
exports.transformedEllipse = transformedEllipse;
function transformedNode(matrix, node) {
if (command_assertion_1.isClosePath(node)) {
return { name: node.name };
}
else if (command_assertion_1.isHLineTo(node)) {
var y0 = path_node_1.getY(node.prev);
var x = transformedX(matrix, node.x, y0);
var y = transformedY(matrix, node.x, y0);
return { name: 'L', x: x, y: y };
}
else if (command_assertion_1.isVLineTo(node)) {
var x0 = path_node_1.getX(node.prev);
var x = transformedX(matrix, x0, node.y);
var y = transformedY(matrix, x0, node.y);
return { name: 'L', x: x, y: y };
}
else {
var x = transformedX(matrix, node.x, node.y);
var y = transformedY(matrix, node.x, node.y);
if (command_assertion_1.isQCurveTo(node)) {
var x1 = transformedX(matrix, node.x1, node.y1);
var y1 = transformedY(matrix, node.x1, node.y1);
return { name: node.name, x1: x1, y1: y1, x: x, y: y };
}
else if (command_assertion_1.isSmoothCurveTo(node)) {
var x2 = transformedX(matrix, node.x2, node.y2);
var y2 = transformedY(matrix, node.x2, node.y2);
return { name: node.name, x2: x2, y2: y2, x: x, y: y };
}
else if (command_assertion_1.isCurveTo(node)) {
var x1 = transformedX(matrix, node.x1, node.y1);
var y1 = transformedY(matrix, node.x1, node.y1);
var x2 = transformedX(matrix, node.x2, node.y2);
var y2 = transformedY(matrix, node.x2, node.y2);
return { name: node.name, x1: x1, y1: y1, x2: x2, y2: y2, x: x, y: y };
}
else if (command_assertion_1.isEllipticalArc(node)) {
return __assign(__assign({ name: node.name }, transformedEllipse(matrix, node)), { x: x, y: y });
}
else {
// if (isMoveTo(node) || isLineTo(node) || isSmoothQCurveTo(node))
return { name: node.name, x: x, y: y };
}
}
}
exports.transformedNode = transformedNode;
function createTransformed(path, matrix) {
return path_node_1.clonePath(path, function (item) { return transformedNode(matrix, item); });
}
exports.createTransformed = createTransformed;
// export function createTranslated(path: PathNode[], deltaX: number, deltaY: number): PathNode[] {
// return clonePath(path, (item) => {
// const next = { ...item };
// applyTranslate(next, deltaX, deltaY);
// return next;
// });
// }
//# sourceMappingURL=transform.js.map