You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
336 lines
9.6 KiB
336 lines
9.6 KiB
1 month ago
|
'use strict';
|
||
|
|
||
|
// TODO implement as separate plugin
|
||
|
|
||
|
const {
|
||
|
transformsMultiply,
|
||
|
transform2js,
|
||
|
transformArc,
|
||
|
} = require('./_transforms.js');
|
||
|
const { removeLeadingZero } = require('../lib/svgo/tools.js');
|
||
|
const { referencesProps, attrsGroupsDefaults } = require('./_collections.js');
|
||
|
|
||
|
const regNumericValues = /[-+]?(\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;
|
||
|
const defaultStrokeWidth = attrsGroupsDefaults.presentation['stroke-width'];
|
||
|
|
||
|
/**
|
||
|
* Apply transformation(s) to the Path data.
|
||
|
*
|
||
|
* @param {Object} elem current element
|
||
|
* @param {Array} path input path data
|
||
|
* @param {Object} params whether to apply transforms to stroked lines and transform precision (used for stroke width)
|
||
|
* @return {Array} output path data
|
||
|
*/
|
||
|
const applyTransforms = (elem, pathData, params) => {
|
||
|
// if there are no 'stroke' attr and references to other objects such as
|
||
|
// gradiends or clip-path which are also subjects to transform.
|
||
|
if (
|
||
|
elem.attributes.transform == null ||
|
||
|
elem.attributes.transform === '' ||
|
||
|
// styles are not considered when applying transform
|
||
|
// can be fixed properly with new style engine
|
||
|
elem.attributes.style != null ||
|
||
|
Object.entries(elem.attributes).some(
|
||
|
([name, value]) =>
|
||
|
referencesProps.includes(name) && value.includes('url(')
|
||
|
)
|
||
|
) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const matrix = transformsMultiply(transform2js(elem.attributes.transform));
|
||
|
const stroke = elem.computedAttr('stroke');
|
||
|
const id = elem.computedAttr('id');
|
||
|
const transformPrecision = params.transformPrecision;
|
||
|
|
||
|
if (stroke && stroke != 'none') {
|
||
|
if (
|
||
|
!params.applyTransformsStroked ||
|
||
|
((matrix.data[0] != matrix.data[3] ||
|
||
|
matrix.data[1] != -matrix.data[2]) &&
|
||
|
(matrix.data[0] != -matrix.data[3] || matrix.data[1] != matrix.data[2]))
|
||
|
)
|
||
|
return;
|
||
|
|
||
|
// "stroke-width" should be inside the part with ID, otherwise it can be overrided in <use>
|
||
|
if (id) {
|
||
|
let idElem = elem;
|
||
|
let hasStrokeWidth = false;
|
||
|
|
||
|
do {
|
||
|
if (idElem.attributes['stroke-width']) {
|
||
|
hasStrokeWidth = true;
|
||
|
}
|
||
|
} while (
|
||
|
idElem.attributes.id !== id &&
|
||
|
!hasStrokeWidth &&
|
||
|
(idElem = idElem.parentNode)
|
||
|
);
|
||
|
|
||
|
if (!hasStrokeWidth) return;
|
||
|
}
|
||
|
|
||
|
const scale = +Math.sqrt(
|
||
|
matrix.data[0] * matrix.data[0] + matrix.data[1] * matrix.data[1]
|
||
|
).toFixed(transformPrecision);
|
||
|
|
||
|
if (scale !== 1) {
|
||
|
const strokeWidth =
|
||
|
elem.computedAttr('stroke-width') || defaultStrokeWidth;
|
||
|
|
||
|
if (
|
||
|
elem.attributes['vector-effect'] == null ||
|
||
|
elem.attributes['vector-effect'] !== 'non-scaling-stroke'
|
||
|
) {
|
||
|
if (elem.attributes['stroke-width'] != null) {
|
||
|
elem.attributes['stroke-width'] = elem.attributes['stroke-width']
|
||
|
.trim()
|
||
|
.replace(regNumericValues, (num) => removeLeadingZero(num * scale));
|
||
|
} else {
|
||
|
elem.attributes['stroke-width'] = strokeWidth.replace(
|
||
|
regNumericValues,
|
||
|
(num) => removeLeadingZero(num * scale)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if (elem.attributes['stroke-dashoffset'] != null) {
|
||
|
elem.attributes['stroke-dashoffset'] = elem.attributes[
|
||
|
'stroke-dashoffset'
|
||
|
]
|
||
|
.trim()
|
||
|
.replace(regNumericValues, (num) => removeLeadingZero(num * scale));
|
||
|
}
|
||
|
|
||
|
if (elem.attributes['stroke-dasharray'] != null) {
|
||
|
elem.attributes['stroke-dasharray'] = elem.attributes[
|
||
|
'stroke-dasharray'
|
||
|
]
|
||
|
.trim()
|
||
|
.replace(regNumericValues, (num) => removeLeadingZero(num * scale));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} else if (id) {
|
||
|
// Stroke and stroke-width can be redefined with <use>
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
applyMatrixToPathData(pathData, matrix.data);
|
||
|
|
||
|
// remove transform attr
|
||
|
delete elem.attributes.transform;
|
||
|
|
||
|
return;
|
||
|
};
|
||
|
exports.applyTransforms = applyTransforms;
|
||
|
|
||
|
const transformAbsolutePoint = (matrix, x, y) => {
|
||
|
const newX = matrix[0] * x + matrix[2] * y + matrix[4];
|
||
|
const newY = matrix[1] * x + matrix[3] * y + matrix[5];
|
||
|
return [newX, newY];
|
||
|
};
|
||
|
|
||
|
const transformRelativePoint = (matrix, x, y) => {
|
||
|
const newX = matrix[0] * x + matrix[2] * y;
|
||
|
const newY = matrix[1] * x + matrix[3] * y;
|
||
|
return [newX, newY];
|
||
|
};
|
||
|
|
||
|
const applyMatrixToPathData = (pathData, matrix) => {
|
||
|
const start = [0, 0];
|
||
|
const cursor = [0, 0];
|
||
|
|
||
|
for (const pathItem of pathData) {
|
||
|
let { command, args } = pathItem;
|
||
|
|
||
|
// moveto (x y)
|
||
|
if (command === 'M') {
|
||
|
cursor[0] = args[0];
|
||
|
cursor[1] = args[1];
|
||
|
start[0] = cursor[0];
|
||
|
start[1] = cursor[1];
|
||
|
const [x, y] = transformAbsolutePoint(matrix, args[0], args[1]);
|
||
|
args[0] = x;
|
||
|
args[1] = y;
|
||
|
}
|
||
|
if (command === 'm') {
|
||
|
cursor[0] += args[0];
|
||
|
cursor[1] += args[1];
|
||
|
start[0] = cursor[0];
|
||
|
start[1] = cursor[1];
|
||
|
const [x, y] = transformRelativePoint(matrix, args[0], args[1]);
|
||
|
args[0] = x;
|
||
|
args[1] = y;
|
||
|
}
|
||
|
|
||
|
// horizontal lineto (x)
|
||
|
// convert to lineto to handle two-dimentional transforms
|
||
|
if (command === 'H') {
|
||
|
command = 'L';
|
||
|
args = [args[0], cursor[1]];
|
||
|
}
|
||
|
if (command === 'h') {
|
||
|
command = 'l';
|
||
|
args = [args[0], 0];
|
||
|
}
|
||
|
|
||
|
// vertical lineto (y)
|
||
|
// convert to lineto to handle two-dimentional transforms
|
||
|
if (command === 'V') {
|
||
|
command = 'L';
|
||
|
args = [cursor[0], args[0]];
|
||
|
}
|
||
|
if (command === 'v') {
|
||
|
command = 'l';
|
||
|
args = [0, args[0]];
|
||
|
}
|
||
|
|
||
|
// lineto (x y)
|
||
|
if (command === 'L') {
|
||
|
cursor[0] = args[0];
|
||
|
cursor[1] = args[1];
|
||
|
const [x, y] = transformAbsolutePoint(matrix, args[0], args[1]);
|
||
|
args[0] = x;
|
||
|
args[1] = y;
|
||
|
}
|
||
|
if (command === 'l') {
|
||
|
cursor[0] += args[0];
|
||
|
cursor[1] += args[1];
|
||
|
const [x, y] = transformRelativePoint(matrix, args[0], args[1]);
|
||
|
args[0] = x;
|
||
|
args[1] = y;
|
||
|
}
|
||
|
|
||
|
// curveto (x1 y1 x2 y2 x y)
|
||
|
if (command === 'C') {
|
||
|
cursor[0] = args[4];
|
||
|
cursor[1] = args[5];
|
||
|
const [x1, y1] = transformAbsolutePoint(matrix, args[0], args[1]);
|
||
|
const [x2, y2] = transformAbsolutePoint(matrix, args[2], args[3]);
|
||
|
const [x, y] = transformAbsolutePoint(matrix, args[4], args[5]);
|
||
|
args[0] = x1;
|
||
|
args[1] = y1;
|
||
|
args[2] = x2;
|
||
|
args[3] = y2;
|
||
|
args[4] = x;
|
||
|
args[5] = y;
|
||
|
}
|
||
|
if (command === 'c') {
|
||
|
cursor[0] += args[4];
|
||
|
cursor[1] += args[5];
|
||
|
const [x1, y1] = transformRelativePoint(matrix, args[0], args[1]);
|
||
|
const [x2, y2] = transformRelativePoint(matrix, args[2], args[3]);
|
||
|
const [x, y] = transformRelativePoint(matrix, args[4], args[5]);
|
||
|
args[0] = x1;
|
||
|
args[1] = y1;
|
||
|
args[2] = x2;
|
||
|
args[3] = y2;
|
||
|
args[4] = x;
|
||
|
args[5] = y;
|
||
|
}
|
||
|
|
||
|
// smooth curveto (x2 y2 x y)
|
||
|
if (command === 'S') {
|
||
|
cursor[0] = args[2];
|
||
|
cursor[1] = args[3];
|
||
|
const [x2, y2] = transformAbsolutePoint(matrix, args[0], args[1]);
|
||
|
const [x, y] = transformAbsolutePoint(matrix, args[2], args[3]);
|
||
|
args[0] = x2;
|
||
|
args[1] = y2;
|
||
|
args[2] = x;
|
||
|
args[3] = y;
|
||
|
}
|
||
|
if (command === 's') {
|
||
|
cursor[0] += args[2];
|
||
|
cursor[1] += args[3];
|
||
|
const [x2, y2] = transformRelativePoint(matrix, args[0], args[1]);
|
||
|
const [x, y] = transformRelativePoint(matrix, args[2], args[3]);
|
||
|
args[0] = x2;
|
||
|
args[1] = y2;
|
||
|
args[2] = x;
|
||
|
args[3] = y;
|
||
|
}
|
||
|
|
||
|
// quadratic Bézier curveto (x1 y1 x y)
|
||
|
if (command === 'Q') {
|
||
|
cursor[0] = args[2];
|
||
|
cursor[1] = args[3];
|
||
|
const [x1, y1] = transformAbsolutePoint(matrix, args[0], args[1]);
|
||
|
const [x, y] = transformAbsolutePoint(matrix, args[2], args[3]);
|
||
|
args[0] = x1;
|
||
|
args[1] = y1;
|
||
|
args[2] = x;
|
||
|
args[3] = y;
|
||
|
}
|
||
|
if (command === 'q') {
|
||
|
cursor[0] += args[2];
|
||
|
cursor[1] += args[3];
|
||
|
const [x1, y1] = transformRelativePoint(matrix, args[0], args[1]);
|
||
|
const [x, y] = transformRelativePoint(matrix, args[2], args[3]);
|
||
|
args[0] = x1;
|
||
|
args[1] = y1;
|
||
|
args[2] = x;
|
||
|
args[3] = y;
|
||
|
}
|
||
|
|
||
|
// smooth quadratic Bézier curveto (x y)
|
||
|
if (command === 'T') {
|
||
|
cursor[0] = args[0];
|
||
|
cursor[1] = args[1];
|
||
|
const [x, y] = transformAbsolutePoint(matrix, args[0], args[1]);
|
||
|
args[0] = x;
|
||
|
args[1] = y;
|
||
|
}
|
||
|
if (command === 't') {
|
||
|
cursor[0] += args[0];
|
||
|
cursor[1] += args[1];
|
||
|
const [x, y] = transformRelativePoint(matrix, args[0], args[1]);
|
||
|
args[0] = x;
|
||
|
args[1] = y;
|
||
|
}
|
||
|
|
||
|
// elliptical arc (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
|
||
|
if (command === 'A') {
|
||
|
transformArc(cursor, args, matrix);
|
||
|
cursor[0] = args[5];
|
||
|
cursor[1] = args[6];
|
||
|
// reduce number of digits in rotation angle
|
||
|
if (Math.abs(args[2]) > 80) {
|
||
|
const a = args[0];
|
||
|
const rotation = args[2];
|
||
|
args[0] = args[1];
|
||
|
args[1] = a;
|
||
|
args[2] = rotation + (rotation > 0 ? -90 : 90);
|
||
|
}
|
||
|
const [x, y] = transformAbsolutePoint(matrix, args[5], args[6]);
|
||
|
args[5] = x;
|
||
|
args[6] = y;
|
||
|
}
|
||
|
if (command === 'a') {
|
||
|
transformArc([0, 0], args, matrix);
|
||
|
cursor[0] += args[5];
|
||
|
cursor[1] += args[6];
|
||
|
// reduce number of digits in rotation angle
|
||
|
if (Math.abs(args[2]) > 80) {
|
||
|
const a = args[0];
|
||
|
const rotation = args[2];
|
||
|
args[0] = args[1];
|
||
|
args[1] = a;
|
||
|
args[2] = rotation + (rotation > 0 ? -90 : 90);
|
||
|
}
|
||
|
const [x, y] = transformRelativePoint(matrix, args[5], args[6]);
|
||
|
args[5] = x;
|
||
|
args[6] = y;
|
||
|
}
|
||
|
|
||
|
// closepath
|
||
|
if (command === 'z' || command === 'Z') {
|
||
|
cursor[0] = start[0];
|
||
|
cursor[1] = start[1];
|
||
|
}
|
||
|
|
||
|
pathItem.command = command;
|
||
|
pathItem.args = args;
|
||
|
}
|
||
|
};
|