svgo-v2 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +294 -0
  3. package/bin/svgo +10 -0
  4. package/dist/svgo.browser.js +1 -0
  5. package/lib/css-tools.js +239 -0
  6. package/lib/parser.js +259 -0
  7. package/lib/path.js +347 -0
  8. package/lib/stringifier.js +326 -0
  9. package/lib/style.js +283 -0
  10. package/lib/svgo/coa.js +517 -0
  11. package/lib/svgo/config.js +138 -0
  12. package/lib/svgo/css-class-list.js +72 -0
  13. package/lib/svgo/css-select-adapter.d.ts +2 -0
  14. package/lib/svgo/css-select-adapter.js +120 -0
  15. package/lib/svgo/css-style-declaration.js +232 -0
  16. package/lib/svgo/jsAPI.d.ts +2 -0
  17. package/lib/svgo/jsAPI.js +443 -0
  18. package/lib/svgo/plugins.js +109 -0
  19. package/lib/svgo/tools.js +137 -0
  20. package/lib/svgo-node.js +106 -0
  21. package/lib/svgo.js +83 -0
  22. package/lib/types.ts +172 -0
  23. package/lib/xast.js +102 -0
  24. package/package.json +130 -0
  25. package/plugins/_applyTransforms.js +335 -0
  26. package/plugins/_collections.js +2168 -0
  27. package/plugins/_path.js +816 -0
  28. package/plugins/_transforms.js +379 -0
  29. package/plugins/addAttributesToSVGElement.js +87 -0
  30. package/plugins/addClassesToSVGElement.js +87 -0
  31. package/plugins/cleanupAttrs.js +55 -0
  32. package/plugins/cleanupEnableBackground.js +75 -0
  33. package/plugins/cleanupIDs.js +297 -0
  34. package/plugins/cleanupListOfValues.js +154 -0
  35. package/plugins/cleanupNumericValues.js +113 -0
  36. package/plugins/collapseGroups.js +135 -0
  37. package/plugins/convertColors.js +152 -0
  38. package/plugins/convertEllipseToCircle.js +39 -0
  39. package/plugins/convertPathData.js +1023 -0
  40. package/plugins/convertShapeToPath.js +175 -0
  41. package/plugins/convertStyleToAttrs.js +132 -0
  42. package/plugins/convertTransform.js +432 -0
  43. package/plugins/inlineStyles.js +379 -0
  44. package/plugins/mergePaths.js +104 -0
  45. package/plugins/mergeStyles.js +93 -0
  46. package/plugins/minifyStyles.js +148 -0
  47. package/plugins/moveElemsAttrsToGroup.js +130 -0
  48. package/plugins/moveGroupAttrsToElems.js +62 -0
  49. package/plugins/plugins.js +56 -0
  50. package/plugins/prefixIds.js +241 -0
  51. package/plugins/preset-default.js +80 -0
  52. package/plugins/removeAttributesBySelector.js +99 -0
  53. package/plugins/removeAttrs.js +159 -0
  54. package/plugins/removeComments.js +31 -0
  55. package/plugins/removeDesc.js +41 -0
  56. package/plugins/removeDimensions.js +43 -0
  57. package/plugins/removeDoctype.js +42 -0
  58. package/plugins/removeEditorsNSData.js +68 -0
  59. package/plugins/removeElementsByAttr.js +78 -0
  60. package/plugins/removeEmptyAttrs.js +33 -0
  61. package/plugins/removeEmptyContainers.js +58 -0
  62. package/plugins/removeEmptyText.js +57 -0
  63. package/plugins/removeHiddenElems.js +318 -0
  64. package/plugins/removeMetadata.js +29 -0
  65. package/plugins/removeNonInheritableGroupAttrs.js +38 -0
  66. package/plugins/removeOffCanvasPaths.js +138 -0
  67. package/plugins/removeRasterImages.js +33 -0
  68. package/plugins/removeScriptElement.js +29 -0
  69. package/plugins/removeStyleElement.js +29 -0
  70. package/plugins/removeTitle.js +29 -0
  71. package/plugins/removeUnknownsAndDefaults.js +218 -0
  72. package/plugins/removeUnusedNS.js +61 -0
  73. package/plugins/removeUselessDefs.js +65 -0
  74. package/plugins/removeUselessStrokeAndFill.js +144 -0
  75. package/plugins/removeViewBox.js +51 -0
  76. package/plugins/removeXMLNS.js +30 -0
  77. package/plugins/removeXMLProcInst.js +30 -0
  78. package/plugins/reusePaths.js +113 -0
  79. package/plugins/sortAttrs.js +113 -0
  80. package/plugins/sortDefsChildren.js +60 -0
