[6a3a178] | 1 | 'use strict';
|
---|
| 2 |
|
---|
| 3 | // TODO implement as separate plugin
|
---|
| 4 |
|
---|
| 5 | const {
|
---|
| 6 | transformsMultiply,
|
---|
| 7 | transform2js,
|
---|
| 8 | transformArc,
|
---|
| 9 | } = require('./_transforms.js');
|
---|
| 10 | const { removeLeadingZero } = require('../lib/svgo/tools.js');
|
---|
| 11 | const { referencesProps, attrsGroupsDefaults } = require('./_collections.js');
|
---|
| 12 |
|
---|
| 13 | const regNumericValues = /[-+]?(\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;
|
---|
| 14 | const defaultStrokeWidth = attrsGroupsDefaults.presentation['stroke-width'];
|
---|
| 15 |
|
---|
| 16 | /**
|
---|
| 17 | * Apply transformation(s) to the Path data.
|
---|
| 18 | *
|
---|
| 19 | * @param {Object} elem current element
|
---|
| 20 | * @param {Array} path input path data
|
---|
| 21 | * @param {Object} params whether to apply transforms to stroked lines and transform precision (used for stroke width)
|
---|
| 22 | * @return {Array} output path data
|
---|
| 23 | */
|
---|
| 24 | const applyTransforms = (elem, pathData, params) => {
|
---|
| 25 | // if there are no 'stroke' attr and references to other objects such as
|
---|
| 26 | // gradiends or clip-path which are also subjects to transform.
|
---|
| 27 | if (
|
---|
| 28 | elem.attributes.transform == null ||
|
---|
| 29 | elem.attributes.transform === '' ||
|
---|
| 30 | // styles are not considered when applying transform
|
---|
| 31 | // can be fixed properly with new style engine
|
---|
| 32 | elem.attributes.style != null ||
|
---|
| 33 | Object.entries(elem.attributes).some(
|
---|
| 34 | ([name, value]) =>
|
---|
| 35 | referencesProps.includes(name) && value.includes('url(')
|
---|
| 36 | )
|
---|
| 37 | ) {
|
---|
| 38 | return;
|
---|
| 39 | }
|
---|
| 40 |
|
---|
| 41 | const matrix = transformsMultiply(transform2js(elem.attributes.transform));
|
---|
| 42 | const stroke = elem.computedAttr('stroke');
|
---|
| 43 | const id = elem.computedAttr('id');
|
---|
| 44 | const transformPrecision = params.transformPrecision;
|
---|
| 45 |
|
---|
| 46 | if (stroke && stroke != 'none') {
|
---|
| 47 | if (
|
---|
| 48 | !params.applyTransformsStroked ||
|
---|
| 49 | ((matrix.data[0] != matrix.data[3] ||
|
---|
| 50 | matrix.data[1] != -matrix.data[2]) &&
|
---|
| 51 | (matrix.data[0] != -matrix.data[3] || matrix.data[1] != matrix.data[2]))
|
---|
| 52 | )
|
---|
| 53 | return;
|
---|
| 54 |
|
---|
| 55 | // "stroke-width" should be inside the part with ID, otherwise it can be overrided in <use>
|
---|
| 56 | if (id) {
|
---|
| 57 | let idElem = elem;
|
---|
| 58 | let hasStrokeWidth = false;
|
---|
| 59 |
|
---|
| 60 | do {
|
---|
| 61 | if (idElem.attributes['stroke-width']) {
|
---|
| 62 | hasStrokeWidth = true;
|
---|
| 63 | }
|
---|
| 64 | } while (
|
---|
| 65 | idElem.attributes.id !== id &&
|
---|
| 66 | !hasStrokeWidth &&
|
---|
| 67 | (idElem = idElem.parentNode)
|
---|
| 68 | );
|
---|
| 69 |
|
---|
| 70 | if (!hasStrokeWidth) return;
|
---|
| 71 | }
|
---|
| 72 |
|
---|
| 73 | const scale = +Math.sqrt(
|
---|
| 74 | matrix.data[0] * matrix.data[0] + matrix.data[1] * matrix.data[1]
|
---|
| 75 | ).toFixed(transformPrecision);
|
---|
| 76 |
|
---|
| 77 | if (scale !== 1) {
|
---|
| 78 | const strokeWidth =
|
---|
| 79 | elem.computedAttr('stroke-width') || defaultStrokeWidth;
|
---|
| 80 |
|
---|
| 81 | if (
|
---|
| 82 | elem.attributes['vector-effect'] == null ||
|
---|
| 83 | elem.attributes['vector-effect'] !== 'non-scaling-stroke'
|
---|
| 84 | ) {
|
---|
| 85 | if (elem.attributes['stroke-width'] != null) {
|
---|
| 86 | elem.attributes['stroke-width'] = elem.attributes['stroke-width']
|
---|
| 87 | .trim()
|
---|
| 88 | .replace(regNumericValues, (num) => removeLeadingZero(num * scale));
|
---|
| 89 | } else {
|
---|
| 90 | elem.attributes['stroke-width'] = strokeWidth.replace(
|
---|
| 91 | regNumericValues,
|
---|
| 92 | (num) => removeLeadingZero(num * scale)
|
---|
| 93 | );
|
---|
| 94 | }
|
---|
| 95 |
|
---|
| 96 | if (elem.attributes['stroke-dashoffset'] != null) {
|
---|
| 97 | elem.attributes['stroke-dashoffset'] = elem.attributes[
|
---|
| 98 | 'stroke-dashoffset'
|
---|
| 99 | ]
|
---|
| 100 | .trim()
|
---|
| 101 | .replace(regNumericValues, (num) => removeLeadingZero(num * scale));
|
---|
| 102 | }
|
---|
| 103 |
|
---|
| 104 | if (elem.attributes['stroke-dasharray'] != null) {
|
---|
| 105 | elem.attributes['stroke-dasharray'] = elem.attributes[
|
---|
| 106 | 'stroke-dasharray'
|
---|
| 107 | ]
|
---|
| 108 | .trim()
|
---|
| 109 | .replace(regNumericValues, (num) => removeLeadingZero(num * scale));
|
---|
| 110 | }
|
---|
| 111 | }
|
---|
| 112 | }
|
---|
| 113 | } else if (id) {
|
---|
| 114 | // Stroke and stroke-width can be redefined with <use>
|
---|
| 115 | return;
|
---|
| 116 | }
|
---|
| 117 |
|
---|
| 118 | applyMatrixToPathData(pathData, matrix.data);
|
---|
| 119 |
|
---|
| 120 | // remove transform attr
|
---|
| 121 | delete elem.attributes.transform;
|
---|
| 122 |
|
---|
| 123 | return;
|
---|
| 124 | };
|
---|
| 125 | exports.applyTransforms = applyTransforms;
|
---|
| 126 |
|
---|
| 127 | const transformAbsolutePoint = (matrix, x, y) => {
|
---|
| 128 | const newX = matrix[0] * x + matrix[2] * y + matrix[4];
|
---|
| 129 | const newY = matrix[1] * x + matrix[3] * y + matrix[5];
|
---|
| 130 | return [newX, newY];
|
---|
| 131 | };
|
---|
| 132 |
|
---|
| 133 | const transformRelativePoint = (matrix, x, y) => {
|
---|
| 134 | const newX = matrix[0] * x + matrix[2] * y;
|
---|
| 135 | const newY = matrix[1] * x + matrix[3] * y;
|
---|
| 136 | return [newX, newY];
|
---|
| 137 | };
|
---|
| 138 |
|
---|
| 139 | const applyMatrixToPathData = (pathData, matrix) => {
|
---|
| 140 | const start = [0, 0];
|
---|
| 141 | const cursor = [0, 0];
|
---|
| 142 |
|
---|
| 143 | for (const pathItem of pathData) {
|
---|
| 144 | let { command, args } = pathItem;
|
---|
| 145 |
|
---|
| 146 | // moveto (x y)
|
---|
| 147 | if (command === 'M') {
|
---|
| 148 | cursor[0] = args[0];
|
---|
| 149 | cursor[1] = args[1];
|
---|
| 150 | start[0] = cursor[0];
|
---|
| 151 | start[1] = cursor[1];
|
---|
| 152 | const [x, y] = transformAbsolutePoint(matrix, args[0], args[1]);
|
---|
| 153 | args[0] = x;
|
---|
| 154 | args[1] = y;
|
---|
| 155 | }
|
---|
| 156 | if (command === 'm') {
|
---|
| 157 | cursor[0] += args[0];
|
---|
| 158 | cursor[1] += args[1];
|
---|
| 159 | start[0] = cursor[0];
|
---|
| 160 | start[1] = cursor[1];
|
---|
| 161 | const [x, y] = transformRelativePoint(matrix, args[0], args[1]);
|
---|
| 162 | args[0] = x;
|
---|
| 163 | args[1] = y;
|
---|
| 164 | }
|
---|
| 165 |
|
---|
| 166 | // horizontal lineto (x)
|
---|
| 167 | // convert to lineto to handle two-dimentional transforms
|
---|
| 168 | if (command === 'H') {
|
---|
| 169 | command = 'L';
|
---|
| 170 | args = [args[0], cursor[1]];
|
---|
| 171 | }
|
---|
| 172 | if (command === 'h') {
|
---|
| 173 | command = 'l';
|
---|
| 174 | args = [args[0], 0];
|
---|
| 175 | }
|
---|
| 176 |
|
---|
| 177 | // vertical lineto (y)
|
---|
| 178 | // convert to lineto to handle two-dimentional transforms
|
---|
| 179 | if (command === 'V') {
|
---|
| 180 | command = 'L';
|
---|
| 181 | args = [cursor[0], args[0]];
|
---|
| 182 | }
|
---|
| 183 | if (command === 'v') {
|
---|
| 184 | command = 'l';
|
---|
| 185 | args = [0, args[0]];
|
---|
| 186 | }
|
---|
| 187 |
|
---|
| 188 | // lineto (x y)
|
---|
| 189 | if (command === 'L') {
|
---|
| 190 | cursor[0] = args[0];
|
---|
| 191 | cursor[1] = args[1];
|
---|
| 192 | const [x, y] = transformAbsolutePoint(matrix, args[0], args[1]);
|
---|
| 193 | args[0] = x;
|
---|
| 194 | args[1] = y;
|
---|
| 195 | }
|
---|
| 196 | if (command === 'l') {
|
---|
| 197 | cursor[0] += args[0];
|
---|
| 198 | cursor[1] += args[1];
|
---|
| 199 | const [x, y] = transformRelativePoint(matrix, args[0], args[1]);
|
---|
| 200 | args[0] = x;
|
---|
| 201 | args[1] = y;
|
---|
| 202 | }
|
---|
| 203 |
|
---|
| 204 | // curveto (x1 y1 x2 y2 x y)
|
---|
| 205 | if (command === 'C') {
|
---|
| 206 | cursor[0] = args[4];
|
---|
| 207 | cursor[1] = args[5];
|
---|
| 208 | const [x1, y1] = transformAbsolutePoint(matrix, args[0], args[1]);
|
---|
| 209 | const [x2, y2] = transformAbsolutePoint(matrix, args[2], args[3]);
|
---|
| 210 | const [x, y] = transformAbsolutePoint(matrix, args[4], args[5]);
|
---|
| 211 | args[0] = x1;
|
---|
| 212 | args[1] = y1;
|
---|
| 213 | args[2] = x2;
|
---|
| 214 | args[3] = y2;
|
---|
| 215 | args[4] = x;
|
---|
| 216 | args[5] = y;
|
---|
| 217 | }
|
---|
| 218 | if (command === 'c') {
|
---|
| 219 | cursor[0] += args[4];
|
---|
| 220 | cursor[1] += args[5];
|
---|
| 221 | const [x1, y1] = transformRelativePoint(matrix, args[0], args[1]);
|
---|
| 222 | const [x2, y2] = transformRelativePoint(matrix, args[2], args[3]);
|
---|
| 223 | const [x, y] = transformRelativePoint(matrix, args[4], args[5]);
|
---|
| 224 | args[0] = x1;
|
---|
| 225 | args[1] = y1;
|
---|
| 226 | args[2] = x2;
|
---|
| 227 | args[3] = y2;
|
---|
| 228 | args[4] = x;
|
---|
| 229 | args[5] = y;
|
---|
| 230 | }
|
---|
| 231 |
|
---|
| 232 | // smooth curveto (x2 y2 x y)
|
---|
| 233 | if (command === 'S') {
|
---|
| 234 | cursor[0] = args[2];
|
---|
| 235 | cursor[1] = args[3];
|
---|
| 236 | const [x2, y2] = transformAbsolutePoint(matrix, args[0], args[1]);
|
---|
| 237 | const [x, y] = transformAbsolutePoint(matrix, args[2], args[3]);
|
---|
| 238 | args[0] = x2;
|
---|
| 239 | args[1] = y2;
|
---|
| 240 | args[2] = x;
|
---|
| 241 | args[3] = y;
|
---|
| 242 | }
|
---|
| 243 | if (command === 's') {
|
---|
| 244 | cursor[0] += args[2];
|
---|
| 245 | cursor[1] += args[3];
|
---|
| 246 | const [x2, y2] = transformRelativePoint(matrix, args[0], args[1]);
|
---|
| 247 | const [x, y] = transformRelativePoint(matrix, args[2], args[3]);
|
---|
| 248 | args[0] = x2;
|
---|
| 249 | args[1] = y2;
|
---|
| 250 | args[2] = x;
|
---|
| 251 | args[3] = y;
|
---|
| 252 | }
|
---|
| 253 |
|
---|
| 254 | // quadratic Bézier curveto (x1 y1 x y)
|
---|
| 255 | if (command === 'Q') {
|
---|
| 256 | cursor[0] = args[2];
|
---|
| 257 | cursor[1] = args[3];
|
---|
| 258 | const [x1, y1] = transformAbsolutePoint(matrix, args[0], args[1]);
|
---|
| 259 | const [x, y] = transformAbsolutePoint(matrix, args[2], args[3]);
|
---|
| 260 | args[0] = x1;
|
---|
| 261 | args[1] = y1;
|
---|
| 262 | args[2] = x;
|
---|
| 263 | args[3] = y;
|
---|
| 264 | }
|
---|
| 265 | if (command === 'q') {
|
---|
| 266 | cursor[0] += args[2];
|
---|
| 267 | cursor[1] += args[3];
|
---|
| 268 | const [x1, y1] = transformRelativePoint(matrix, args[0], args[1]);
|
---|
| 269 | const [x, y] = transformRelativePoint(matrix, args[2], args[3]);
|
---|
| 270 | args[0] = x1;
|
---|
| 271 | args[1] = y1;
|
---|
| 272 | args[2] = x;
|
---|
| 273 | args[3] = y;
|
---|
| 274 | }
|
---|
| 275 |
|
---|
| 276 | // smooth quadratic Bézier curveto (x y)
|
---|
| 277 | if (command === 'T') {
|
---|
| 278 | cursor[0] = args[0];
|
---|
| 279 | cursor[1] = args[1];
|
---|
| 280 | const [x, y] = transformAbsolutePoint(matrix, args[0], args[1]);
|
---|
| 281 | args[0] = x;
|
---|
| 282 | args[1] = y;
|
---|
| 283 | }
|
---|
| 284 | if (command === 't') {
|
---|
| 285 | cursor[0] += args[0];
|
---|
| 286 | cursor[1] += args[1];
|
---|
| 287 | const [x, y] = transformRelativePoint(matrix, args[0], args[1]);
|
---|
| 288 | args[0] = x;
|
---|
| 289 | args[1] = y;
|
---|
| 290 | }
|
---|
| 291 |
|
---|
| 292 | // elliptical arc (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
|
---|
| 293 | if (command === 'A') {
|
---|
| 294 | transformArc(cursor, args, matrix);
|
---|
| 295 | cursor[0] = args[5];
|
---|
| 296 | cursor[1] = args[6];
|
---|
| 297 | // reduce number of digits in rotation angle
|
---|
| 298 | if (Math.abs(args[2]) > 80) {
|
---|
| 299 | const a = args[0];
|
---|
| 300 | const rotation = args[2];
|
---|
| 301 | args[0] = args[1];
|
---|
| 302 | args[1] = a;
|
---|
| 303 | args[2] = rotation + (rotation > 0 ? -90 : 90);
|
---|
| 304 | }
|
---|
| 305 | const [x, y] = transformAbsolutePoint(matrix, args[5], args[6]);
|
---|
| 306 | args[5] = x;
|
---|
| 307 | args[6] = y;
|
---|
| 308 | }
|
---|
| 309 | if (command === 'a') {
|
---|
| 310 | transformArc([0, 0], args, matrix);
|
---|
| 311 | cursor[0] += args[5];
|
---|
| 312 | cursor[1] += args[6];
|
---|
| 313 | // reduce number of digits in rotation angle
|
---|
| 314 | if (Math.abs(args[2]) > 80) {
|
---|
| 315 | const a = args[0];
|
---|
| 316 | const rotation = args[2];
|
---|
| 317 | args[0] = args[1];
|
---|
| 318 | args[1] = a;
|
---|
| 319 | args[2] = rotation + (rotation > 0 ? -90 : 90);
|
---|
| 320 | }
|
---|
| 321 | const [x, y] = transformRelativePoint(matrix, args[5], args[6]);
|
---|
| 322 | args[5] = x;
|
---|
| 323 | args[6] = y;
|
---|
| 324 | }
|
---|
| 325 |
|
---|
| 326 | // closepath
|
---|
| 327 | if (command === 'z' || command === 'Z') {
|
---|
| 328 | cursor[0] = start[0];
|
---|
| 329 | cursor[1] = start[1];
|
---|
| 330 | }
|
---|
| 331 |
|
---|
| 332 | pathItem.command = command;
|
---|
| 333 | pathItem.args = args;
|
---|
| 334 | }
|
---|
| 335 | };
|
---|