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
package/lib/style.js
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('css-tree-v2').Rule} CsstreeRule
|
|
5
|
+
* @typedef {import('./types').Specificity} Specificity
|
|
6
|
+
* @typedef {import('./types').Stylesheet} Stylesheet
|
|
7
|
+
* @typedef {import('./types').StylesheetRule} StylesheetRule
|
|
8
|
+
* @typedef {import('./types').StylesheetDeclaration} StylesheetDeclaration
|
|
9
|
+
* @typedef {import('./types').ComputedStyles} ComputedStyles
|
|
10
|
+
* @typedef {import('./types').XastRoot} XastRoot
|
|
11
|
+
* @typedef {import('./types').XastElement} XastElement
|
|
12
|
+
* @typedef {import('./types').XastParent} XastParent
|
|
13
|
+
* @typedef {import('./types').XastChild} XastChild
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const stable = require('stable');
|
|
17
|
+
const csstree = require('css-tree-v2');
|
|
18
|
+
// @ts-ignore not defined in @types/csso
|
|
19
|
+
const specificity = require('csso/lib/restructure/prepare/specificity');
|
|
20
|
+
const { visit, matches } = require('./xast.js');
|
|
21
|
+
const {
|
|
22
|
+
attrsGroups,
|
|
23
|
+
inheritableAttrs,
|
|
24
|
+
presentationNonInheritableGroupAttrs,
|
|
25
|
+
} = require('../plugins/_collections.js');
|
|
26
|
+
|
|
27
|
+
// @ts-ignore not defined in @types/csstree
|
|
28
|
+
const csstreeWalkSkip = csstree.walk.skip;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @type {(ruleNode: CsstreeRule, dynamic: boolean) => StylesheetRule}
|
|
32
|
+
*/
|
|
33
|
+
const parseRule = (ruleNode, dynamic) => {
|
|
34
|
+
let selectors;
|
|
35
|
+
let selectorsSpecificity;
|
|
36
|
+
/**
|
|
37
|
+
* @type {Array<StylesheetDeclaration>}
|
|
38
|
+
*/
|
|
39
|
+
const declarations = [];
|
|
40
|
+
csstree.walk(ruleNode, (cssNode) => {
|
|
41
|
+
if (cssNode.type === 'SelectorList') {
|
|
42
|
+
// compute specificity from original node to consider pseudo classes
|
|
43
|
+
selectorsSpecificity = specificity(cssNode);
|
|
44
|
+
const newSelectorsNode = csstree.clone(cssNode);
|
|
45
|
+
csstree.walk(newSelectorsNode, (pseudoClassNode, item, list) => {
|
|
46
|
+
if (pseudoClassNode.type === 'PseudoClassSelector') {
|
|
47
|
+
dynamic = true;
|
|
48
|
+
list.remove(item);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
selectors = csstree.generate(newSelectorsNode);
|
|
52
|
+
return csstreeWalkSkip;
|
|
53
|
+
}
|
|
54
|
+
if (cssNode.type === 'Declaration') {
|
|
55
|
+
declarations.push({
|
|
56
|
+
name: cssNode.property,
|
|
57
|
+
value: csstree.generate(cssNode.value),
|
|
58
|
+
important: cssNode.important === true,
|
|
59
|
+
});
|
|
60
|
+
return csstreeWalkSkip;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
if (selectors == null || selectorsSpecificity == null) {
|
|
64
|
+
throw Error('assert');
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
dynamic,
|
|
68
|
+
selectors,
|
|
69
|
+
specificity: selectorsSpecificity,
|
|
70
|
+
declarations,
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @type {(css: string, dynamic: boolean) => Array<StylesheetRule>}
|
|
76
|
+
*/
|
|
77
|
+
const parseStylesheet = (css, dynamic) => {
|
|
78
|
+
/**
|
|
79
|
+
* @type {Array<StylesheetRule>}
|
|
80
|
+
*/
|
|
81
|
+
const rules = [];
|
|
82
|
+
const ast = csstree.parse(css, {
|
|
83
|
+
parseValue: false,
|
|
84
|
+
parseAtrulePrelude: false,
|
|
85
|
+
});
|
|
86
|
+
csstree.walk(ast, (cssNode) => {
|
|
87
|
+
if (cssNode.type === 'Rule') {
|
|
88
|
+
rules.push(parseRule(cssNode, dynamic || false));
|
|
89
|
+
return csstreeWalkSkip;
|
|
90
|
+
}
|
|
91
|
+
if (cssNode.type === 'Atrule') {
|
|
92
|
+
if (cssNode.name === 'keyframes') {
|
|
93
|
+
return csstreeWalkSkip;
|
|
94
|
+
}
|
|
95
|
+
csstree.walk(cssNode, (ruleNode) => {
|
|
96
|
+
if (ruleNode.type === 'Rule') {
|
|
97
|
+
rules.push(parseRule(ruleNode, dynamic || true));
|
|
98
|
+
return csstreeWalkSkip;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
return csstreeWalkSkip;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
return rules;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @type {(css: string) => Array<StylesheetDeclaration>}
|
|
109
|
+
*/
|
|
110
|
+
const parseStyleDeclarations = (css) => {
|
|
111
|
+
/**
|
|
112
|
+
* @type {Array<StylesheetDeclaration>}
|
|
113
|
+
*/
|
|
114
|
+
const declarations = [];
|
|
115
|
+
const ast = csstree.parse(css, {
|
|
116
|
+
context: 'declarationList',
|
|
117
|
+
parseValue: false,
|
|
118
|
+
});
|
|
119
|
+
csstree.walk(ast, (cssNode) => {
|
|
120
|
+
if (cssNode.type === 'Declaration') {
|
|
121
|
+
declarations.push({
|
|
122
|
+
name: cssNode.property,
|
|
123
|
+
value: csstree.generate(cssNode.value),
|
|
124
|
+
important: cssNode.important === true,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
return declarations;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @type {(stylesheet: Stylesheet, node: XastElement) => ComputedStyles}
|
|
133
|
+
*/
|
|
134
|
+
const computeOwnStyle = (stylesheet, node) => {
|
|
135
|
+
/**
|
|
136
|
+
* @type {ComputedStyles}
|
|
137
|
+
*/
|
|
138
|
+
const computedStyle = {};
|
|
139
|
+
const importantStyles = new Map();
|
|
140
|
+
|
|
141
|
+
// collect attributes
|
|
142
|
+
for (const [name, value] of Object.entries(node.attributes)) {
|
|
143
|
+
if (attrsGroups.presentation.includes(name)) {
|
|
144
|
+
computedStyle[name] = { type: 'static', inherited: false, value };
|
|
145
|
+
importantStyles.set(name, false);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// collect matching rules
|
|
150
|
+
for (const { selectors, declarations, dynamic } of stylesheet.rules) {
|
|
151
|
+
if (matches(node, selectors)) {
|
|
152
|
+
for (const { name, value, important } of declarations) {
|
|
153
|
+
const computed = computedStyle[name];
|
|
154
|
+
if (computed && computed.type === 'dynamic') {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (dynamic) {
|
|
158
|
+
computedStyle[name] = { type: 'dynamic', inherited: false };
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (
|
|
162
|
+
computed == null ||
|
|
163
|
+
important === true ||
|
|
164
|
+
importantStyles.get(name) === false
|
|
165
|
+
) {
|
|
166
|
+
computedStyle[name] = { type: 'static', inherited: false, value };
|
|
167
|
+
importantStyles.set(name, important);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// collect inline styles
|
|
174
|
+
const styleDeclarations =
|
|
175
|
+
node.attributes.style == null
|
|
176
|
+
? []
|
|
177
|
+
: parseStyleDeclarations(node.attributes.style);
|
|
178
|
+
for (const { name, value, important } of styleDeclarations) {
|
|
179
|
+
const computed = computedStyle[name];
|
|
180
|
+
if (computed && computed.type === 'dynamic') {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (
|
|
184
|
+
computed == null ||
|
|
185
|
+
important === true ||
|
|
186
|
+
importantStyles.get(name) === false
|
|
187
|
+
) {
|
|
188
|
+
computedStyle[name] = { type: 'static', inherited: false, value };
|
|
189
|
+
importantStyles.set(name, important);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return computedStyle;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Compares two selector specificities.
|
|
198
|
+
* extracted from https://github.com/keeganstreet/specificity/blob/master/specificity.js#L211
|
|
199
|
+
*
|
|
200
|
+
* @type {(a: Specificity, b: Specificity) => number}
|
|
201
|
+
*/
|
|
202
|
+
const compareSpecificity = (a, b) => {
|
|
203
|
+
for (var i = 0; i < 4; i += 1) {
|
|
204
|
+
if (a[i] < b[i]) {
|
|
205
|
+
return -1;
|
|
206
|
+
} else if (a[i] > b[i]) {
|
|
207
|
+
return 1;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return 0;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* @type {(root: XastRoot) => Stylesheet}
|
|
216
|
+
*/
|
|
217
|
+
const collectStylesheet = (root) => {
|
|
218
|
+
/**
|
|
219
|
+
* @type {Array<StylesheetRule>}
|
|
220
|
+
*/
|
|
221
|
+
const rules = [];
|
|
222
|
+
/**
|
|
223
|
+
* @type {Map<XastElement, XastParent>}
|
|
224
|
+
*/
|
|
225
|
+
const parents = new Map();
|
|
226
|
+
visit(root, {
|
|
227
|
+
element: {
|
|
228
|
+
enter: (node, parentNode) => {
|
|
229
|
+
// store parents
|
|
230
|
+
parents.set(node, parentNode);
|
|
231
|
+
// find and parse all styles
|
|
232
|
+
if (node.name === 'style') {
|
|
233
|
+
const dynamic =
|
|
234
|
+
node.attributes.media != null && node.attributes.media !== 'all';
|
|
235
|
+
if (
|
|
236
|
+
node.attributes.type == null ||
|
|
237
|
+
node.attributes.type === '' ||
|
|
238
|
+
node.attributes.type === 'text/css'
|
|
239
|
+
) {
|
|
240
|
+
const children = node.children;
|
|
241
|
+
for (const child of children) {
|
|
242
|
+
if (child.type === 'text' || child.type === 'cdata') {
|
|
243
|
+
rules.push(...parseStylesheet(child.value, dynamic));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
// sort by selectors specificity
|
|
252
|
+
stable.inplace(rules, (a, b) =>
|
|
253
|
+
compareSpecificity(a.specificity, b.specificity)
|
|
254
|
+
);
|
|
255
|
+
return { rules, parents };
|
|
256
|
+
};
|
|
257
|
+
exports.collectStylesheet = collectStylesheet;
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* @type {(stylesheet: Stylesheet, node: XastElement) => ComputedStyles}
|
|
261
|
+
*/
|
|
262
|
+
const computeStyle = (stylesheet, node) => {
|
|
263
|
+
const { parents } = stylesheet;
|
|
264
|
+
// collect inherited styles
|
|
265
|
+
const computedStyles = computeOwnStyle(stylesheet, node);
|
|
266
|
+
let parent = parents.get(node);
|
|
267
|
+
while (parent != null && parent.type !== 'root') {
|
|
268
|
+
const inheritedStyles = computeOwnStyle(stylesheet, parent);
|
|
269
|
+
for (const [name, computed] of Object.entries(inheritedStyles)) {
|
|
270
|
+
if (
|
|
271
|
+
computedStyles[name] == null &&
|
|
272
|
+
// ignore not inheritable styles
|
|
273
|
+
inheritableAttrs.includes(name) === true &&
|
|
274
|
+
presentationNonInheritableGroupAttrs.includes(name) === false
|
|
275
|
+
) {
|
|
276
|
+
computedStyles[name] = { ...computed, inherited: true };
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
parent = parents.get(parent);
|
|
280
|
+
}
|
|
281
|
+
return computedStyles;
|
|
282
|
+
};
|
|
283
|
+
exports.computeStyle = computeStyle;
|