summaryrefslogtreecommitdiff
path: root/node_modules/svgo/plugins/_transforms.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/svgo/plugins/_transforms.js')
-rw-r--r--node_modules/svgo/plugins/_transforms.js241
1 files changed, 136 insertions, 105 deletions
diff --git a/node_modules/svgo/plugins/_transforms.js b/node_modules/svgo/plugins/_transforms.js
index efc141c7..1ab6d648 100644
--- a/node_modules/svgo/plugins/_transforms.js
+++ b/node_modules/svgo/plugins/_transforms.js
@@ -1,176 +1,182 @@
'use strict';
-const regTransformTypes = /matrix|translate|scale|rotate|skewX|skewY/;
-const regTransformSplit =
- /\s*(matrix|translate|scale|rotate|skewX|skewY)\s*\(\s*(.+?)\s*\)[\s,]*/;
-const regNumericValues = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;
+const { toFixed } = require('../lib/svgo/tools');
/**
- * @typedef {{ name: string, data: Array<number> }} TransformItem
+ * @typedef {{ name: string, data: number[] }} TransformItem
+ * @typedef {{
+ * convertToShorts: boolean,
+ * floatPrecision: number,
+ * transformPrecision: number,
+ * matrixToTransform: boolean,
+ * shortTranslate: boolean,
+ * shortScale: boolean,
+ * shortRotate: boolean,
+ * removeUseless: boolean,
+ * collapseIntoOne: boolean,
+ * leadingZero: boolean,
+ * negativeExtraSpace: boolean,
+ * }} TransformParams
*/
+const transformTypes = new Set([
+ 'matrix',
+ 'rotate',
+ 'scale',
+ 'skewX',
+ 'skewY',
+ 'translate',
+]);
+
+const regTransformSplit =
+ /\s*(matrix|translate|scale|rotate|skewX|skewY)\s*\(\s*(.+?)\s*\)[\s,]*/;
+const regNumericValues = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;
+
/**
* Convert transform string to JS representation.
*
- * @type {(transformString: string) => Array<TransformItem>}
+ * @param {string} transformString
+ * @returns {TransformItem[]} Object representation of transform, or an empty array if it was malformed.
*/
exports.transform2js = (transformString) => {
- // JS representation of the transform data
- /**
- * @type {Array<TransformItem>}
- */
+ /** @type {TransformItem[]} */
const transforms = [];
- // current transform context
- /**
- * @type {?TransformItem}
- */
- let current = null;
+ /** @type {?TransformItem} */
+ let currentTransform = null;
+
// split value into ['', 'translate', '10 50', '', 'scale', '2', '', 'rotate', '-45', '']
for (const item of transformString.split(regTransformSplit)) {
- var num;
- if (item) {
- // if item is a translate function
- if (regTransformTypes.test(item)) {
- // then collect it and change current context
- current = { name: item, data: [] };
- transforms.push(current);
- // else if item is data
- } else {
- // then split it into [10, 50] and collect as context.data
- // eslint-disable-next-line no-cond-assign
- while ((num = regNumericValues.exec(item))) {
- num = Number(num);
- if (current != null) {
- current.data.push(num);
- }
+ if (!item) {
+ continue;
+ }
+
+ if (transformTypes.has(item)) {
+ currentTransform = { name: item, data: [] };
+ transforms.push(currentTransform);
+ } else {
+ let num;
+ // then split it into [10, 50] and collect as context.data
+ while ((num = regNumericValues.exec(item))) {
+ num = Number(num);
+ if (currentTransform != null) {
+ currentTransform.data.push(num);
}
}
}
}
- // return empty array if broken transform (no data)
- return current == null || current.data.length == 0 ? [] : transforms;
+
+ return currentTransform == null || currentTransform.data.length == 0
+ ? []
+ : transforms;
};
/**
* Multiply transforms into one.
*
- * @type {(transforms: Array<TransformItem>) => TransformItem}
+ * @param {TransformItem[]} transforms
+ * @returns {TransformItem}
*/
exports.transformsMultiply = (transforms) => {
- // convert transforms objects to the matrices
const matrixData = transforms.map((transform) => {
if (transform.name === 'matrix') {
return transform.data;
}
return transformToMatrix(transform);
});
- // multiply all matrices into one
+
const matrixTransform = {
name: 'matrix',
data:
matrixData.length > 0 ? matrixData.reduce(multiplyTransformMatrices) : [],
};
+
return matrixTransform;
};
/**
- * math utilities in radians.
+ * Math utilities in radians.
*/
const mth = {
/**
- * @type {(deg: number) => number}
+ * @param {number} deg
+ * @returns {number}
*/
rad: (deg) => {
return (deg * Math.PI) / 180;
},
/**
- * @type {(rad: number) => number}
+ * @param {number} rad
+ * @returns {number}
*/
deg: (rad) => {
return (rad * 180) / Math.PI;
},
/**
- * @type {(deg: number) => number}
+ * @param {number} deg
+ * @returns {number}
*/
cos: (deg) => {
return Math.cos(mth.rad(deg));
},
/**
- * @type {(val: number, floatPrecision: number) => number}
+ * @param {number} val
+ * @param {number} floatPrecision
+ * @returns {number}
*/
acos: (val, floatPrecision) => {
- return Number(mth.deg(Math.acos(val)).toFixed(floatPrecision));
+ return toFixed(mth.deg(Math.acos(val)), floatPrecision);
},
/**
- * @type {(deg: number) => number}
+ * @param {number} deg
+ * @returns {number}
*/
sin: (deg) => {
return Math.sin(mth.rad(deg));
},
/**
- * @type {(val: number, floatPrecision: number) => number}
+ * @param {number} val
+ * @param {number} floatPrecision
+ * @returns {number}
*/
asin: (val, floatPrecision) => {
- return Number(mth.deg(Math.asin(val)).toFixed(floatPrecision));
+ return toFixed(mth.deg(Math.asin(val)), floatPrecision);
},
/**
- * @type {(deg: number) => number}
+ * @param {number} deg
+ * @returns {number}
*/
tan: (deg) => {
return Math.tan(mth.rad(deg));
},
/**
- * @type {(val: number, floatPrecision: number) => number}
+ * @param {number} val
+ * @param {number} floatPrecision
+ * @returns {number}
*/
atan: (val, floatPrecision) => {
- return Number(mth.deg(Math.atan(val)).toFixed(floatPrecision));
+ return toFixed(mth.deg(Math.atan(val)), floatPrecision);
},
};
/**
- * @typedef {{
- * convertToShorts: boolean,
- * floatPrecision: number,
- * transformPrecision: number,
- * matrixToTransform: boolean,
- * shortTranslate: boolean,
- * shortScale: boolean,
- * shortRotate: boolean,
- * removeUseless: boolean,
- * collapseIntoOne: boolean,
- * leadingZero: boolean,
- * negativeExtraSpace: boolean,
- * }} TransformParams
- */
-
-/**
- * Decompose matrix into simple transforms. See
- * https://frederic-wang.fr/decomposition-of-2d-transform-matrices.html
+ * Decompose matrix into simple transforms.
*
- * @type {(transform: TransformItem, params: TransformParams) => Array<TransformItem>}
+ * @param {TransformItem} transform
+ * @param {TransformParams} params
+ * @returns {TransformItem[]}
+ * @see https://frederic-wang.fr/decomposition-of-2d-transform-matrices.html
*/
exports.matrixToTransform = (transform, params) => {
- let floatPrecision = params.floatPrecision;
- let data = transform.data;
- let transforms = [];
- let sx = Number(
- Math.hypot(data[0], data[1]).toFixed(params.transformPrecision)
- );
- let sy = Number(
- ((data[0] * data[3] - data[1] * data[2]) / sx).toFixed(
- params.transformPrecision
- )
- );
- let colsSum = data[0] * data[2] + data[1] * data[3];
- let rowsSum = data[0] * data[1] + data[2] * data[3];
- let scaleBefore = rowsSum != 0 || sx == sy;
+ const floatPrecision = params.floatPrecision;
+ const data = transform.data;
+ const transforms = [];
// [..., ..., ..., ..., tx, ty] → translate(tx, ty)
if (data[4] || data[5]) {
@@ -180,6 +186,15 @@ exports.matrixToTransform = (transform, params) => {
});
}
+ let sx = toFixed(Math.hypot(data[0], data[1]), params.transformPrecision);
+ let sy = toFixed(
+ (data[0] * data[3] - data[1] * data[2]) / sx,
+ params.transformPrecision,
+ );
+ const colsSum = data[0] * data[2] + data[1] * data[3];
+ const rowsSum = data[0] * data[1] + data[2] * data[3];
+ const scaleBefore = rowsSum !== 0 || sx === sy;
+
// [sx, 0, tan(a)·sy, sy, 0, 0] → skewX(a)·scale(sx, sy)
if (!data[1] && data[2]) {
transforms.push({
@@ -198,19 +213,34 @@ exports.matrixToTransform = (transform, params) => {
// [sx·cos(a), sx·sin(a), sy·-sin(a), sy·cos(a), x, y] → rotate(a[, cx, cy])·(scale or skewX) or
// [sx·cos(a), sy·sin(a), sx·-sin(a), sy·cos(a), x, y] → scale(sx, sy)·rotate(a[, cx, cy]) (if !scaleBefore)
- } else if (!colsSum || (sx == 1 && sy == 1) || !scaleBefore) {
+ } else if (!colsSum || (sx === 1 && sy === 1) || !scaleBefore) {
if (!scaleBefore) {
- sx = (data[0] < 0 ? -1 : 1) * Math.hypot(data[0], data[2]);
- sy = (data[3] < 0 ? -1 : 1) * Math.hypot(data[1], data[3]);
+ sx = Math.hypot(data[0], data[2]);
+ sy = Math.hypot(data[1], data[3]);
+
+ if (toFixed(data[0], params.transformPrecision) < 0) {
+ sx = -sx;
+ }
+
+ if (
+ data[3] < 0 ||
+ (Math.sign(data[1]) === Math.sign(data[2]) &&
+ toFixed(data[3], params.transformPrecision) === 0)
+ ) {
+ sy = -sy;
+ }
+
transforms.push({ name: 'scale', data: [sx, sy] });
}
- var angle = Math.min(Math.max(-1, data[0] / sx), 1),
- rotate = [
- mth.acos(angle, floatPrecision) *
- ((scaleBefore ? 1 : sy) * data[1] < 0 ? -1 : 1),
- ];
+ const angle = Math.min(Math.max(-1, data[0] / sx), 1);
+ const rotate = [
+ mth.acos(angle, floatPrecision) *
+ ((scaleBefore ? 1 : sy) * data[1] < 0 ? -1 : 1),
+ ];
- if (rotate[0]) transforms.push({ name: 'rotate', data: rotate });
+ if (rotate[0]) {
+ transforms.push({ name: 'rotate', data: rotate });
+ }
if (rowsSum && colsSum)
transforms.push({
@@ -221,15 +251,15 @@ exports.matrixToTransform = (transform, params) => {
// rotate(a, cx, cy) can consume translate() within optional arguments cx, cy (rotation point)
if (rotate[0] && (data[4] || data[5])) {
transforms.shift();
- var cos = data[0] / sx,
- sin = data[1] / (scaleBefore ? sx : sy),
- x = data[4] * (scaleBefore ? 1 : sy),
- y = data[5] * (scaleBefore ? 1 : sx),
- denom =
- (Math.pow(1 - cos, 2) + Math.pow(sin, 2)) *
- (scaleBefore ? 1 : sx * sy);
- rotate.push(((1 - cos) * x - sin * y) / denom);
- rotate.push(((1 - cos) * y + sin * x) / denom);
+ const oneOverCos = 1 - data[0] / sx;
+ const sin = data[1] / (scaleBefore ? sx : sy);
+ const x = data[4] * (scaleBefore ? 1 : sy);
+ const y = data[5] * (scaleBefore ? 1 : sx);
+ const denom = (oneOverCos ** 2 + sin ** 2) * (scaleBefore ? 1 : sx * sy);
+ rotate.push(
+ (oneOverCos * x - sin * y) / denom,
+ (oneOverCos * y + sin * x) / denom,
+ );
}
// Too many transformations, return original matrix if it isn't just a scale/translate
@@ -237,11 +267,12 @@ exports.matrixToTransform = (transform, params) => {
return [transform];
}
- if ((scaleBefore && (sx != 1 || sy != 1)) || !transforms.length)
+ if ((scaleBefore && (sx != 1 || sy != 1)) || !transforms.length) {
transforms.push({
name: 'scale',
data: sx == sy ? [sx] : [sx, sy],
});
+ }
return transforms;
};
@@ -249,7 +280,7 @@ exports.matrixToTransform = (transform, params) => {
/**
* Convert transform to the matrix data.
*
- * @type {(transform: TransformItem) => Array<number> }
+ * @type {(transform: TransformItem) => number[] }
*/
const transformToMatrix = (transform) => {
if (transform.name === 'matrix') {
@@ -302,9 +333,9 @@ const transformToMatrix = (transform) => {
*
* @type {(
* cursor: [x: number, y: number],
- * arc: Array<number>,
- * transform: Array<number>
- * ) => Array<number>}
+ * arc: number[],
+ * transform: number[]
+ * ) => number[]}
*/
exports.transformArc = (cursor, arc, transform) => {
const x = arc[5] - cursor[0];
@@ -365,7 +396,7 @@ exports.transformArc = (cursor, arc, transform) => {
/**
* Multiply transformation matrices.
*
- * @type {(a: Array<number>, b: Array<number>) => Array<number>}
+ * @type {(a: number[], b: number[]) => number[]}
*/
const multiplyTransformMatrices = (a, b) => {
return [