1 | 'use strict';
|
2 |
|
3 | exports.type = 'perItem';
|
4 |
|
5 | exports.active = true;
|
6 |
|
7 | exports.description = 'collapses multiple transformations and optimizes it';
|
8 |
|
9 | exports.params = {
|
10 | convertToShorts: true,
|
11 |
|
12 | floatPrecision: 3,
|
13 | transformPrecision: 5,
|
14 | matrixToTransform: true,
|
15 | shortTranslate: true,
|
16 | shortScale: true,
|
17 | shortRotate: true,
|
18 | removeUseless: true,
|
19 | collapseIntoOne: true,
|
20 | leadingZero: true,
|
21 | negativeExtraSpace: false
|
22 | };
|
23 |
|
24 | var cleanupOutData = require('../lib/svgo/tools').cleanupOutData,
|
25 | transform2js = require('./_transforms.js').transform2js,
|
26 | transformsMultiply = require('./_transforms.js').transformsMultiply,
|
27 | matrixToTransform = require('./_transforms.js').matrixToTransform,
|
28 | degRound,
|
29 | floatRound,
|
30 | transformRound;
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 | exports.fn = function(item, params) {
|
47 |
|
48 | if (item.elem) {
|
49 |
|
50 |
|
51 | if (item.hasAttr('transform')) {
|
52 | convertTransform(item, 'transform', params);
|
53 | }
|
54 |
|
55 |
|
56 | if (item.hasAttr('gradientTransform')) {
|
57 | convertTransform(item, 'gradientTransform', params);
|
58 | }
|
59 |
|
60 |
|
61 | if (item.hasAttr('patternTransform')) {
|
62 | convertTransform(item, 'patternTransform', params);
|
63 | }
|
64 |
|
65 | }
|
66 |
|
67 | };
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 | function convertTransform(item, attrName, params) {
|
77 | var data = transform2js(item.attr(attrName).value);
|
78 | params = definePrecision(data, params);
|
79 |
|
80 | if (params.collapseIntoOne && data.length > 1) {
|
81 | data = [transformsMultiply(data)];
|
82 | }
|
83 |
|
84 | if (params.convertToShorts) {
|
85 | data = convertToShorts(data, params);
|
86 | } else {
|
87 | data.forEach(roundTransform);
|
88 | }
|
89 |
|
90 | if (params.removeUseless) {
|
91 | data = removeUseless(data);
|
92 | }
|
93 |
|
94 | if (data.length) {
|
95 | item.attr(attrName).value = js2transform(data, params);
|
96 | } else {
|
97 | item.removeAttr(attrName);
|
98 | }
|
99 | }
|
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 |
|
112 | function definePrecision(data, params) {
|
113 |
|
114 | var matrixData = data.reduce(getMatrixData, []),
|
115 | significantDigits = params.transformPrecision;
|
116 |
|
117 |
|
118 | params = Object.assign({}, params);
|
119 |
|
120 |
|
121 | if (matrixData.length) {
|
122 | params.transformPrecision = Math.min(params.transformPrecision,
|
123 | Math.max.apply(Math, matrixData.map(floatDigits)) || params.transformPrecision);
|
124 |
|
125 | significantDigits = Math.max.apply(Math, matrixData.map(function(n) {
|
126 | return String(n).replace(/\D+/g, '').length;
|
127 | }));
|
128 | }
|
129 |
|
130 | if (!('degPrecision' in params)) {
|
131 | params.degPrecision = Math.max(0, Math.min(params.floatPrecision, significantDigits - 2));
|
132 | }
|
133 |
|
134 | floatRound = params.floatPrecision >= 1 && params.floatPrecision < 20 ?
|
135 | smartRound.bind(this, params.floatPrecision) :
|
136 | round;
|
137 | degRound = params.degPrecision >= 1 && params.floatPrecision < 20 ?
|
138 | smartRound.bind(this, params.degPrecision) :
|
139 | round;
|
140 | transformRound = params.transformPrecision >= 1 && params.floatPrecision < 20 ?
|
141 | smartRound.bind(this, params.transformPrecision) :
|
142 | round;
|
143 |
|
144 | return params;
|
145 | }
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 | function getMatrixData(a, b) {
|
155 | return b.name == 'matrix' ? a.concat(b.data.slice(0, 4)) : a;
|
156 | }
|
157 |
|
158 |
|
159 |
|
160 |
|
161 | function floatDigits(n) {
|
162 | return (n = String(n)).slice(n.indexOf('.')).length - 1;
|
163 | }
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 |
|
172 | function convertToShorts(transforms, params) {
|
173 |
|
174 | for(var i = 0; i < transforms.length; i++) {
|
175 |
|
176 | var transform = transforms[i];
|
177 |
|
178 |
|
179 | if (
|
180 | params.matrixToTransform &&
|
181 | transform.name === 'matrix'
|
182 | ) {
|
183 | var decomposed = matrixToTransform(transform, params);
|
184 | if (decomposed != transform &&
|
185 | js2transform(decomposed, params).length <= js2transform([transform], params).length) {
|
186 |
|
187 | transforms.splice.apply(transforms, [i, 1].concat(decomposed));
|
188 | }
|
189 | transform = transforms[i];
|
190 | }
|
191 |
|
192 |
|
193 |
|
194 | roundTransform(transform);
|
195 |
|
196 |
|
197 |
|
198 | if (
|
199 | params.shortTranslate &&
|
200 | transform.name === 'translate' &&
|
201 | transform.data.length === 2 &&
|
202 | !transform.data[1]
|
203 | ) {
|
204 | transform.data.pop();
|
205 | }
|
206 |
|
207 |
|
208 |
|
209 | if (
|
210 | params.shortScale &&
|
211 | transform.name === 'scale' &&
|
212 | transform.data.length === 2 &&
|
213 | transform.data[0] === transform.data[1]
|
214 | ) {
|
215 | transform.data.pop();
|
216 | }
|
217 |
|
218 |
|
219 |
|
220 | if (
|
221 | params.shortRotate &&
|
222 | transforms[i - 2] &&
|
223 | transforms[i - 2].name === 'translate' &&
|
224 | transforms[i - 1].name === 'rotate' &&
|
225 | transforms[i].name === 'translate' &&
|
226 | transforms[i - 2].data[0] === -transforms[i].data[0] &&
|
227 | transforms[i - 2].data[1] === -transforms[i].data[1]
|
228 | ) {
|
229 | transforms.splice(i - 2, 3, {
|
230 | name: 'rotate',
|
231 | data: [
|
232 | transforms[i - 1].data[0],
|
233 | transforms[i - 2].data[0],
|
234 | transforms[i - 2].data[1]
|
235 | ]
|
236 | });
|
237 |
|
238 |
|
239 | i -= 2;
|
240 |
|
241 | transform = transforms[i];
|
242 | }
|
243 |
|
244 | }
|
245 |
|
246 | return transforms;
|
247 |
|
248 | }
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 |
|
256 | function removeUseless(transforms) {
|
257 |
|
258 | return transforms.filter(function(transform) {
|
259 |
|
260 |
|
261 | if (
|
262 | ['translate', 'rotate', 'skewX', 'skewY'].indexOf(transform.name) > -1 &&
|
263 | (transform.data.length == 1 || transform.name == 'rotate') &&
|
264 | !transform.data[0] ||
|
265 |
|
266 |
|
267 | transform.name == 'translate' &&
|
268 | !transform.data[0] &&
|
269 | !transform.data[1] ||
|
270 |
|
271 |
|
272 | transform.name == 'scale' &&
|
273 | transform.data[0] == 1 &&
|
274 | (transform.data.length < 2 || transform.data[1] == 1) ||
|
275 |
|
276 |
|
277 | transform.name == 'matrix' &&
|
278 | transform.data[0] == 1 &&
|
279 | transform.data[3] == 1 &&
|
280 | !(transform.data[1] || transform.data[2] || transform.data[4] || transform.data[5])
|
281 | ) {
|
282 | return false;
|
283 | }
|
284 |
|
285 | return true;
|
286 |
|
287 | });
|
288 |
|
289 | }
|
290 |
|
291 |
|
292 |
|
293 |
|
294 |
|
295 |
|
296 |
|
297 |
|
298 | function js2transform(transformJS, params) {
|
299 |
|
300 | var transformString = '';
|
301 |
|
302 |
|
303 | transformJS.forEach(function(transform) {
|
304 | roundTransform(transform);
|
305 | transformString += (transformString && ' ') + transform.name + '(' + cleanupOutData(transform.data, params) + ')';
|
306 | });
|
307 |
|
308 | return transformString;
|
309 |
|
310 | }
|
311 |
|
312 | function roundTransform(transform) {
|
313 | switch (transform.name) {
|
314 | case 'translate':
|
315 | transform.data = floatRound(transform.data);
|
316 | break;
|
317 | case 'rotate':
|
318 | transform.data = degRound(transform.data.slice(0, 1)).concat(floatRound(transform.data.slice(1)));
|
319 | break;
|
320 | case 'skewX':
|
321 | case 'skewY':
|
322 | transform.data = degRound(transform.data);
|
323 | break;
|
324 | case 'scale':
|
325 | transform.data = transformRound(transform.data);
|
326 | break;
|
327 | case 'matrix':
|
328 | transform.data = transformRound(transform.data.slice(0, 4)).concat(floatRound(transform.data.slice(4)));
|
329 | break;
|
330 | }
|
331 | return transform;
|
332 | }
|
333 |
|
334 |
|
335 |
|
336 |
|
337 |
|
338 |
|
339 |
|
340 | function round(data) {
|
341 | return data.map(Math.round);
|
342 | }
|
343 |
|
344 |
|
345 |
|
346 |
|
347 |
|
348 |
|
349 |
|
350 |
|
351 |
|
352 |
|
353 | function smartRound(precision, data) {
|
354 | for (var i = data.length, tolerance = +Math.pow(.1, precision).toFixed(precision); i--;) {
|
355 | if (data[i].toFixed(precision) != data[i]) {
|
356 | var rounded = +data[i].toFixed(precision - 1);
|
357 | data[i] = +Math.abs(rounded - data[i]).toFixed(precision + 1) >= tolerance ?
|
358 | +data[i].toFixed(precision) :
|
359 | rounded;
|
360 | }
|
361 | }
|
362 | return data;
|
363 | }
|