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,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { detachNodeFromParent } = require('../lib/xast.js');
|
|
4
|
+
|
|
5
|
+
exports.name = 'removeStyleElement';
|
|
6
|
+
exports.type = 'visitor';
|
|
7
|
+
exports.active = false;
|
|
8
|
+
exports.description = 'removes <style> element (disabled by default)';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Remove <style>.
|
|
12
|
+
*
|
|
13
|
+
* https://www.w3.org/TR/SVG11/styling.html#StyleElement
|
|
14
|
+
*
|
|
15
|
+
* @author Betsy Dupuis
|
|
16
|
+
*
|
|
17
|
+
* @type {import('../lib/types').Plugin<void>}
|
|
18
|
+
*/
|
|
19
|
+
exports.fn = () => {
|
|
20
|
+
return {
|
|
21
|
+
element: {
|
|
22
|
+
enter: (node, parentNode) => {
|
|
23
|
+
if (node.name === 'style') {
|
|
24
|
+
detachNodeFromParent(node, parentNode);
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { detachNodeFromParent } = require('../lib/xast.js');
|
|
4
|
+
|
|
5
|
+
exports.name = 'removeTitle';
|
|
6
|
+
exports.type = 'visitor';
|
|
7
|
+
exports.active = true;
|
|
8
|
+
exports.description = 'removes <title>';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Remove <title>.
|
|
12
|
+
*
|
|
13
|
+
* https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title
|
|
14
|
+
*
|
|
15
|
+
* @author Igor Kalashnikov
|
|
16
|
+
*
|
|
17
|
+
* @type {import('../lib/types').Plugin<void>}
|
|
18
|
+
*/
|
|
19
|
+
exports.fn = () => {
|
|
20
|
+
return {
|
|
21
|
+
element: {
|
|
22
|
+
enter: (node, parentNode) => {
|
|
23
|
+
if (node.name === 'title') {
|
|
24
|
+
detachNodeFromParent(node, parentNode);
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
};
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { visitSkip, detachNodeFromParent } = require('../lib/xast.js');
|
|
4
|
+
const { collectStylesheet, computeStyle } = require('../lib/style.js');
|
|
5
|
+
const {
|
|
6
|
+
elems,
|
|
7
|
+
attrsGroups,
|
|
8
|
+
elemsGroups,
|
|
9
|
+
attrsGroupsDefaults,
|
|
10
|
+
presentationNonInheritableGroupAttrs,
|
|
11
|
+
} = require('./_collections');
|
|
12
|
+
|
|
13
|
+
exports.type = 'visitor';
|
|
14
|
+
exports.name = 'removeUnknownsAndDefaults';
|
|
15
|
+
exports.active = true;
|
|
16
|
+
exports.description =
|
|
17
|
+
'removes unknown elements content and attributes, removes attrs with default values';
|
|
18
|
+
|
|
19
|
+
// resolve all groups references
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @type {Map<string, Set<string>>}
|
|
23
|
+
*/
|
|
24
|
+
const allowedChildrenPerElement = new Map();
|
|
25
|
+
/**
|
|
26
|
+
* @type {Map<string, Set<string>>}
|
|
27
|
+
*/
|
|
28
|
+
const allowedAttributesPerElement = new Map();
|
|
29
|
+
/**
|
|
30
|
+
* @type {Map<string, Map<string, string>>}
|
|
31
|
+
*/
|
|
32
|
+
const attributesDefaultsPerElement = new Map();
|
|
33
|
+
|
|
34
|
+
for (const [name, config] of Object.entries(elems)) {
|
|
35
|
+
/**
|
|
36
|
+
* @type {Set<string>}
|
|
37
|
+
*/
|
|
38
|
+
const allowedChildren = new Set();
|
|
39
|
+
if (config.content) {
|
|
40
|
+
for (const elementName of config.content) {
|
|
41
|
+
allowedChildren.add(elementName);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (config.contentGroups) {
|
|
45
|
+
for (const contentGroupName of config.contentGroups) {
|
|
46
|
+
const elemsGroup = elemsGroups[contentGroupName];
|
|
47
|
+
if (elemsGroup) {
|
|
48
|
+
for (const elementName of elemsGroup) {
|
|
49
|
+
allowedChildren.add(elementName);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* @type {Set<string>}
|
|
56
|
+
*/
|
|
57
|
+
const allowedAttributes = new Set();
|
|
58
|
+
if (config.attrs) {
|
|
59
|
+
for (const attrName of config.attrs) {
|
|
60
|
+
allowedAttributes.add(attrName);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* @type {Map<string, string>}
|
|
65
|
+
*/
|
|
66
|
+
const attributesDefaults = new Map();
|
|
67
|
+
if (config.defaults) {
|
|
68
|
+
for (const [attrName, defaultValue] of Object.entries(config.defaults)) {
|
|
69
|
+
attributesDefaults.set(attrName, defaultValue);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
for (const attrsGroupName of config.attrsGroups) {
|
|
73
|
+
const attrsGroup = attrsGroups[attrsGroupName];
|
|
74
|
+
if (attrsGroup) {
|
|
75
|
+
for (const attrName of attrsGroup) {
|
|
76
|
+
allowedAttributes.add(attrName);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const groupDefaults = attrsGroupsDefaults[attrsGroupName];
|
|
80
|
+
if (groupDefaults) {
|
|
81
|
+
for (const [attrName, defaultValue] of Object.entries(groupDefaults)) {
|
|
82
|
+
attributesDefaults.set(attrName, defaultValue);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
allowedChildrenPerElement.set(name, allowedChildren);
|
|
87
|
+
allowedAttributesPerElement.set(name, allowedAttributes);
|
|
88
|
+
attributesDefaultsPerElement.set(name, attributesDefaults);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Remove unknown elements content and attributes,
|
|
93
|
+
* remove attributes with default values.
|
|
94
|
+
*
|
|
95
|
+
* @author Kir Belevich
|
|
96
|
+
*
|
|
97
|
+
* @type {import('../lib/types').Plugin<{
|
|
98
|
+
* unknownContent?: boolean,
|
|
99
|
+
* unknownAttrs?: boolean,
|
|
100
|
+
* defaultAttrs?: boolean,
|
|
101
|
+
* uselessOverrides?: boolean,
|
|
102
|
+
* keepDataAttrs?: boolean,
|
|
103
|
+
* keepAriaAttrs?: boolean,
|
|
104
|
+
* keepRoleAttr?: boolean,
|
|
105
|
+
* }>}
|
|
106
|
+
*/
|
|
107
|
+
exports.fn = (root, params) => {
|
|
108
|
+
const {
|
|
109
|
+
unknownContent = true,
|
|
110
|
+
unknownAttrs = true,
|
|
111
|
+
defaultAttrs = true,
|
|
112
|
+
uselessOverrides = true,
|
|
113
|
+
keepDataAttrs = true,
|
|
114
|
+
keepAriaAttrs = true,
|
|
115
|
+
keepRoleAttr = false,
|
|
116
|
+
} = params;
|
|
117
|
+
const stylesheet = collectStylesheet(root);
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
element: {
|
|
121
|
+
enter: (node, parentNode) => {
|
|
122
|
+
// skip namespaced elements
|
|
123
|
+
if (node.name.includes(':')) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
// skip visiting foreignObject subtree
|
|
127
|
+
if (node.name === 'foreignObject') {
|
|
128
|
+
return visitSkip;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// remove unknown element's content
|
|
132
|
+
if (unknownContent && parentNode.type === 'element') {
|
|
133
|
+
const allowedChildren = allowedChildrenPerElement.get(
|
|
134
|
+
parentNode.name
|
|
135
|
+
);
|
|
136
|
+
if (allowedChildren == null || allowedChildren.size === 0) {
|
|
137
|
+
// remove unknown elements
|
|
138
|
+
if (allowedChildrenPerElement.get(node.name) == null) {
|
|
139
|
+
detachNodeFromParent(node, parentNode);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
// remove not allowed children
|
|
144
|
+
if (allowedChildren.has(node.name) === false) {
|
|
145
|
+
detachNodeFromParent(node, parentNode);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const allowedAttributes = allowedAttributesPerElement.get(node.name);
|
|
152
|
+
const attributesDefaults = attributesDefaultsPerElement.get(node.name);
|
|
153
|
+
const computedParentStyle =
|
|
154
|
+
parentNode.type === 'element'
|
|
155
|
+
? computeStyle(stylesheet, parentNode)
|
|
156
|
+
: null;
|
|
157
|
+
|
|
158
|
+
// remove element's unknown attrs and attrs with default values
|
|
159
|
+
for (const [name, value] of Object.entries(node.attributes)) {
|
|
160
|
+
if (keepDataAttrs && name.startsWith('data-')) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (keepAriaAttrs && name.startsWith('aria-')) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (keepRoleAttr && name === 'role') {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
// skip xmlns attribute
|
|
170
|
+
if (name === 'xmlns') {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
// skip namespaced attributes except xml:* and xlink:*
|
|
174
|
+
if (name.includes(':')) {
|
|
175
|
+
const [prefix] = name.split(':');
|
|
176
|
+
if (prefix !== 'xml' && prefix !== 'xlink') {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (
|
|
182
|
+
unknownAttrs &&
|
|
183
|
+
allowedAttributes &&
|
|
184
|
+
allowedAttributes.has(name) === false
|
|
185
|
+
) {
|
|
186
|
+
delete node.attributes[name];
|
|
187
|
+
}
|
|
188
|
+
if (
|
|
189
|
+
defaultAttrs &&
|
|
190
|
+
node.attributes.id == null &&
|
|
191
|
+
attributesDefaults &&
|
|
192
|
+
attributesDefaults.get(name) === value
|
|
193
|
+
) {
|
|
194
|
+
// keep defaults if parent has own or inherited style
|
|
195
|
+
if (
|
|
196
|
+
computedParentStyle == null ||
|
|
197
|
+
computedParentStyle[name] == null
|
|
198
|
+
) {
|
|
199
|
+
delete node.attributes[name];
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (uselessOverrides && node.attributes.id == null) {
|
|
203
|
+
const style =
|
|
204
|
+
computedParentStyle == null ? null : computedParentStyle[name];
|
|
205
|
+
if (
|
|
206
|
+
presentationNonInheritableGroupAttrs.includes(name) === false &&
|
|
207
|
+
style != null &&
|
|
208
|
+
style.type === 'static' &&
|
|
209
|
+
style.value === value
|
|
210
|
+
) {
|
|
211
|
+
delete node.attributes[name];
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
exports.type = 'visitor';
|
|
4
|
+
exports.name = 'removeUnusedNS';
|
|
5
|
+
exports.active = true;
|
|
6
|
+
exports.description = 'removes unused namespaces declaration';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Remove unused namespaces declaration from svg element
|
|
10
|
+
* which are not used in elements or attributes
|
|
11
|
+
*
|
|
12
|
+
* @author Kir Belevich
|
|
13
|
+
*
|
|
14
|
+
* @type {import('../lib/types').Plugin<void>}
|
|
15
|
+
*/
|
|
16
|
+
exports.fn = () => {
|
|
17
|
+
/**
|
|
18
|
+
* @type {Set<string>}
|
|
19
|
+
*/
|
|
20
|
+
const unusedNamespaces = new Set();
|
|
21
|
+
return {
|
|
22
|
+
element: {
|
|
23
|
+
enter: (node, parentNode) => {
|
|
24
|
+
// collect all namespaces from svg element
|
|
25
|
+
// (such as xmlns:xlink="http://www.w3.org/1999/xlink")
|
|
26
|
+
if (node.name === 'svg' && parentNode.type === 'root') {
|
|
27
|
+
for (const name of Object.keys(node.attributes)) {
|
|
28
|
+
if (name.startsWith('xmlns:')) {
|
|
29
|
+
const local = name.slice('xmlns:'.length);
|
|
30
|
+
unusedNamespaces.add(local);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (unusedNamespaces.size !== 0) {
|
|
35
|
+
// preserve namespace used in nested elements names
|
|
36
|
+
if (node.name.includes(':')) {
|
|
37
|
+
const [ns] = node.name.split(':');
|
|
38
|
+
if (unusedNamespaces.has(ns)) {
|
|
39
|
+
unusedNamespaces.delete(ns);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// preserve namespace used in nested elements attributes
|
|
43
|
+
for (const name of Object.keys(node.attributes)) {
|
|
44
|
+
if (name.includes(':')) {
|
|
45
|
+
const [ns] = name.split(':');
|
|
46
|
+
unusedNamespaces.delete(ns);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
exit: (node, parentNode) => {
|
|
52
|
+
// remove unused namespace attributes from svg element
|
|
53
|
+
if (node.name === 'svg' && parentNode.type === 'root') {
|
|
54
|
+
for (const name of unusedNamespaces) {
|
|
55
|
+
delete node.attributes[`xmlns:${name}`];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('../lib/types').XastElement} XastElement
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { detachNodeFromParent } = require('../lib/xast.js');
|
|
8
|
+
const { elemsGroups } = require('./_collections.js');
|
|
9
|
+
|
|
10
|
+
exports.type = 'visitor';
|
|
11
|
+
exports.name = 'removeUselessDefs';
|
|
12
|
+
exports.active = true;
|
|
13
|
+
exports.description = 'removes elements in <defs> without id';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Removes content of defs and properties that aren't rendered directly without ids.
|
|
17
|
+
*
|
|
18
|
+
* @author Lev Solntsev
|
|
19
|
+
*
|
|
20
|
+
* @type {import('../lib/types').Plugin<void>}
|
|
21
|
+
*/
|
|
22
|
+
exports.fn = () => {
|
|
23
|
+
return {
|
|
24
|
+
element: {
|
|
25
|
+
enter: (node, parentNode) => {
|
|
26
|
+
if (node.name === 'defs') {
|
|
27
|
+
/**
|
|
28
|
+
* @type {Array<XastElement>}
|
|
29
|
+
*/
|
|
30
|
+
const usefulNodes = [];
|
|
31
|
+
collectUsefulNodes(node, usefulNodes);
|
|
32
|
+
if (usefulNodes.length === 0) {
|
|
33
|
+
detachNodeFromParent(node, parentNode);
|
|
34
|
+
}
|
|
35
|
+
// TODO remove in SVGO 3
|
|
36
|
+
for (const usefulNode of usefulNodes) {
|
|
37
|
+
// @ts-ignore parentNode is legacy
|
|
38
|
+
usefulNode.parentNode = node;
|
|
39
|
+
}
|
|
40
|
+
node.children = usefulNodes;
|
|
41
|
+
} else if (
|
|
42
|
+
elemsGroups.nonRendering.includes(node.name) &&
|
|
43
|
+
node.attributes.id == null
|
|
44
|
+
) {
|
|
45
|
+
detachNodeFromParent(node, parentNode);
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @type {(node: XastElement, usefulNodes: Array<XastElement>) => void}
|
|
54
|
+
*/
|
|
55
|
+
const collectUsefulNodes = (node, usefulNodes) => {
|
|
56
|
+
for (const child of node.children) {
|
|
57
|
+
if (child.type === 'element') {
|
|
58
|
+
if (child.attributes.id != null || child.name === 'style') {
|
|
59
|
+
usefulNodes.push(child);
|
|
60
|
+
} else {
|
|
61
|
+
collectUsefulNodes(child, usefulNodes);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { visit, visitSkip, detachNodeFromParent } = require('../lib/xast.js');
|
|
4
|
+
const { collectStylesheet, computeStyle } = require('../lib/style.js');
|
|
5
|
+
const { elemsGroups } = require('./_collections.js');
|
|
6
|
+
|
|
7
|
+
exports.type = 'visitor';
|
|
8
|
+
exports.name = 'removeUselessStrokeAndFill';
|
|
9
|
+
exports.active = true;
|
|
10
|
+
exports.description = 'removes useless stroke and fill attributes';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Remove useless stroke and fill attrs.
|
|
14
|
+
*
|
|
15
|
+
* @author Kir Belevich
|
|
16
|
+
*
|
|
17
|
+
* @type {import('../lib/types').Plugin<{
|
|
18
|
+
* stroke?: boolean,
|
|
19
|
+
* fill?: boolean,
|
|
20
|
+
* removeNone?: boolean
|
|
21
|
+
* }>}
|
|
22
|
+
*/
|
|
23
|
+
exports.fn = (root, params) => {
|
|
24
|
+
const {
|
|
25
|
+
stroke: removeStroke = true,
|
|
26
|
+
fill: removeFill = true,
|
|
27
|
+
removeNone = false,
|
|
28
|
+
} = params;
|
|
29
|
+
|
|
30
|
+
// style and script elements deoptimise this plugin
|
|
31
|
+
let hasStyleOrScript = false;
|
|
32
|
+
visit(root, {
|
|
33
|
+
element: {
|
|
34
|
+
enter: (node) => {
|
|
35
|
+
if (node.name === 'style' || node.name === 'script') {
|
|
36
|
+
hasStyleOrScript = true;
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
if (hasStyleOrScript) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const stylesheet = collectStylesheet(root);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
element: {
|
|
49
|
+
enter: (node, parentNode) => {
|
|
50
|
+
// id attribute deoptimise the whole subtree
|
|
51
|
+
if (node.attributes.id != null) {
|
|
52
|
+
return visitSkip;
|
|
53
|
+
}
|
|
54
|
+
if (elemsGroups.shape.includes(node.name) == false) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const computedStyle = computeStyle(stylesheet, node);
|
|
58
|
+
const stroke = computedStyle.stroke;
|
|
59
|
+
const strokeOpacity = computedStyle['stroke-opacity'];
|
|
60
|
+
const strokeWidth = computedStyle['stroke-width'];
|
|
61
|
+
const markerEnd = computedStyle['marker-end'];
|
|
62
|
+
const fill = computedStyle.fill;
|
|
63
|
+
const fillOpacity = computedStyle['fill-opacity'];
|
|
64
|
+
const computedParentStyle =
|
|
65
|
+
parentNode.type === 'element'
|
|
66
|
+
? computeStyle(stylesheet, parentNode)
|
|
67
|
+
: null;
|
|
68
|
+
const parentStroke =
|
|
69
|
+
computedParentStyle == null ? null : computedParentStyle.stroke;
|
|
70
|
+
|
|
71
|
+
// remove stroke*
|
|
72
|
+
if (removeStroke) {
|
|
73
|
+
if (
|
|
74
|
+
stroke == null ||
|
|
75
|
+
(stroke.type === 'static' && stroke.value == 'none') ||
|
|
76
|
+
(strokeOpacity != null &&
|
|
77
|
+
strokeOpacity.type === 'static' &&
|
|
78
|
+
strokeOpacity.value === '0') ||
|
|
79
|
+
(strokeWidth != null &&
|
|
80
|
+
strokeWidth.type === 'static' &&
|
|
81
|
+
strokeWidth.value === '0')
|
|
82
|
+
) {
|
|
83
|
+
// stroke-width may affect the size of marker-end
|
|
84
|
+
// marker is not visible when stroke-width is 0
|
|
85
|
+
if (
|
|
86
|
+
(strokeWidth != null &&
|
|
87
|
+
strokeWidth.type === 'static' &&
|
|
88
|
+
strokeWidth.value === '0') ||
|
|
89
|
+
markerEnd == null
|
|
90
|
+
) {
|
|
91
|
+
for (const name of Object.keys(node.attributes)) {
|
|
92
|
+
if (name.startsWith('stroke')) {
|
|
93
|
+
delete node.attributes[name];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// set explicit none to not inherit from parent
|
|
97
|
+
if (
|
|
98
|
+
parentStroke != null &&
|
|
99
|
+
parentStroke.type === 'static' &&
|
|
100
|
+
parentStroke.value !== 'none'
|
|
101
|
+
) {
|
|
102
|
+
node.attributes.stroke = 'none';
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// remove fill*
|
|
109
|
+
if (removeFill) {
|
|
110
|
+
if (
|
|
111
|
+
(fill != null && fill.type === 'static' && fill.value === 'none') ||
|
|
112
|
+
(fillOpacity != null &&
|
|
113
|
+
fillOpacity.type === 'static' &&
|
|
114
|
+
fillOpacity.value === '0')
|
|
115
|
+
) {
|
|
116
|
+
for (const name of Object.keys(node.attributes)) {
|
|
117
|
+
if (name.startsWith('fill-')) {
|
|
118
|
+
delete node.attributes[name];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (
|
|
122
|
+
fill == null ||
|
|
123
|
+
(fill.type === 'static' && fill.value !== 'none')
|
|
124
|
+
) {
|
|
125
|
+
node.attributes.fill = 'none';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (removeNone) {
|
|
131
|
+
if (
|
|
132
|
+
(stroke == null || node.attributes.stroke === 'none') &&
|
|
133
|
+
((fill != null &&
|
|
134
|
+
fill.type === 'static' &&
|
|
135
|
+
fill.value === 'none') ||
|
|
136
|
+
node.attributes.fill === 'none')
|
|
137
|
+
) {
|
|
138
|
+
detachNodeFromParent(node, parentNode);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
exports.type = 'visitor';
|
|
4
|
+
exports.name = 'removeViewBox';
|
|
5
|
+
exports.active = true;
|
|
6
|
+
exports.description = 'removes viewBox attribute when possible';
|
|
7
|
+
|
|
8
|
+
const viewBoxElems = ['svg', 'pattern', 'symbol'];
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Remove viewBox attr which coincides with a width/height box.
|
|
12
|
+
*
|
|
13
|
+
* @see https://www.w3.org/TR/SVG11/coords.html#ViewBoxAttribute
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* <svg width="100" height="50" viewBox="0 0 100 50">
|
|
17
|
+
* ⬇
|
|
18
|
+
* <svg width="100" height="50">
|
|
19
|
+
*
|
|
20
|
+
* @author Kir Belevich
|
|
21
|
+
*
|
|
22
|
+
* @type {import('../lib/types').Plugin<void>}
|
|
23
|
+
*/
|
|
24
|
+
exports.fn = () => {
|
|
25
|
+
return {
|
|
26
|
+
element: {
|
|
27
|
+
enter: (node, parentNode) => {
|
|
28
|
+
if (
|
|
29
|
+
viewBoxElems.includes(node.name) &&
|
|
30
|
+
node.attributes.viewBox != null &&
|
|
31
|
+
node.attributes.width != null &&
|
|
32
|
+
node.attributes.height != null
|
|
33
|
+
) {
|
|
34
|
+
// TODO remove width/height for such case instead
|
|
35
|
+
if (node.name === 'svg' && parentNode.type !== 'root') {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const nums = node.attributes.viewBox.split(/[ ,]+/g);
|
|
39
|
+
if (
|
|
40
|
+
nums[0] === '0' &&
|
|
41
|
+
nums[1] === '0' &&
|
|
42
|
+
node.attributes.width.replace(/px$/, '') === nums[2] && // could use parseFloat too
|
|
43
|
+
node.attributes.height.replace(/px$/, '') === nums[3]
|
|
44
|
+
) {
|
|
45
|
+
delete node.attributes.viewBox;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
exports.name = 'removeXMLNS';
|
|
4
|
+
|
|
5
|
+
exports.type = 'perItem';
|
|
6
|
+
|
|
7
|
+
exports.active = false;
|
|
8
|
+
|
|
9
|
+
exports.description =
|
|
10
|
+
'removes xmlns attribute (for inline svg, disabled by default)';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Remove the xmlns attribute when present.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* <svg viewBox="0 0 100 50" xmlns="http://www.w3.org/2000/svg">
|
|
17
|
+
* ↓
|
|
18
|
+
* <svg viewBox="0 0 100 50">
|
|
19
|
+
*
|
|
20
|
+
* @param {Object} item current iteration item
|
|
21
|
+
* @return {Boolean} if true, xmlns will be filtered out
|
|
22
|
+
*
|
|
23
|
+
* @author Ricardo Tomasi
|
|
24
|
+
*/
|
|
25
|
+
exports.fn = function (item) {
|
|
26
|
+
if (item.type === 'element' && item.name === 'svg') {
|
|
27
|
+
delete item.attributes.xmlns;
|
|
28
|
+
delete item.attributes['xmlns:xlink'];
|
|
29
|
+
}
|
|
30
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { detachNodeFromParent } = require('../lib/xast.js');
|
|
4
|
+
|
|
5
|
+
exports.name = 'removeXMLProcInst';
|
|
6
|
+
exports.type = 'visitor';
|
|
7
|
+
exports.active = true;
|
|
8
|
+
exports.description = 'removes XML processing instructions';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Remove XML Processing Instruction.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* <?xml version="1.0" encoding="utf-8"?>
|
|
15
|
+
*
|
|
16
|
+
* @author Kir Belevich
|
|
17
|
+
*
|
|
18
|
+
* @type {import('../lib/types').Plugin<void>}
|
|
19
|
+
*/
|
|
20
|
+
exports.fn = () => {
|
|
21
|
+
return {
|
|
22
|
+
instruction: {
|
|
23
|
+
enter: (node, parentNode) => {
|
|
24
|
+
if (node.name === 'xml') {
|
|
25
|
+
detachNodeFromParent(node, parentNode);
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
};
|