mirror of
https://github.com/svg/svgo.git
synced 2025-07-29 20:21:14 +03:00
Refactor apply transforms
- handle each command separately - handle both relative and absolute commands - moved into _applyTransforms.js to convert eventually into plugin - apply transforms before converting into relative These changes makes code independent and easy to work with.
This commit is contained in:
328
plugins/_applyTransforms.js
Normal file
328
plugins/_applyTransforms.js
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
'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.hasAttr('transform') ||
|
||||||
|
!elem.attr('transform').value ||
|
||||||
|
// styles are not considered when applying transform
|
||||||
|
// can be fixed properly with new style engine
|
||||||
|
elem.hasAttr('style') ||
|
||||||
|
elem.someAttr(
|
||||||
|
(attr) =>
|
||||||
|
referencesProps.includes(attr.name) && attr.value.includes('url(')
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matrix = transformsMultiply(transform2js(elem.attr('transform').value));
|
||||||
|
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.hasAttr('stroke-width')) hasStrokeWidth = true;
|
||||||
|
} while (
|
||||||
|
!idElem.hasAttr('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.hasAttr('vector-effect') ||
|
||||||
|
elem.attr('vector-effect').value !== 'non-scaling-stroke'
|
||||||
|
) {
|
||||||
|
if (elem.hasAttr('stroke-width')) {
|
||||||
|
elem.attrs['stroke-width'].value = elem.attrs['stroke-width'].value
|
||||||
|
.trim()
|
||||||
|
.replace(regNumericValues, (num) => removeLeadingZero(num * scale));
|
||||||
|
} else {
|
||||||
|
elem.addAttr({
|
||||||
|
name: 'stroke-width',
|
||||||
|
value: strokeWidth.replace(regNumericValues, (num) =>
|
||||||
|
removeLeadingZero(num * scale)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elem.hasAttr('stroke-dashoffset')) {
|
||||||
|
elem.attrs['stroke-dashoffset'].value = elem.attrs[
|
||||||
|
'stroke-dashoffset'
|
||||||
|
].value
|
||||||
|
.trim()
|
||||||
|
.replace(regNumericValues, (num) => removeLeadingZero(num * scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elem.hasAttr('stroke-dasharray')) {
|
||||||
|
elem.attrs['stroke-dasharray'].value = elem.attrs[
|
||||||
|
'stroke-dasharray'
|
||||||
|
].value
|
||||||
|
.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
|
||||||
|
elem.removeAttr('transform');
|
||||||
|
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
let start = [0, 0];
|
||||||
|
let cursor = [0, 0];
|
||||||
|
|
||||||
|
for (const pathItem of pathData) {
|
||||||
|
let { instruction: command, data: 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
pathItem.instruction = command;
|
||||||
|
pathItem.data = args;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
exports.applyTransforms = applyTransforms;
|
221
plugins/_path.js
221
plugins/_path.js
@ -2,16 +2,7 @@
|
|||||||
|
|
||||||
const { parsePathData, stringifyPathData } = require('../lib/path.js');
|
const { parsePathData, stringifyPathData } = require('../lib/path.js');
|
||||||
|
|
||||||
var regNumericValues = /[-+]?(\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g,
|
var prevCtrlPoint;
|
||||||
transform2js = require('./_transforms').transform2js,
|
|
||||||
transformsMultiply = require('./_transforms').transformsMultiply,
|
|
||||||
transformArc = require('./_transforms').transformArc,
|
|
||||||
collections = require('./_collections.js'),
|
|
||||||
referencesProps = collections.referencesProps,
|
|
||||||
defaultStrokeWidth =
|
|
||||||
collections.attrsGroupsDefaults.presentation['stroke-width'],
|
|
||||||
removeLeadingZero = require('../lib/svgo/tools').removeLeadingZero,
|
|
||||||
prevCtrlPoint;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert path string to JS representation.
|
* Convert path string to JS representation.
|
||||||
@ -95,216 +86,6 @@ var relative2absolute = (exports.relative2absolute = function (data) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
exports.applyTransforms = function (elem, path, 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.hasAttr('transform') ||
|
|
||||||
!elem.attr('transform').value ||
|
|
||||||
// styles are not considered when applying transform
|
|
||||||
// can be fixed properly with new style engine
|
|
||||||
elem.hasAttr('style') ||
|
|
||||||
elem.someAttr(
|
|
||||||
(attr) =>
|
|
||||||
referencesProps.includes(attr.name) && attr.value.includes('url(')
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
var matrix = transformsMultiply(transform2js(elem.attr('transform').value)),
|
|
||||||
stroke = elem.computedAttr('stroke'),
|
|
||||||
id = elem.computedAttr('id'),
|
|
||||||
transformPrecision = params.transformPrecision,
|
|
||||||
scale;
|
|
||||||
|
|
||||||
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 path;
|
|
||||||
|
|
||||||
// "stroke-width" should be inside the part with ID, otherwise it can be overrided in <use>
|
|
||||||
if (id) {
|
|
||||||
var idElem = elem,
|
|
||||||
hasStrokeWidth = false;
|
|
||||||
|
|
||||||
do {
|
|
||||||
if (idElem.hasAttr('stroke-width')) hasStrokeWidth = true;
|
|
||||||
} while (
|
|
||||||
!idElem.hasAttr('id', id) &&
|
|
||||||
!hasStrokeWidth &&
|
|
||||||
(idElem = idElem.parentNode)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!hasStrokeWidth) return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
scale = +Math.sqrt(
|
|
||||||
matrix.data[0] * matrix.data[0] + matrix.data[1] * matrix.data[1]
|
|
||||||
).toFixed(transformPrecision);
|
|
||||||
|
|
||||||
if (scale !== 1) {
|
|
||||||
var strokeWidth = elem.computedAttr('stroke-width') || defaultStrokeWidth;
|
|
||||||
|
|
||||||
if (
|
|
||||||
!elem.hasAttr('vector-effect') ||
|
|
||||||
elem.attr('vector-effect').value !== 'non-scaling-stroke'
|
|
||||||
) {
|
|
||||||
if (elem.hasAttr('stroke-width')) {
|
|
||||||
elem.attrs['stroke-width'].value = elem.attrs['stroke-width'].value
|
|
||||||
.trim()
|
|
||||||
.replace(regNumericValues, function (num) {
|
|
||||||
return removeLeadingZero(num * scale);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
elem.addAttr({
|
|
||||||
name: 'stroke-width',
|
|
||||||
value: strokeWidth.replace(regNumericValues, function (num) {
|
|
||||||
return removeLeadingZero(num * scale);
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (elem.hasAttr('stroke-dashoffset')) {
|
|
||||||
elem.attrs['stroke-dashoffset'].value = elem.attrs[
|
|
||||||
'stroke-dashoffset'
|
|
||||||
].value
|
|
||||||
.trim()
|
|
||||||
.replace(regNumericValues, (num) => removeLeadingZero(num * scale));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (elem.hasAttr('stroke-dasharray')) {
|
|
||||||
elem.attrs['stroke-dasharray'].value = elem.attrs[
|
|
||||||
'stroke-dasharray'
|
|
||||||
].value
|
|
||||||
.trim()
|
|
||||||
.replace(regNumericValues, (num) => removeLeadingZero(num * scale));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (id) {
|
|
||||||
// Stroke and stroke-width can be redefined with <use>
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
let lastMovetoCoords = [0, 0];
|
|
||||||
|
|
||||||
path.forEach(function (pathItem) {
|
|
||||||
// h -> l
|
|
||||||
if (pathItem.instruction === 'h') {
|
|
||||||
pathItem.instruction = 'l';
|
|
||||||
pathItem.data[1] = 0;
|
|
||||||
|
|
||||||
// v -> l
|
|
||||||
} else if (pathItem.instruction === 'v') {
|
|
||||||
pathItem.instruction = 'l';
|
|
||||||
pathItem.data[1] = pathItem.data[0];
|
|
||||||
pathItem.data[0] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there is a translate() transform
|
|
||||||
if (pathItem.instruction === 'M') {
|
|
||||||
// then apply it only to the first absoluted M
|
|
||||||
const newPoint = transformPoint(
|
|
||||||
matrix.data,
|
|
||||||
pathItem.data[0],
|
|
||||||
pathItem.data[1]
|
|
||||||
);
|
|
||||||
pathItem.data[0] = newPoint[0];
|
|
||||||
pathItem.data[1] = newPoint[1];
|
|
||||||
pathItem.coords[0] = newPoint[0];
|
|
||||||
pathItem.coords[1] = newPoint[1];
|
|
||||||
lastMovetoCoords[0] = pathItem.coords[0];
|
|
||||||
lastMovetoCoords[1] = pathItem.coords[1];
|
|
||||||
// clear translate() data from transform matrix
|
|
||||||
matrix.data[4] = 0;
|
|
||||||
matrix.data[5] = 0;
|
|
||||||
} else if (pathItem.instruction === 'm') {
|
|
||||||
const newPoint = transformPoint(
|
|
||||||
matrix.data,
|
|
||||||
pathItem.data[0],
|
|
||||||
pathItem.data[1]
|
|
||||||
);
|
|
||||||
pathItem.data[0] = newPoint[0];
|
|
||||||
pathItem.data[1] = newPoint[1];
|
|
||||||
pathItem.coords[0] = pathItem.base[0] + newPoint[0];
|
|
||||||
pathItem.coords[1] = pathItem.base[1] + newPoint[1];
|
|
||||||
lastMovetoCoords[0] = pathItem.coords[0];
|
|
||||||
lastMovetoCoords[1] = pathItem.coords[1];
|
|
||||||
} else if (pathItem.instruction === 'Z' || pathItem.instruction === 'z') {
|
|
||||||
pathItem.coords[0] = lastMovetoCoords[0];
|
|
||||||
pathItem.coords[1] = lastMovetoCoords[1];
|
|
||||||
} else {
|
|
||||||
if (pathItem.instruction == 'a') {
|
|
||||||
transformArc(pathItem.data, matrix.data);
|
|
||||||
|
|
||||||
// reduce number of digits in rotation angle
|
|
||||||
if (Math.abs(pathItem.data[2]) > 80) {
|
|
||||||
var a = pathItem.data[0],
|
|
||||||
rotation = pathItem.data[2];
|
|
||||||
pathItem.data[0] = pathItem.data[1];
|
|
||||||
pathItem.data[1] = a;
|
|
||||||
pathItem.data[2] = rotation + (rotation > 0 ? -90 : 90);
|
|
||||||
}
|
|
||||||
|
|
||||||
const newPoint = transformPoint(
|
|
||||||
matrix.data,
|
|
||||||
pathItem.data[5],
|
|
||||||
pathItem.data[6]
|
|
||||||
);
|
|
||||||
pathItem.data[5] = newPoint[0];
|
|
||||||
pathItem.data[6] = newPoint[1];
|
|
||||||
} else {
|
|
||||||
for (var i = 0; i < pathItem.data.length; i += 2) {
|
|
||||||
const newPoint = transformPoint(
|
|
||||||
matrix.data,
|
|
||||||
pathItem.data[i],
|
|
||||||
pathItem.data[i + 1]
|
|
||||||
);
|
|
||||||
pathItem.data[i] = newPoint[0];
|
|
||||||
pathItem.data[i + 1] = newPoint[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pathItem.coords[0] =
|
|
||||||
pathItem.base[0] + pathItem.data[pathItem.data.length - 2];
|
|
||||||
pathItem.coords[1] =
|
|
||||||
pathItem.base[1] + pathItem.data[pathItem.data.length - 1];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// remove transform attr
|
|
||||||
elem.removeAttr('transform');
|
|
||||||
|
|
||||||
return path;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply transform 3x3 matrix to x-y point.
|
|
||||||
*
|
|
||||||
* @param {Array} matrix transform 3x3 matrix
|
|
||||||
* @param {Array} point x-y point
|
|
||||||
* @return {Array} point with new coordinates
|
|
||||||
*/
|
|
||||||
function transformPoint(matrix, x, y) {
|
|
||||||
return [
|
|
||||||
matrix[0] * x + matrix[2] * y + matrix[4],
|
|
||||||
matrix[1] * x + matrix[3] * y + matrix[5],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute Cubic Bézie bounding box.
|
* Compute Cubic Bézie bounding box.
|
||||||
*
|
*
|
||||||
|
@ -261,19 +261,22 @@ function transformToMatrix(transform) {
|
|||||||
* rotate(θ)·scale(a b)·rotate(φ). This gives us new ellipse params a, b and θ.
|
* rotate(θ)·scale(a b)·rotate(φ). This gives us new ellipse params a, b and θ.
|
||||||
* SVD is being done with the formulae provided by Wolffram|Alpha (svd {{m0, m2}, {m1, m3}})
|
* SVD is being done with the formulae provided by Wolffram|Alpha (svd {{m0, m2}, {m1, m3}})
|
||||||
*
|
*
|
||||||
|
* @param {Array} cursor [x, y]
|
||||||
* @param {Array} arc [a, b, rotation in deg]
|
* @param {Array} arc [a, b, rotation in deg]
|
||||||
* @param {Array} transform transformation matrix
|
* @param {Array} transform transformation matrix
|
||||||
* @return {Array} arc transformed input arc
|
* @return {Array} arc transformed input arc
|
||||||
*/
|
*/
|
||||||
exports.transformArc = function (arc, transform) {
|
exports.transformArc = function (cursor, arc, transform) {
|
||||||
|
const x = arc[5] - cursor[0];
|
||||||
|
const y = arc[6] - cursor[1];
|
||||||
var a = arc[0],
|
var a = arc[0],
|
||||||
b = arc[1],
|
b = arc[1],
|
||||||
rot = (arc[2] * Math.PI) / 180,
|
rot = (arc[2] * Math.PI) / 180,
|
||||||
cos = Math.cos(rot),
|
cos = Math.cos(rot),
|
||||||
sin = Math.sin(rot),
|
sin = Math.sin(rot),
|
||||||
h =
|
h =
|
||||||
Math.pow(arc[5] * cos + arc[6] * sin, 2) / (4 * a * a) +
|
Math.pow(x * cos + y * sin, 2) / (4 * a * a) +
|
||||||
Math.pow(arc[6] * cos - arc[5] * sin, 2) / (4 * b * b);
|
Math.pow(y * cos - x * sin, 2) / (4 * b * b);
|
||||||
if (h > 1) {
|
if (h > 1) {
|
||||||
h = Math.sqrt(h);
|
h = Math.sqrt(h);
|
||||||
a *= h;
|
a *= h;
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
const { computeStyle } = require('../lib/style.js');
|
const { computeStyle } = require('../lib/style.js');
|
||||||
const { pathElems } = require('./_collections.js');
|
const { pathElems } = require('./_collections.js');
|
||||||
const { path2js, js2path, applyTransforms } = require('./_path.js');
|
const { path2js, js2path } = require('./_path.js');
|
||||||
|
const { applyTransforms } = require('./_applyTransforms.js');
|
||||||
const { cleanupOutData } = require('../lib/svgo/tools');
|
const { cleanupOutData } = require('../lib/svgo/tools');
|
||||||
|
|
||||||
exports.type = 'perItem';
|
exports.type = 'perItem';
|
||||||
@ -82,12 +83,12 @@ exports.fn = function (item, params) {
|
|||||||
|
|
||||||
// TODO: get rid of functions returns
|
// TODO: get rid of functions returns
|
||||||
if (data.length) {
|
if (data.length) {
|
||||||
convertToRelative(data);
|
|
||||||
|
|
||||||
if (params.applyTransforms) {
|
if (params.applyTransforms) {
|
||||||
data = applyTransforms(item, data, params);
|
applyTransforms(item, data, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
convertToRelative(data);
|
||||||
|
|
||||||
data = filters(data, params, {
|
data = filters(data, params, {
|
||||||
maybeHasStrokeAndLinecap,
|
maybeHasStrokeAndLinecap,
|
||||||
hasMarkerMid,
|
hasMarkerMid,
|
||||||
@ -257,7 +258,7 @@ const convertToRelative = (pathData) => {
|
|||||||
|
|
||||||
// closepath
|
// closepath
|
||||||
if (command === 'Z' || command === 'z') {
|
if (command === 'Z' || command === 'z') {
|
||||||
// reset current cursor
|
// reset cursor
|
||||||
cursor[0] = start[0];
|
cursor[0] = start[0];
|
||||||
cursor[1] = start[1];
|
cursor[1] = start[1];
|
||||||
}
|
}
|
||||||
|
17
test/plugins/convertPathData.24.svg
Normal file
17
test/plugins/convertPathData.24.svg
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
Apply transforms
|
||||||
|
|
||||||
|
- both absolute and relative arcs should be transformed correctly
|
||||||
|
|
||||||
|
===
|
||||||
|
|
||||||
|
<svg width="1200" height="1200">
|
||||||
|
<path transform="translate(100) scale(2)" d="m200 200 h-100 a100 100 0 1 0 100 -100 z"/>
|
||||||
|
<path transform="translate(100) scale(2)" d="M400 200 H300 A100 100 0 1 0 400 100 z"/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
@@@
|
||||||
|
|
||||||
|
<svg width="1200" height="1200">
|
||||||
|
<path d="M500 400H300a200 200 0 1 0 200-200z"/>
|
||||||
|
<path d="M900 400H700a200 200 0 1 0 200-200z"/>
|
||||||
|
</svg>
|
Reference in New Issue
Block a user