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.
- package/LICENSE +21 -0
- package/README.md +294 -0
- package/bin/svgo +10 -0
- package/dist/svgo.browser.js +1 -0
- package/lib/css-tools.js +239 -0
- package/lib/parser.js +259 -0
- package/lib/path.js +347 -0
- package/lib/stringifier.js +326 -0
- package/lib/style.js +283 -0
- package/lib/svgo/coa.js +517 -0
- package/lib/svgo/config.js +138 -0
- package/lib/svgo/css-class-list.js +72 -0
- package/lib/svgo/css-select-adapter.d.ts +2 -0
- package/lib/svgo/css-select-adapter.js +120 -0
- package/lib/svgo/css-style-declaration.js +232 -0
- package/lib/svgo/jsAPI.d.ts +2 -0
- package/lib/svgo/jsAPI.js +443 -0
- package/lib/svgo/plugins.js +109 -0
- package/lib/svgo/tools.js +137 -0
- package/lib/svgo-node.js +106 -0
- package/lib/svgo.js +83 -0
- package/lib/types.ts +172 -0
- package/lib/xast.js +102 -0
- package/package.json +130 -0
- package/plugins/_applyTransforms.js +335 -0
- package/plugins/_collections.js +2168 -0
- package/plugins/_path.js +816 -0
- package/plugins/_transforms.js +379 -0
- package/plugins/addAttributesToSVGElement.js +87 -0
- package/plugins/addClassesToSVGElement.js +87 -0
- package/plugins/cleanupAttrs.js +55 -0
- package/plugins/cleanupEnableBackground.js +75 -0
- package/plugins/cleanupIDs.js +297 -0
- package/plugins/cleanupListOfValues.js +154 -0
- package/plugins/cleanupNumericValues.js +113 -0
- package/plugins/collapseGroups.js +135 -0
- package/plugins/convertColors.js +152 -0
- package/plugins/convertEllipseToCircle.js +39 -0
- package/plugins/convertPathData.js +1023 -0
- package/plugins/convertShapeToPath.js +175 -0
- package/plugins/convertStyleToAttrs.js +132 -0
- package/plugins/convertTransform.js +432 -0
- package/plugins/inlineStyles.js +379 -0
- package/plugins/mergePaths.js +104 -0
- package/plugins/mergeStyles.js +93 -0
- package/plugins/minifyStyles.js +148 -0
- package/plugins/moveElemsAttrsToGroup.js +130 -0
- package/plugins/moveGroupAttrsToElems.js +62 -0
- package/plugins/plugins.js +56 -0
- package/plugins/prefixIds.js +241 -0
- package/plugins/preset-default.js +80 -0
- package/plugins/removeAttributesBySelector.js +99 -0
- package/plugins/removeAttrs.js +159 -0
- package/plugins/removeComments.js +31 -0
- package/plugins/removeDesc.js +41 -0
- package/plugins/removeDimensions.js +43 -0
- package/plugins/removeDoctype.js +42 -0
- package/plugins/removeEditorsNSData.js +68 -0
- package/plugins/removeElementsByAttr.js +78 -0
- package/plugins/removeEmptyAttrs.js +33 -0
- package/plugins/removeEmptyContainers.js +58 -0
- package/plugins/removeEmptyText.js +57 -0
- package/plugins/removeHiddenElems.js +318 -0
- package/plugins/removeMetadata.js +29 -0
- package/plugins/removeNonInheritableGroupAttrs.js +38 -0
- package/plugins/removeOffCanvasPaths.js +138 -0
- package/plugins/removeRasterImages.js +33 -0
- package/plugins/removeScriptElement.js +29 -0
- package/plugins/removeStyleElement.js +29 -0
- package/plugins/removeTitle.js +29 -0
- package/plugins/removeUnknownsAndDefaults.js +218 -0
- package/plugins/removeUnusedNS.js +61 -0
- package/plugins/removeUselessDefs.js +65 -0
- package/plugins/removeUselessStrokeAndFill.js +144 -0
- package/plugins/removeViewBox.js +51 -0
- package/plugins/removeXMLNS.js +30 -0
- package/plugins/removeXMLProcInst.js +30 -0
- package/plugins/reusePaths.js +113 -0
- package/plugins/sortAttrs.js +113 -0
- package/plugins/sortDefsChildren.js +60 -0
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { selectAll, selectOne, is } = require('css-select');
|
|
4
|
+
const svgoCssSelectAdapter = require('./css-select-adapter');
|
|
5
|
+
const CSSClassList = require('./css-class-list');
|
|
6
|
+
const CSSStyleDeclaration = require('./css-style-declaration');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @type {(name: string) => { prefix: string, local: string }}
|
|
10
|
+
*/
|
|
11
|
+
const parseName = (name) => {
|
|
12
|
+
if (name == null) {
|
|
13
|
+
return {
|
|
14
|
+
prefix: '',
|
|
15
|
+
local: '',
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
if (name === 'xmlns') {
|
|
19
|
+
return {
|
|
20
|
+
prefix: 'xmlns',
|
|
21
|
+
local: '',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const chunks = name.split(':');
|
|
25
|
+
if (chunks.length === 1) {
|
|
26
|
+
return {
|
|
27
|
+
prefix: '',
|
|
28
|
+
local: chunks[0],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
prefix: chunks[0],
|
|
33
|
+
local: chunks[1],
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
var cssSelectOpts = {
|
|
38
|
+
xmlMode: true,
|
|
39
|
+
adapter: svgoCssSelectAdapter,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const attrsHandler = {
|
|
43
|
+
get: (attributes, name) => {
|
|
44
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
45
|
+
if (attributes.hasOwnProperty(name)) {
|
|
46
|
+
return {
|
|
47
|
+
name,
|
|
48
|
+
get value() {
|
|
49
|
+
return attributes[name];
|
|
50
|
+
},
|
|
51
|
+
set value(value) {
|
|
52
|
+
attributes[name] = value;
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
set: (attributes, name, attr) => {
|
|
58
|
+
attributes[name] = attr.value;
|
|
59
|
+
return true;
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
var JSAPI = function (data, parentNode) {
|
|
64
|
+
Object.assign(this, data);
|
|
65
|
+
if (this.type === 'element') {
|
|
66
|
+
if (this.attributes == null) {
|
|
67
|
+
this.attributes = {};
|
|
68
|
+
}
|
|
69
|
+
if (this.children == null) {
|
|
70
|
+
this.children = [];
|
|
71
|
+
}
|
|
72
|
+
Object.defineProperty(this, 'class', {
|
|
73
|
+
writable: true,
|
|
74
|
+
configurable: true,
|
|
75
|
+
value: new CSSClassList(this),
|
|
76
|
+
});
|
|
77
|
+
Object.defineProperty(this, 'style', {
|
|
78
|
+
writable: true,
|
|
79
|
+
configurable: true,
|
|
80
|
+
value: new CSSStyleDeclaration(this),
|
|
81
|
+
});
|
|
82
|
+
Object.defineProperty(this, 'parentNode', {
|
|
83
|
+
writable: true,
|
|
84
|
+
value: parentNode,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// temporary attrs polyfill
|
|
88
|
+
// TODO remove after migration
|
|
89
|
+
const element = this;
|
|
90
|
+
Object.defineProperty(this, 'attrs', {
|
|
91
|
+
configurable: true,
|
|
92
|
+
get() {
|
|
93
|
+
return new Proxy(element.attributes, attrsHandler);
|
|
94
|
+
},
|
|
95
|
+
set(value) {
|
|
96
|
+
const newAttributes = {};
|
|
97
|
+
for (const attr of Object.values(value)) {
|
|
98
|
+
newAttributes[attr.name] = attr.value;
|
|
99
|
+
}
|
|
100
|
+
element.attributes = newAttributes;
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
module.exports = JSAPI;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Perform a deep clone of this node.
|
|
109
|
+
*
|
|
110
|
+
* @return {Object} element
|
|
111
|
+
*/
|
|
112
|
+
JSAPI.prototype.clone = function () {
|
|
113
|
+
const { children, ...nodeData } = this;
|
|
114
|
+
// Deep-clone node data.
|
|
115
|
+
const clonedNode = new JSAPI(JSON.parse(JSON.stringify(nodeData)), null);
|
|
116
|
+
if (children) {
|
|
117
|
+
clonedNode.children = children.map((child) => {
|
|
118
|
+
const clonedChild = child.clone();
|
|
119
|
+
clonedChild.parentNode = clonedNode;
|
|
120
|
+
return clonedChild;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return clonedNode;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Determine if item is an element
|
|
128
|
+
* (any, with a specific name or in a names array).
|
|
129
|
+
*
|
|
130
|
+
* @param {String|Array} [param] element name or names arrays
|
|
131
|
+
* @return {Boolean}
|
|
132
|
+
*/
|
|
133
|
+
JSAPI.prototype.isElem = function (param) {
|
|
134
|
+
if (this.type !== 'element') {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
if (param == null) {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
if (Array.isArray(param)) {
|
|
141
|
+
return param.includes(this.name);
|
|
142
|
+
}
|
|
143
|
+
return this.name === param;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Renames an element
|
|
148
|
+
*
|
|
149
|
+
* @param {String} name new element name
|
|
150
|
+
* @return {Object} element
|
|
151
|
+
*/
|
|
152
|
+
JSAPI.prototype.renameElem = function (name) {
|
|
153
|
+
if (name && typeof name === 'string') this.name = name;
|
|
154
|
+
|
|
155
|
+
return this;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Determine if element is empty.
|
|
160
|
+
*
|
|
161
|
+
* @return {Boolean}
|
|
162
|
+
*/
|
|
163
|
+
JSAPI.prototype.isEmpty = function () {
|
|
164
|
+
return !this.children || !this.children.length;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Find the closest ancestor of the current element.
|
|
169
|
+
* @param elemName
|
|
170
|
+
*
|
|
171
|
+
* @return {?Object}
|
|
172
|
+
*/
|
|
173
|
+
JSAPI.prototype.closestElem = function (elemName) {
|
|
174
|
+
var elem = this;
|
|
175
|
+
|
|
176
|
+
while ((elem = elem.parentNode) && !elem.isElem(elemName));
|
|
177
|
+
|
|
178
|
+
return elem;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Changes children by removing elements and/or adding new elements.
|
|
183
|
+
*
|
|
184
|
+
* @param {Number} start Index at which to start changing the children.
|
|
185
|
+
* @param {Number} n Number of elements to remove.
|
|
186
|
+
* @param {Array|Object} [insertion] Elements to add to the children.
|
|
187
|
+
* @return {Array} Removed elements.
|
|
188
|
+
*/
|
|
189
|
+
JSAPI.prototype.spliceContent = function (start, n, insertion) {
|
|
190
|
+
if (arguments.length < 2) return [];
|
|
191
|
+
|
|
192
|
+
if (!Array.isArray(insertion))
|
|
193
|
+
insertion = Array.apply(null, arguments).slice(2);
|
|
194
|
+
|
|
195
|
+
insertion.forEach(function (inner) {
|
|
196
|
+
inner.parentNode = this;
|
|
197
|
+
}, this);
|
|
198
|
+
|
|
199
|
+
return this.children.splice.apply(
|
|
200
|
+
this.children,
|
|
201
|
+
[start, n].concat(insertion)
|
|
202
|
+
);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Determine if element has an attribute
|
|
207
|
+
* (any, or by name or by name + value).
|
|
208
|
+
*
|
|
209
|
+
* @param {String} [name] attribute name
|
|
210
|
+
* @param {String} [val] attribute value (will be toString()'ed)
|
|
211
|
+
* @return {Boolean}
|
|
212
|
+
*/
|
|
213
|
+
JSAPI.prototype.hasAttr = function (name, val) {
|
|
214
|
+
if (this.type !== 'element') {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
if (Object.keys(this.attributes).length === 0) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
if (name == null) {
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
224
|
+
if (this.attributes.hasOwnProperty(name) === false) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
if (val !== undefined) {
|
|
228
|
+
return this.attributes[name] === val.toString();
|
|
229
|
+
}
|
|
230
|
+
return true;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Determine if element has an attribute by local name
|
|
235
|
+
* (any, or by name or by name + value).
|
|
236
|
+
*
|
|
237
|
+
* @param {String} [localName] local attribute name
|
|
238
|
+
* @param {Number|String|RegExp|Function} [val] attribute value (will be toString()'ed or executed, otherwise ignored)
|
|
239
|
+
* @return {Boolean}
|
|
240
|
+
*/
|
|
241
|
+
JSAPI.prototype.hasAttrLocal = function (localName, val) {
|
|
242
|
+
if (!this.attrs || !Object.keys(this.attrs).length) return false;
|
|
243
|
+
|
|
244
|
+
if (!arguments.length) return !!this.attrs;
|
|
245
|
+
|
|
246
|
+
var callback;
|
|
247
|
+
|
|
248
|
+
switch (val != null && val.constructor && val.constructor.name) {
|
|
249
|
+
case 'Number': // same as String
|
|
250
|
+
case 'String':
|
|
251
|
+
callback = stringValueTest;
|
|
252
|
+
break;
|
|
253
|
+
case 'RegExp':
|
|
254
|
+
callback = regexpValueTest;
|
|
255
|
+
break;
|
|
256
|
+
case 'Function':
|
|
257
|
+
callback = funcValueTest;
|
|
258
|
+
break;
|
|
259
|
+
default:
|
|
260
|
+
callback = nameTest;
|
|
261
|
+
}
|
|
262
|
+
return this.someAttr(callback);
|
|
263
|
+
|
|
264
|
+
function nameTest(attr) {
|
|
265
|
+
const { local } = parseName(attr.name);
|
|
266
|
+
return local === localName;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function stringValueTest(attr) {
|
|
270
|
+
const { local } = parseName(attr.name);
|
|
271
|
+
return local === localName && val == attr.value;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function regexpValueTest(attr) {
|
|
275
|
+
const { local } = parseName(attr.name);
|
|
276
|
+
return local === localName && val.test(attr.value);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function funcValueTest(attr) {
|
|
280
|
+
const { local } = parseName(attr.name);
|
|
281
|
+
return local === localName && val(attr.value);
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Get a specific attribute from an element
|
|
287
|
+
* (by name or name + value).
|
|
288
|
+
*
|
|
289
|
+
* @param {String} name attribute name
|
|
290
|
+
* @param {String} [val] attribute value (will be toString()'ed)
|
|
291
|
+
* @return {Object|Undefined}
|
|
292
|
+
*/
|
|
293
|
+
JSAPI.prototype.attr = function (name, val) {
|
|
294
|
+
if (this.hasAttr(name, val)) {
|
|
295
|
+
return this.attrs[name];
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Get computed attribute value from an element
|
|
301
|
+
*
|
|
302
|
+
* @param {String} name attribute name
|
|
303
|
+
* @return {Object|Undefined}
|
|
304
|
+
*/
|
|
305
|
+
JSAPI.prototype.computedAttr = function (name, val) {
|
|
306
|
+
if (!arguments.length) return;
|
|
307
|
+
|
|
308
|
+
for (
|
|
309
|
+
var elem = this;
|
|
310
|
+
elem && (!elem.hasAttr(name) || !elem.attributes[name]);
|
|
311
|
+
elem = elem.parentNode
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
if (val != null) {
|
|
315
|
+
return elem ? elem.hasAttr(name, val) : false;
|
|
316
|
+
} else if (elem && elem.hasAttr(name)) {
|
|
317
|
+
return elem.attributes[name];
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Remove a specific attribute.
|
|
323
|
+
*
|
|
324
|
+
* @param {String|Array} name attribute name
|
|
325
|
+
* @param {String} [val] attribute value
|
|
326
|
+
* @return {Boolean}
|
|
327
|
+
*/
|
|
328
|
+
JSAPI.prototype.removeAttr = function (name, val) {
|
|
329
|
+
if (this.type !== 'element') {
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
if (arguments.length === 0) {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
if (Array.isArray(name)) {
|
|
336
|
+
for (const nameItem of name) {
|
|
337
|
+
this.removeAttr(nameItem, val);
|
|
338
|
+
}
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
if (this.hasAttr(name, val) === false) {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
delete this.attributes[name];
|
|
345
|
+
return true;
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Add attribute.
|
|
350
|
+
*
|
|
351
|
+
* @param {Object} [attr={}] attribute object
|
|
352
|
+
* @return {Object|Boolean} created attribute or false if no attr was passed in
|
|
353
|
+
*/
|
|
354
|
+
JSAPI.prototype.addAttr = function (attr) {
|
|
355
|
+
attr = attr || {};
|
|
356
|
+
|
|
357
|
+
if (attr.name === undefined) return false;
|
|
358
|
+
|
|
359
|
+
this.attributes[attr.name] = attr.value;
|
|
360
|
+
|
|
361
|
+
if (attr.name === 'class') {
|
|
362
|
+
// newly added class attribute
|
|
363
|
+
this.class.addClassValueHandler();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (attr.name === 'style') {
|
|
367
|
+
// newly added style attribute
|
|
368
|
+
this.style.addStyleValueHandler();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return this.attrs[attr.name];
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Iterates over all attributes.
|
|
376
|
+
*
|
|
377
|
+
* @param {Function} callback callback
|
|
378
|
+
* @param {Object} [context] callback context
|
|
379
|
+
* @return {Boolean} false if there are no any attributes
|
|
380
|
+
*/
|
|
381
|
+
JSAPI.prototype.eachAttr = function (callback, context) {
|
|
382
|
+
if (this.type !== 'element') {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
if (callback == null) {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
for (const attr of Object.values(this.attrs)) {
|
|
389
|
+
callback.call(context, attr);
|
|
390
|
+
}
|
|
391
|
+
return true;
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Tests whether some attribute passes the test.
|
|
396
|
+
*
|
|
397
|
+
* @param {Function} callback callback
|
|
398
|
+
* @param {Object} [context] callback context
|
|
399
|
+
* @return {Boolean} false if there are no any attributes
|
|
400
|
+
*/
|
|
401
|
+
JSAPI.prototype.someAttr = function (callback, context) {
|
|
402
|
+
if (this.type !== 'element') {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
for (const attr of Object.values(this.attrs)) {
|
|
407
|
+
if (callback.call(context, attr)) return true;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return false;
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Evaluate a string of CSS selectors against the element and returns matched elements.
|
|
415
|
+
*
|
|
416
|
+
* @param {String} selectors CSS selector(s) string
|
|
417
|
+
* @return {Array} null if no elements matched
|
|
418
|
+
*/
|
|
419
|
+
JSAPI.prototype.querySelectorAll = function (selectors) {
|
|
420
|
+
var matchedEls = selectAll(selectors, this, cssSelectOpts);
|
|
421
|
+
|
|
422
|
+
return matchedEls.length > 0 ? matchedEls : null;
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Evaluate a string of CSS selectors against the element and returns only the first matched element.
|
|
427
|
+
*
|
|
428
|
+
* @param {String} selectors CSS selector(s) string
|
|
429
|
+
* @return {Array} null if no element matched
|
|
430
|
+
*/
|
|
431
|
+
JSAPI.prototype.querySelector = function (selectors) {
|
|
432
|
+
return selectOne(selectors, this, cssSelectOpts);
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Test if a selector matches a given element.
|
|
437
|
+
*
|
|
438
|
+
* @param {String} selector CSS selector string
|
|
439
|
+
* @return {Boolean} true if element would be selected by selector string, false if it does not
|
|
440
|
+
*/
|
|
441
|
+
JSAPI.prototype.matches = function (selector) {
|
|
442
|
+
return is(this, selector, cssSelectOpts);
|
|
443
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { visit } = require('../xast.js');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Plugins engine.
|
|
7
|
+
*
|
|
8
|
+
* @module plugins
|
|
9
|
+
*
|
|
10
|
+
* @param {Object} ast input ast
|
|
11
|
+
* @param {Object} info extra information
|
|
12
|
+
* @param {Array} plugins plugins object from config
|
|
13
|
+
* @return {Object} output ast
|
|
14
|
+
*/
|
|
15
|
+
const invokePlugins = (ast, info, plugins, overrides, globalOverrides) => {
|
|
16
|
+
for (const plugin of plugins) {
|
|
17
|
+
const override = overrides == null ? null : overrides[plugin.name];
|
|
18
|
+
if (override === false) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const params = { ...plugin.params, ...globalOverrides, ...override };
|
|
22
|
+
|
|
23
|
+
if (plugin.type === 'perItem') {
|
|
24
|
+
ast = perItem(ast, info, plugin, params);
|
|
25
|
+
}
|
|
26
|
+
if (plugin.type === 'perItemReverse') {
|
|
27
|
+
ast = perItem(ast, info, plugin, params, true);
|
|
28
|
+
}
|
|
29
|
+
if (plugin.type === 'full') {
|
|
30
|
+
if (plugin.active) {
|
|
31
|
+
ast = plugin.fn(ast, params, info);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (plugin.type === 'visitor') {
|
|
35
|
+
if (plugin.active) {
|
|
36
|
+
const visitor = plugin.fn(ast, params, info);
|
|
37
|
+
if (visitor != null) {
|
|
38
|
+
visit(ast, visitor);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return ast;
|
|
44
|
+
};
|
|
45
|
+
exports.invokePlugins = invokePlugins;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Direct or reverse per-item loop.
|
|
49
|
+
*
|
|
50
|
+
* @param {Object} data input data
|
|
51
|
+
* @param {Object} info extra information
|
|
52
|
+
* @param {Array} plugins plugins list to process
|
|
53
|
+
* @param {boolean} [reverse] reverse pass?
|
|
54
|
+
* @return {Object} output data
|
|
55
|
+
*/
|
|
56
|
+
function perItem(data, info, plugin, params, reverse) {
|
|
57
|
+
function monkeys(items) {
|
|
58
|
+
items.children = items.children.filter(function (item) {
|
|
59
|
+
// reverse pass
|
|
60
|
+
if (reverse && item.children) {
|
|
61
|
+
monkeys(item);
|
|
62
|
+
}
|
|
63
|
+
// main filter
|
|
64
|
+
let kept = true;
|
|
65
|
+
if (plugin.active) {
|
|
66
|
+
kept = plugin.fn(item, params, info) !== false;
|
|
67
|
+
}
|
|
68
|
+
// direct pass
|
|
69
|
+
if (!reverse && item.children) {
|
|
70
|
+
monkeys(item);
|
|
71
|
+
}
|
|
72
|
+
return kept;
|
|
73
|
+
});
|
|
74
|
+
return items;
|
|
75
|
+
}
|
|
76
|
+
return monkeys(data);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const createPreset = ({ name, plugins }) => {
|
|
80
|
+
return {
|
|
81
|
+
name,
|
|
82
|
+
type: 'full',
|
|
83
|
+
fn: (ast, params, info) => {
|
|
84
|
+
const { floatPrecision, overrides } = params;
|
|
85
|
+
const globalOverrides = {};
|
|
86
|
+
if (floatPrecision != null) {
|
|
87
|
+
globalOverrides.floatPrecision = floatPrecision;
|
|
88
|
+
}
|
|
89
|
+
if (overrides) {
|
|
90
|
+
for (const [pluginName, override] of Object.entries(overrides)) {
|
|
91
|
+
if (override === true) {
|
|
92
|
+
console.warn(
|
|
93
|
+
`You are trying to enable ${pluginName} which is not part of preset.\n` +
|
|
94
|
+
`Try to put it before or after preset, for example\n\n` +
|
|
95
|
+
`plugins: [\n` +
|
|
96
|
+
` {\n` +
|
|
97
|
+
` name: 'preset-default',\n` +
|
|
98
|
+
` },\n` +
|
|
99
|
+
` 'cleanupListOfValues'\n` +
|
|
100
|
+
`]\n`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return invokePlugins(ast, info, plugins, overrides, globalOverrides);
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
exports.createPreset = createPreset;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('../types').PathDataCommand} PathDataCommand
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Encode plain SVG data string into Data URI string.
|
|
9
|
+
*
|
|
10
|
+
* @type {(str: string, type?: 'base64' | 'enc' | 'unenc') => string}
|
|
11
|
+
*/
|
|
12
|
+
exports.encodeSVGDatauri = (str, type) => {
|
|
13
|
+
var prefix = 'data:image/svg+xml';
|
|
14
|
+
if (!type || type === 'base64') {
|
|
15
|
+
// base64
|
|
16
|
+
prefix += ';base64,';
|
|
17
|
+
str = prefix + Buffer.from(str).toString('base64');
|
|
18
|
+
} else if (type === 'enc') {
|
|
19
|
+
// URI encoded
|
|
20
|
+
str = prefix + ',' + encodeURIComponent(str);
|
|
21
|
+
} else if (type === 'unenc') {
|
|
22
|
+
// unencoded
|
|
23
|
+
str = prefix + ',' + str;
|
|
24
|
+
}
|
|
25
|
+
return str;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Decode SVG Data URI string into plain SVG string.
|
|
30
|
+
*
|
|
31
|
+
* @type {(str: string) => string}
|
|
32
|
+
*/
|
|
33
|
+
exports.decodeSVGDatauri = (str) => {
|
|
34
|
+
var regexp = /data:image\/svg\+xml(;charset=[^;,]*)?(;base64)?,(.*)/;
|
|
35
|
+
var match = regexp.exec(str);
|
|
36
|
+
|
|
37
|
+
// plain string
|
|
38
|
+
if (!match) return str;
|
|
39
|
+
|
|
40
|
+
var data = match[3];
|
|
41
|
+
|
|
42
|
+
if (match[2]) {
|
|
43
|
+
// base64
|
|
44
|
+
str = Buffer.from(data, 'base64').toString('utf8');
|
|
45
|
+
} else if (data.charAt(0) === '%') {
|
|
46
|
+
// URI encoded
|
|
47
|
+
str = decodeURIComponent(data);
|
|
48
|
+
} else if (data.charAt(0) === '<') {
|
|
49
|
+
// unencoded
|
|
50
|
+
str = data;
|
|
51
|
+
}
|
|
52
|
+
return str;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @typedef {{
|
|
57
|
+
* noSpaceAfterFlags?: boolean,
|
|
58
|
+
* leadingZero?: boolean,
|
|
59
|
+
* negativeExtraSpace?: boolean
|
|
60
|
+
* }} CleanupOutDataParams
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Convert a row of numbers to an optimized string view.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* [0, -1, .5, .5] → "0-1 .5.5"
|
|
68
|
+
*
|
|
69
|
+
* @type {(data: Array<number>, params: CleanupOutDataParams, command?: PathDataCommand) => string}
|
|
70
|
+
*/
|
|
71
|
+
exports.cleanupOutData = (data, params, command) => {
|
|
72
|
+
let str = '';
|
|
73
|
+
let delimiter;
|
|
74
|
+
/**
|
|
75
|
+
* @type {number}
|
|
76
|
+
*/
|
|
77
|
+
let prev;
|
|
78
|
+
|
|
79
|
+
data.forEach((item, i) => {
|
|
80
|
+
// space delimiter by default
|
|
81
|
+
delimiter = ' ';
|
|
82
|
+
|
|
83
|
+
// no extra space in front of first number
|
|
84
|
+
if (i == 0) delimiter = '';
|
|
85
|
+
|
|
86
|
+
// no extra space after 'arcto' command flags(large-arc and sweep flags)
|
|
87
|
+
// a20 60 45 0 1 30 20 → a20 60 45 0130 20
|
|
88
|
+
if (params.noSpaceAfterFlags && (command == 'A' || command == 'a')) {
|
|
89
|
+
var pos = i % 7;
|
|
90
|
+
if (pos == 4 || pos == 5) delimiter = '';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// remove floating-point numbers leading zeros
|
|
94
|
+
// 0.5 → .5
|
|
95
|
+
// -0.5 → -.5
|
|
96
|
+
const itemStr = params.leadingZero
|
|
97
|
+
? removeLeadingZero(item)
|
|
98
|
+
: item.toString();
|
|
99
|
+
|
|
100
|
+
// no extra space in front of negative number or
|
|
101
|
+
// in front of a floating number if a previous number is floating too
|
|
102
|
+
if (
|
|
103
|
+
params.negativeExtraSpace &&
|
|
104
|
+
delimiter != '' &&
|
|
105
|
+
(item < 0 || (itemStr.charAt(0) === '.' && prev % 1 !== 0))
|
|
106
|
+
) {
|
|
107
|
+
delimiter = '';
|
|
108
|
+
}
|
|
109
|
+
// save prev item value
|
|
110
|
+
prev = item;
|
|
111
|
+
str += delimiter + itemStr;
|
|
112
|
+
});
|
|
113
|
+
return str;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Remove floating-point numbers leading zero.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* 0.5 → .5
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* -0.5 → -.5
|
|
124
|
+
*
|
|
125
|
+
* @type {(num: number) => string}
|
|
126
|
+
*/
|
|
127
|
+
const removeLeadingZero = (num) => {
|
|
128
|
+
var strNum = num.toString();
|
|
129
|
+
|
|
130
|
+
if (0 < num && num < 1 && strNum.charAt(0) === '0') {
|
|
131
|
+
strNum = strNum.slice(1);
|
|
132
|
+
} else if (-1 < num && num < 0 && strNum.charAt(1) === '0') {
|
|
133
|
+
strNum = strNum.charAt(0) + strNum.slice(2);
|
|
134
|
+
}
|
|
135
|
+
return strNum;
|
|
136
|
+
};
|
|
137
|
+
exports.removeLeadingZero = removeLeadingZero;
|