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