@@ -0,0 +1,432 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @typedef {import('../lib/types').XastElement} XastElement
5
+ */
6
+
7
+ const { cleanupOutData } = require('../lib/svgo/tools.js');
8
+ const {
9
+ transform2js,
10
+ transformsMultiply,
11
+ matrixToTransform,
12
+ } = require('./_transforms.js');
13
+
14
+ exports.type = 'visitor';
15
+ exports.name = 'convertTransform';
16
+ exports.active = true;
17
+ exports.description = 'collapses multiple transformations and optimizes it';
18
+
19
+ /**
20
+ * Convert matrices to the short aliases,
21
+ * convert long translate, scale or rotate transform notations to the shorts ones,
22
+ * convert transforms to the matrices and multiply them all into one,
23
+ * remove useless transforms.
24
+ *
25
+ * @see https://www.w3.org/TR/SVG11/coords.html#TransformMatrixDefined
26
+ *
27
+ * @author Kir Belevich
28
+ *
29
+ * @type {import('../lib/types').Plugin<{
30
+ * convertToShorts?: boolean,
31
+ * degPrecision?: number,
32
+ * floatPrecision?: number,
33
+ * transformPrecision?: number,
34
+ * matrixToTransform?: boolean,
35
+ * shortTranslate?: boolean,
36
+ * shortScale?: boolean,
37
+ * shortRotate?: boolean,
38
+ * removeUseless?: boolean,
39
+ * collapseIntoOne?: boolean,
40
+ * leadingZero?: boolean,
41
+ * negativeExtraSpace?: boolean,
42
+ * }>}
43
+ */
44
+ exports.fn = (_root, params) => {
45
+ const {
46
+ convertToShorts = true,
47
+ // degPrecision = 3, // transformPrecision (or matrix precision) - 2 by default
48
+ degPrecision,
49
+ floatPrecision = 3,
50
+ transformPrecision = 5,
51
+ matrixToTransform = true,
52
+ shortTranslate = true,
53
+ shortScale = true,
54
+ shortRotate = true,
55
+ removeUseless = true,
56
+ collapseIntoOne = true,
57
+ leadingZero = true,
58
+ negativeExtraSpace = false,
59
+ } = params;
60
+ const newParams = {
61
+ convertToShorts,
62
+ degPrecision,
63
+ floatPrecision,
64
+ transformPrecision,
65
+ matrixToTransform,
66
+ shortTranslate,
67
+ shortScale,
68
+ shortRotate,
69
+ removeUseless,
70
+ collapseIntoOne,
71
+ leadingZero,
72
+ negativeExtraSpace,
73
+ };
74
+ return {
75
+ element: {
76
+ enter: (node) => {
77
+ // transform
78
+ if (node.attributes.transform != null) {
79
+ convertTransform(node, 'transform', newParams);
80
+ }
81
+ // gradientTransform
82
+ if (node.attributes.gradientTransform != null) {
83
+ convertTransform(node, 'gradientTransform', newParams);
84
+ }
85
+ // patternTransform
86
+ if (node.attributes.patternTransform != null) {
87
+ convertTransform(node, 'patternTransform', newParams);
88
+ }
89
+ },
90
+ },
91
+ };
92
+ };
93
+
94
+ /**
95
+ * @typedef {{
96
+ * convertToShorts: boolean,
97
+ * degPrecision?: number,
98
+ * floatPrecision: number,
99
+ * transformPrecision: number,
100
+ * matrixToTransform: boolean,
101
+ * shortTranslate: boolean,
102
+ * shortScale: boolean,
103
+ * shortRotate: boolean,
104
+ * removeUseless: boolean,
105
+ * collapseIntoOne: boolean,
106
+ * leadingZero: boolean,
107
+ * negativeExtraSpace: boolean,
108
+ * }} TransformParams
109
+ */
110
+
111
+ /**
112
+ * @typedef {{ name: string, data: Array<number> }} TransformItem
113
+ */
114
+
115
+ /**
116
+ * Main function.
117
+ *
118
+ * @type {(item: XastElement, attrName: string, params: TransformParams) => void}
119
+ */
120
+ const convertTransform = (item, attrName, params) => {
121
+ let data = transform2js(item.attributes[attrName]);
122
+ params = definePrecision(data, params);
123
+
124
+ if (params.collapseIntoOne && data.length > 1) {
125
+ data = [transformsMultiply(data)];
126
+ }
127
+
128
+ if (params.convertToShorts) {
129
+ data = convertToShorts(data, params);
130
+ } else {
131
+ data.forEach((item) => roundTransform(item, params));
132
+ }
133
+
134
+ if (params.removeUseless) {
135
+ data = removeUseless(data);
136
+ }
137
+
138
+ if (data.length) {
139
+ item.attributes[attrName] = js2transform(data, params);
140
+ } else {
141
+ delete item.attributes[attrName];
142
+ }
143
+ };
144
+
145
+ /**
146
+ * Defines precision to work with certain parts.
147
+ * transformPrecision - for scale and four first matrix parameters (needs a better precision due to multiplying),
148
+ * floatPrecision - for translate including two last matrix and rotate parameters,
149
+ * degPrecision - for rotate and skew. By default it's equal to (rougly)
150
+ * transformPrecision - 2 or floatPrecision whichever is lower. Can be set in params.
151
+ *
152
+ * @type {(data: Array<TransformItem>, params: TransformParams) => TransformParams}
153
+ *
154
+ * clone params so it don't affect other elements transformations.
155
+ */
156
+ const definePrecision = (data, { ...newParams }) => {
157
+ const matrixData = [];
158
+ for (const item of data) {
159
+ if (item.name == 'matrix') {
160
+ matrixData.push(...item.data.slice(0, 4));
161
+ }
162
+ }
163
+ let significantDigits = newParams.transformPrecision;
164
+ // Limit transform precision with matrix one. Calculating with larger precision doesn't add any value.
165
+ if (matrixData.length) {
166
+ newParams.transformPrecision = Math.min(
167
+ newParams.transformPrecision,
168
+ Math.max.apply(Math, matrixData.map(floatDigits)) ||
169
+ newParams.transformPrecision
170
+ );
171
+ significantDigits = Math.max.apply(
172
+ Math,
173
+ matrixData.map(
174
+ (n) => n.toString().replace(/\D+/g, '').length // Number of digits in a number. 123.45 → 5
175
+ )
176
+ );
177
+ }
178
+ // No sense in angle precision more then number of significant digits in matrix.
179
+ if (newParams.degPrecision == null) {
180
+ newParams.degPrecision = Math.max(
181
+ 0,
182
+ Math.min(newParams.floatPrecision, significantDigits - 2)
183
+ );
184
+ }
185
+ return newParams;
186
+ };
187
+
188
+ /**
189
+ * @type {(data: Array<number>, params: TransformParams) => Array<number>}
190
+ */
191
+ const degRound = (data, params) => {
192
+ if (
193
+ params.degPrecision != null &&
194
+ params.degPrecision >= 1 &&
195
+ params.floatPrecision < 20
196
+ ) {
197
+ return smartRound(params.degPrecision, data);
198
+ } else {
199
+ return round(data);
200
+ }
201
+ };
202
+ /**
203
+ * @type {(data: Array<number>, params: TransformParams) => Array<number>}
204
+ */
205
+ const floatRound = (data, params) => {
206
+ if (params.floatPrecision >= 1 && params.floatPrecision < 20) {
207
+ return smartRound(params.floatPrecision, data);
208
+ } else {
209
+ return round(data);
210
+ }
211
+ };
212
+
213
+ /**
214
+ * @type {(data: Array<number>, params: TransformParams) => Array<number>}
215
+ */
216
+ const transformRound = (data, params) => {
217
+ if (params.transformPrecision >= 1 && params.floatPrecision < 20) {
218
+ return smartRound(params.transformPrecision, data);
219
+ } else {
220
+ return round(data);
221
+ }
222
+ };
223
+
224
+ /**
225
+ * Returns number of digits after the point. 0.125 → 3
226
+ *
227
+ * @type {(n: number) => number}
228
+ */
229
+ const floatDigits = (n) => {
230
+ const str = n.toString();
231
+ return str.slice(str.indexOf('.')).length - 1;
232
+ };
233
+
234
+ /**
235
+ * Convert transforms to the shorthand alternatives.
236
+ *
237
+ * @type {(transforms: Array<TransformItem>, params: TransformParams) => Array<TransformItem>}
238
+ */
239
+ const convertToShorts = (transforms, params) => {
240
+ for (var i = 0; i < transforms.length; i++) {
241
+ var transform = transforms[i];
242
+
243
+ // convert matrix to the short aliases
244
+ if (params.matrixToTransform && transform.name === 'matrix') {
245
+ var decomposed = matrixToTransform(transform, params);
246
+ if (
247
+ js2transform(decomposed, params).length <=
248
+ js2transform([transform], params).length
249
+ ) {
250
+ transforms.splice(i, 1, ...decomposed);
251
+ }
252
+ transform = transforms[i];
253
+ }
254
+
255
+ // fixed-point numbers
256
+ // 12.754997 → 12.755
257
+ roundTransform(transform, params);
258
+
259
+ // convert long translate transform notation to the shorts one
260
+ // translate(10 0) → translate(10)
261
+ if (
262
+ params.shortTranslate &&
263
+ transform.name === 'translate' &&
264
+ transform.data.length === 2 &&
265
+ !transform.data[1]
266
+ ) {
267
+ transform.data.pop();
268
+ }
269
+
270
+ // convert long scale transform notation to the shorts one
271
+ // scale(2 2) → scale(2)
272
+ if (
273
+ params.shortScale &&
274
+ transform.name === 'scale' &&
275
+ transform.data.length === 2 &&
276
+ transform.data[0] === transform.data[1]
277
+ ) {
278
+ transform.data.pop();
279
+ }
280
+
281
+ // convert long rotate transform notation to the short one
282
+ // translate(cx cy) rotate(a) translate(-cx -cy) → rotate(a cx cy)
283
+ if (
284
+ params.shortRotate &&
285
+ transforms[i - 2] &&
286
+ transforms[i - 2].name === 'translate' &&
287
+ transforms[i - 1].name === 'rotate' &&
288
+ transforms[i].name === 'translate' &&
289
+ transforms[i - 2].data[0] === -transforms[i].data[0] &&
290
+ transforms[i - 2].data[1] === -transforms[i].data[1]
291
+ ) {
292
+ transforms.splice(i - 2, 3, {
293
+ name: 'rotate',
294
+ data: [
295
+ transforms[i - 1].data[0],
296
+ transforms[i - 2].data[0],
297
+ transforms[i - 2].data[1],
298
+ ],
299
+ });
300
+
301
+ // splice compensation
302
+ i -= 2;
303
+ }
304
+ }
305
+
306
+ return transforms;
307
+ };
308
+
309
+ /**
310
+ * Remove useless transforms.
311
+ *
312
+ * @type {(trasforms: Array<TransformItem>) => Array<TransformItem>}
313
+ */
314
+ const removeUseless = (transforms) => {
315
+ return transforms.filter((transform) => {
316
+ // translate(0), rotate(0[, cx, cy]), skewX(0), skewY(0)
317
+ if (
318
+ (['translate', 'rotate', 'skewX', 'skewY'].indexOf(transform.name) > -1 &&
319
+ (transform.data.length == 1 || transform.name == 'rotate') &&
320
+ !transform.data[0]) ||
321
+ // translate(0, 0)
322
+ (transform.name == 'translate' &&
323
+ !transform.data[0] &&
324
+ !transform.data[1]) ||
325
+ // scale(1)
326
+ (transform.name == 'scale' &&
327
+ transform.data[0] == 1 &&
328
+ (transform.data.length < 2 || transform.data[1] == 1)) ||
329
+ // matrix(1 0 0 1 0 0)
330
+ (transform.name == 'matrix' &&
331
+ transform.data[0] == 1 &&
332
+ transform.data[3] == 1 &&
333
+ !(
334
+ transform.data[1] ||
335
+ transform.data[2] ||
336
+ transform.data[4] ||
337
+ transform.data[5]
338
+ ))
339
+ ) {
340
+ return false;
341
+ }
342
+
343
+ return true;
344
+ });
345
+ };
346
+
347
+ /**
348
+ * Convert transforms JS representation to string.
349
+ *
350
+ * @type {(transformJS: Array<TransformItem>, params: TransformParams) => string}
351
+ */
352
+ const js2transform = (transformJS, params) => {
353
+ var transformString = '';
354
+
355
+ // collect output value string
356
+ transformJS.forEach((transform) => {
357
+ roundTransform(transform, params);
358
+ transformString +=
359
+ (transformString && ' ') +
360
+ transform.name +
361
+ '(' +
362
+ cleanupOutData(transform.data, params) +
363
+ ')';
364
+ });
365
+
366
+ return transformString;
367
+ };
368
+
369
+ /**
370
+ * @type {(transform: TransformItem, params: TransformParams) => TransformItem}
371
+ */
372
+ const roundTransform = (transform, params) => {
373
+ switch (transform.name) {
374
+ case 'translate':
375
+ transform.data = floatRound(transform.data, params);
376
+ break;
377
+ case 'rotate':
378
+ transform.data = [
379
+ ...degRound(transform.data.slice(0, 1), params),
380
+ ...floatRound(transform.data.slice(1), params),
381
+ ];
382
+ break;
383
+ case 'skewX':
384
+ case 'skewY':
385
+ transform.data = degRound(transform.data, params);
386
+ break;
387
+ case 'scale':
388
+ transform.data = transformRound(transform.data, params);
389
+ break;
390
+ case 'matrix':
391
+ transform.data = [
392
+ ...transformRound(transform.data.slice(0, 4), params),
393
+ ...floatRound(transform.data.slice(4), params),
394
+ ];
395
+ break;
396
+ }
397
+ return transform;
398
+ };
399
+
400
+ /**
401
+ * Rounds numbers in array.
402
+ *
403
+ * @type {(data: Array<number>) => Array<number>}
404
+ */
405
+ const round = (data) => {
406
+ return data.map(Math.round);
407
+ };
408
+
409
+ /**
410
+ * Decrease accuracy of floating-point numbers
411
+ * in transforms keeping a specified number of decimals.
412
+ * Smart rounds values like 2.349 to 2.35.
413
+ *
414
+ * @type {(precision: number, data: Array<number>) => Array<number>}
415
+ */
416
+ const smartRound = (precision, data) => {
417
+ for (
418
+ var i = data.length,
419
+ tolerance = +Math.pow(0.1, precision).toFixed(precision);
420
+ i--;
421
+
422
+ ) {
423
+ if (Number(data[i].toFixed(precision)) !== data[i]) {
424
+ var rounded = +data[i].toFixed(precision - 1);
425
+ data[i] =
426
+ +Math.abs(rounded - data[i]).toFixed(precision + 1) >= tolerance
427
+ ? +data[i].toFixed(precision)
428
+ : rounded;
429
+ }
430
+ }
431
+ return data;
432
+ };