Files
enviPy-bayer/static/js/ketcher2/node_modules/svg-pathdata/src/SVGPathDataTransformer.js
2025-06-23 20:13:54 +02:00

417 lines
14 KiB
JavaScript
Executable File

'use strict';
// Transform SVG PathData
// http://www.w3.org/TR/SVG/paths.html#PathDataBNF
// a2c utility
var a2c = require('./a2c.js');
// Access to SVGPathData constructor
var SVGPathData = require('./SVGPathData.js');
// TransformStream inherance required modules
var TransformStream = require('readable-stream').Transform;
var util = require('util');
// Inherit of transform stream
util.inherits(SVGPathDataTransformer, TransformStream);
function SVGPathDataTransformer(transformFunction) {
// Ensure new were used
if(!(this instanceof SVGPathDataTransformer)) {
return new (SVGPathDataTransformer.bind.apply(SVGPathDataTransformer,
[SVGPathDataTransformer].concat([].slice.call(arguments, 0))));
}
// Transform function needed
if('function' !== typeof transformFunction) {
throw new Error('Please provide a transform callback to receive commands.');
}
this._transformer = transformFunction.apply(null, [].slice.call(arguments, 1));
if('function' !== typeof this._transformer) {
throw new Error('Please provide a valid transform (returning a function).');
}
// Parent constructor
TransformStream.call(this, {
objectMode: true,
});
}
SVGPathDataTransformer.prototype._transform = function(commands, encoding, done) {
var i;
var j;
if(!(commands instanceof Array)) {
commands = [commands];
}
for(i = 0, j = commands.length; i < j; i++) {
this.push(this._transformer(commands[i]));
}
done();
};
// Predefined transforming functions
// Rounds commands values
SVGPathDataTransformer.ROUND = function roundGenerator(roundVal) {
roundVal = roundVal || 10e12;
return function round(command) {
// x1/y1 values
if('undefined' !== typeof command.x1) {
command.x1 = Math.round(command.x1 * roundVal) / roundVal;
}
if('undefined' !== typeof command.y1) {
command.y1 = Math.round(command.y1 * roundVal) / roundVal;
}
// x2/y2 values
if('undefined' !== typeof command.x2) {
command.x2 = Math.round(command.x2 * roundVal) / roundVal;
}
if('undefined' !== typeof command.y2) {
command.y2 = Math.round(command.y2 * roundVal) / roundVal;
}
// Finally x/y values
if('undefined' !== typeof command.x) {
command.x = Math.round(command.x * roundVal, 12) / roundVal;
}
if('undefined' !== typeof command.y) {
command.y = Math.round(command.y * roundVal, 12) / roundVal;
}
return command;
};
};
// Relative to absolute commands
SVGPathDataTransformer.TO_ABS = function toAbsGenerator() {
var prevX = 0;
var prevY = 0;
var pathStartX = NaN;
var pathStartY = NaN;
return function toAbs(command) {
if(isNaN(pathStartX) && (command.type & SVGPathData.DRAWING_COMMANDS)) {
pathStartX = prevX;
pathStartY = prevY;
}
if((command.type & SVGPathData.CLOSE_PATH) && !isNaN(pathStartX)) {
prevX = isNaN(pathStartX) ? 0 : pathStartX;
prevY = isNaN(pathStartY) ? 0 : pathStartY;
pathStartX = NaN;
pathStartY = NaN;
}
if(command.relative) {
// x1/y1 values
if('undefined' !== typeof command.x1) {
command.x1 = prevX + command.x1;
}
if('undefined' !== typeof command.y1) {
command.y1 = prevY + command.y1;
}
// x2/y2 values
if('undefined' !== typeof command.x2) {
command.x2 = prevX + command.x2;
}
if('undefined' !== typeof command.y2) {
command.y2 = prevY + command.y2;
}
// Finally x/y values
if('undefined' !== typeof command.x) {
command.x = prevX + command.x;
}
if('undefined' !== typeof command.y) {
command.y = prevY + command.y;
}
command.relative = false;
}
prevX = ('undefined' !== typeof command.x ? command.x : prevX);
prevY = ('undefined' !== typeof command.y ? command.y : prevY);
if(command.type & SVGPathData.MOVE_TO) {
pathStartX = prevX;
pathStartY = prevY;
}
return command;
};
};
// Absolute to relative commands
SVGPathDataTransformer.TO_REL = function toRelGenerator() {
var prevX = 0;
var prevY = 0;
return function toRel(command) {
if(!command.relative) {
// x1/y1 values
if('undefined' !== typeof command.x1) {
command.x1 -= prevX;
}
if('undefined' !== typeof command.y1) {
command.y1 -= prevY;
}
// x2/y2 values
if('undefined' !== typeof command.x2) {
command.x2 -= prevX;
}
if('undefined' !== typeof command.y2) {
command.y2 -= prevY;
}
// Finally x/y values
if('undefined' !== typeof command.x) {
command.x -= prevX;
}
if('undefined' !== typeof command.y) {
command.y -= prevY;
}
command.relative = true;
}
prevX = ('undefined' !== typeof command.x ? prevX + command.x : prevX);
prevY = ('undefined' !== typeof command.y ? prevY + command.y : prevY);
return command;
};
};
// SVG Transforms : http://www.w3.org/TR/SVGTiny12/coords.html#TransformList
// Matrix : http://apike.ca/prog_svg_transform.html
SVGPathDataTransformer.MATRIX = function matrixGenerator(a, b, c, d, e, f) {
var prevX;
var prevY;
if('number' !== typeof a || 'number' !== typeof b ||
'number' !== typeof c || 'number' !== typeof d ||
'number' !== typeof e || 'number' !== typeof f) {
throw new Error('A matrix transformation requires parameters' +
' [a,b,c,d,e,f] to be set and to be numbers.');
}
return function matrix(command) {
var origX = command.x;
var origX1 = command.x1;
var origX2 = command.x2;
if('undefined' !== typeof command.x) {
command.x = (command.x * a) +
('undefined' !== typeof command.y ?
command.y : (command.relative ? 0 : prevY || 0)
) * c +
(command.relative && 'undefined' !== typeof prevX ? 0 : e);
}
if('undefined' !== typeof command.y) {
command.y = ('undefined' !== typeof origX ?
origX : (command.relative ? 0 : prevX || 0)
) * b +
command.y * d +
(command.relative && 'undefined' !== typeof prevY ? 0 : f);
}
if('undefined' !== typeof command.x1) {
command.x1 = command.x1 * a + command.y1 * c +
(command.relative && 'undefined' !== typeof prevX ? 0 : e);
}
if('undefined' !== typeof command.y1) {
command.y1 = origX1 * b + command.y1 * d +
(command.relative && 'undefined' !== typeof prevY ? 0 : f);
}
if('undefined' !== typeof command.x2) {
command.x2 = command.x2 * a + command.y2 * c +
(command.relative && 'undefined' !== typeof prevX ? 0 : e);
}
if('undefined' !== typeof command.y2) {
command.y2 = origX2 * b + command.y2 * d +
(command.relative && 'undefined' !== typeof prevY ? 0 : f);
}
function sq(x) { return x*x; }
var det = a*d - b*c;
if('undefined' !== typeof command.xRot) {
// Skip if this is a pure translation
if(a !== 1 || b !== 0 || c !== 0 || d !== 1) {
// Special case for singular matrix
if(det === 0) {
// In the singular case, the arc is compressed to a line. The actual geometric image of the original
// curve under this transform possibly extends beyond the starting and/or ending points of the segment, but
// for simplicity we ignore this detail and just replace this command with a single line segment.
delete command.rX;
delete command.rY;
delete command.xRot;
delete command.lArcFlag;
delete command.sweepFlag;
command.type = SVGPathData.LINE_TO;
} else {
// Convert to radians
var xRot = command.xRot*Math.PI/180;
// Convert rotated ellipse to general conic form
// x0^2/rX^2 + y0^2/rY^2 - 1 = 0
// x0 = x*cos(xRot) + y*sin(xRot)
// y0 = -x*sin(xRot) + y*cos(xRot)
// --> A*x^2 + B*x*y + C*y^2 - 1 = 0, where
var sinRot = Math.sin(xRot), cosRot = Math.cos(xRot),
xCurve = 1/sq(command.rX), yCurve = 1/sq(command.rY);
var A = sq(cosRot)*xCurve + sq(sinRot)*yCurve,
B = 2*sinRot*cosRot*(xCurve - yCurve),
C = sq(sinRot)*xCurve + sq(cosRot)*yCurve;
// Apply matrix to A*x^2 + B*x*y + C*y^2 - 1 = 0
// x1 = a*x + c*y
// y1 = b*x + d*y
// (we can ignore e and f, since pure translations don't affect the shape of the ellipse)
// --> A1*x1^2 + B1*x1*y1 + C1*y1^2 - det^2 = 0, where
var A1 = A*d*d - B*b*d + C*b*b,
B1 = B*(a*d + b*c) - 2*(A*c*d + C*a*b),
C1 = A*c*c - B*a*c + C*a*a;
// Unapply newXRot to get back to axis-aligned ellipse equation
// x1 = x2*cos(newXRot) - y2*sin(newXRot)
// y1 = x2*sin(newXRot) + y2*cos(newXRot)
// A1*x1^2 + B1*x1*y1 + C1*y1^2 - det^2 =
// x2^2*(A1*cos(newXRot)^2 + B1*sin(newXRot)*cos(newXRot) + C1*sin(newXRot)^2)
// + x2*y2*(2*(C1 - A1)*sin(newXRot)*cos(newXRot) + B1*(cos(newXRot)^2 - sin(newXRot)^2))
// + y2^2*(A1*sin(newXRot)^2 - B1*sin(newXRot)*cos(newXRot) + C1*cos(newXRot)^2)
// (which must have the same zeroes as)
// x2^2/newRX^2 + y2^2/newRY^2 - 1
// (so we have)
// 2*(C1 - A1)*sin(newXRot)*cos(newXRot) + B1*(cos(newXRot)^2 - sin(newXRot)^2) = 0
// (A1 - C1)*sin(2*newXRot) = B1*cos(2*newXRot)
// 2*newXRot = atan2(B1, A1 - C1)
var newXRot = ((Math.atan2(B1, A1 - C1) + Math.PI) % Math.PI)/2;
// For any integer n, (atan2(B1, A1 - C1) + n*pi)/2 is a solution to the above; incrementing n just swaps the
// x and y radii computed below (since that's what rotating an ellipse by pi/2 does). Choosing the rotation
// between 0 and pi/2 eliminates the ambiguity and leads to more predictable output.
// Finally, we get newRX and newRY from the same-zeroes relationship that gave us newXRot
var newSinRot = Math.sin(newXRot), newCosRot = Math.cos(newXRot);
command.rX = Math.abs(det)/Math.sqrt(A1*sq(newCosRot) + B1*newSinRot*newCosRot + C1*sq(newSinRot));
command.rY = Math.abs(det)/Math.sqrt(A1*sq(newSinRot) - B1*newSinRot*newCosRot + C1*sq(newCosRot));
command.xRot = newXRot*180/Math.PI;
}
}
}
// sweepFlag needs to be inverted when mirroring shapes
// see http://www.itk.ilstu.edu/faculty/javila/SVG/SVG_drawing1/elliptical_curve.htm
// m 65,10 a 50,25 0 1 0 50,25
// M 65,60 A 50,25 0 1 1 115,35
if('undefined' !== typeof command.sweepFlag) {
command.sweepFlag = (command.sweepFlag + (0 <= det ? 0 : 1)) % 2;
}
prevX = ('undefined' !== typeof command.x ?
(command.relative ? (prevX || 0) + command.x : command.x) :
prevX || 0);
prevY = ('undefined' !== typeof command.y ?
(command.relative ? (prevY || 0) + command.y : command.y) :
prevY || 0);
return command;
};
};
// Rotation
SVGPathDataTransformer.ROTATE = function rotateGenerator(a, x, y) {
if('number' !== typeof a) {
throw new Error('A rotate transformation requires the parameter a' +
' to be set and to be a number.');
}
return (function(toOrigin, doRotate, fromOrigin) {
return function rotate(command) {
return fromOrigin(doRotate(toOrigin(command)));
};
})(SVGPathDataTransformer.TRANSLATE(-(x || 0), -(y || 0)),
SVGPathDataTransformer.MATRIX(Math.cos(a), Math.sin(a),
-Math.sin(a), Math.cos(a), 0, 0),
SVGPathDataTransformer.TRANSLATE(x || 0, y || 0)
);
};
// Translation
SVGPathDataTransformer.TRANSLATE = function translateGenerator(dX, dY) {
if('number' !== typeof dX) {
throw new Error('A translate transformation requires the parameter dX' +
' to be set and to be a number.');
}
return SVGPathDataTransformer.MATRIX(1, 0, 0, 1, dX, dY || 0);
};
// Scaling
SVGPathDataTransformer.SCALE = function scaleGenerator(dX, dY) {
if('number' !== typeof dX) {
throw new Error('A scale transformation requires the parameter dX' +
' to be set and to be a number.');
}
return SVGPathDataTransformer.MATRIX(dX, 0, 0, dY || dX, 0, 0);
};
// Skew
SVGPathDataTransformer.SKEW_X = function skewXGenerator(a) {
if('number' !== typeof a) {
throw new Error('A skewX transformation requires the parameter x' +
' to be set and to be a number.');
}
return SVGPathDataTransformer.MATRIX(1, 0, Math.atan(a), 1, 0, 0);
};
SVGPathDataTransformer.SKEW_Y = function skewYGenerator(a) {
if('number' !== typeof a) {
throw new Error('A skewY transformation requires the parameter y' +
' to be set and to be a number.');
}
return SVGPathDataTransformer.MATRIX(1, Math.atan(a), 0, 1, 0, 0);
};
// Symetry througth the X axis
SVGPathDataTransformer.X_AXIS_SIMETRY = function xSymetryGenerator(xDecal) {
return (function(toAbs, scale, translate) {
return function xSymetry(command) {
return translate(scale(toAbs(command)));
};
})(SVGPathDataTransformer.TO_ABS(),
SVGPathDataTransformer.SCALE(-1, 1),
SVGPathDataTransformer.TRANSLATE(xDecal || 0, 0)
);
};
// Symetry througth the Y axis
SVGPathDataTransformer.Y_AXIS_SIMETRY = function ySymetryGenerator(yDecal) {
return (function(toAbs, scale, translate) {
return function ySymetry(command) {
return translate(scale(toAbs(command)));
};
})(SVGPathDataTransformer.TO_ABS(),
SVGPathDataTransformer.SCALE(1, -1),
SVGPathDataTransformer.TRANSLATE(0, yDecal || 0)
);
};
// Convert arc commands to curve commands
SVGPathDataTransformer.A_TO_C = function a2CGenerator() {
var prevX = 0;
var prevY = 0;
var args;
return (function(toAbs) {
return function a2C(command) {
var commands = [];
var i;
var ii;
command = toAbs(command);
if(command.type === SVGPathData.ARC) {
args = a2c(prevX, prevY, command.rX, command.rX, command.xRot,
command.lArcFlag, command.sweepFlag, command.x, command.y);
prevX = command.x; prevY = command.y;
for(i = 0, ii = args.length; i < ii; i += 6) {
commands.push({
type: SVGPathData.CURVE_TO,
relative: false,
x2: args[i],
y2: args[i + 1],
x1: args[i + 2],
y1: args[i + 3],
x: args[i + 4],
y: args[i + 5],
});
}
return commands;
}
prevX = command.x; prevY = command.y;
return command;
};
})(SVGPathDataTransformer.TO_ABS());
};
module.exports = SVGPathDataTransformer;