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