123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- '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;
- }
- };
|