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,379 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const regTransformTypes = /matrix|translate|scale|rotate|skewX|skewY/;
|
|
4
|
+
const regTransformSplit =
|
|
5
|
+
/\s*(matrix|translate|scale|rotate|skewX|skewY)\s*\(\s*(.+?)\s*\)[\s,]*/;
|
|
6
|
+
const regNumericValues = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {{ name: string, data: Array<number> }} TransformItem
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Convert transform string to JS representation.
|
|
14
|
+
*
|
|
15
|
+
* @type {(transformString: string) => Array<TransformItem>}
|
|
16
|
+
*/
|
|
17
|
+
exports.transform2js = (transformString) => {
|
|
18
|
+
// JS representation of the transform data
|
|
19
|
+
/**
|
|
20
|
+
* @type {Array<TransformItem>}
|
|
21
|
+
*/
|
|
22
|
+
const transforms = [];
|
|
23
|
+
// current transform context
|
|
24
|
+
/**
|
|
25
|
+
* @type {null | TransformItem}
|
|
26
|
+
*/
|
|
27
|
+
let current = null;
|
|
28
|
+
// split value into ['', 'translate', '10 50', '', 'scale', '2', '', 'rotate', '-45', '']
|
|
29
|
+
for (const item of transformString.split(regTransformSplit)) {
|
|
30
|
+
var num;
|
|
31
|
+
if (item) {
|
|
32
|
+
// if item is a translate function
|
|
33
|
+
if (regTransformTypes.test(item)) {
|
|
34
|
+
// then collect it and change current context
|
|
35
|
+
current = { name: item, data: [] };
|
|
36
|
+
transforms.push(current);
|
|
37
|
+
// else if item is data
|
|
38
|
+
} else {
|
|
39
|
+
// then split it into [10, 50] and collect as context.data
|
|
40
|
+
// eslint-disable-next-line no-cond-assign
|
|
41
|
+
while ((num = regNumericValues.exec(item))) {
|
|
42
|
+
num = Number(num);
|
|
43
|
+
if (current != null) {
|
|
44
|
+
current.data.push(num);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// return empty array if broken transform (no data)
|
|
51
|
+
return current == null || current.data.length == 0 ? [] : transforms;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Multiply transforms into one.
|
|
56
|
+
*
|
|
57
|
+
* @type {(transforms: Array<TransformItem>) => TransformItem}
|
|
58
|
+
*/
|
|
59
|
+
exports.transformsMultiply = (transforms) => {
|
|
60
|
+
// convert transforms objects to the matrices
|
|
61
|
+
const matrixData = transforms.map((transform) => {
|
|
62
|
+
if (transform.name === 'matrix') {
|
|
63
|
+
return transform.data;
|
|
64
|
+
}
|
|
65
|
+
return transformToMatrix(transform);
|
|
66
|
+
});
|
|
67
|
+
// multiply all matrices into one
|
|
68
|
+
const matrixTransform = {
|
|
69
|
+
name: 'matrix',
|
|
70
|
+
data:
|
|
71
|
+
matrixData.length > 0 ? matrixData.reduce(multiplyTransformMatrices) : [],
|
|
72
|
+
};
|
|
73
|
+
return matrixTransform;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* math utilities in radians.
|
|
78
|
+
*/
|
|
79
|
+
const mth = {
|
|
80
|
+
/**
|
|
81
|
+
* @type {(deg: number) => number}
|
|
82
|
+
*/
|
|
83
|
+
rad: (deg) => {
|
|
84
|
+
return (deg * Math.PI) / 180;
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @type {(rad: number) => number}
|
|
89
|
+
*/
|
|
90
|
+
deg: (rad) => {
|
|
91
|
+
return (rad * 180) / Math.PI;
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @type {(deg: number) => number}
|
|
96
|
+
*/
|
|
97
|
+
cos: (deg) => {
|
|
98
|
+
return Math.cos(mth.rad(deg));
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @type {(val: number, floatPrecision: number) => number}
|
|
103
|
+
*/
|
|
104
|
+
acos: (val, floatPrecision) => {
|
|
105
|
+
return Number(mth.deg(Math.acos(val)).toFixed(floatPrecision));
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @type {(deg: number) => number}
|
|
110
|
+
*/
|
|
111
|
+
sin: (deg) => {
|
|
112
|
+
return Math.sin(mth.rad(deg));
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @type {(val: number, floatPrecision: number) => number}
|
|
117
|
+
*/
|
|
118
|
+
asin: (val, floatPrecision) => {
|
|
119
|
+
return Number(mth.deg(Math.asin(val)).toFixed(floatPrecision));
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @type {(deg: number) => number}
|
|
124
|
+
*/
|
|
125
|
+
tan: (deg) => {
|
|
126
|
+
return Math.tan(mth.rad(deg));
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @type {(val: number, floatPrecision: number) => number}
|
|
131
|
+
*/
|
|
132
|
+
atan: (val, floatPrecision) => {
|
|
133
|
+
return Number(mth.deg(Math.atan(val)).toFixed(floatPrecision));
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @typedef {{
|
|
139
|
+
* convertToShorts: boolean,
|
|
140
|
+
* floatPrecision: number,
|
|
141
|
+
* transformPrecision: number,
|
|
142
|
+
* matrixToTransform: boolean,
|
|
143
|
+
* shortTranslate: boolean,
|
|
144
|
+
* shortScale: boolean,
|
|
145
|
+
* shortRotate: boolean,
|
|
146
|
+
* removeUseless: boolean,
|
|
147
|
+
* collapseIntoOne: boolean,
|
|
148
|
+
* leadingZero: boolean,
|
|
149
|
+
* negativeExtraSpace: boolean,
|
|
150
|
+
* }} TransformParams
|
|
151
|
+
*/
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Decompose matrix into simple transforms. See
|
|
155
|
+
* https://frederic-wang.fr/decomposition-of-2d-transform-matrices.html
|
|
156
|
+
*
|
|
157
|
+
* @type {(transform: TransformItem, params: TransformParams) => Array<TransformItem>}
|
|
158
|
+
*/
|
|
159
|
+
exports.matrixToTransform = (transform, params) => {
|
|
160
|
+
let floatPrecision = params.floatPrecision;
|
|
161
|
+
let data = transform.data;
|
|
162
|
+
let transforms = [];
|
|
163
|
+
let sx = Number(
|
|
164
|
+
Math.hypot(data[0], data[1]).toFixed(params.transformPrecision)
|
|
165
|
+
);
|
|
166
|
+
let sy = Number(
|
|
167
|
+
((data[0] * data[3] - data[1] * data[2]) / sx).toFixed(
|
|
168
|
+
params.transformPrecision
|
|
169
|
+
)
|
|
170
|
+
);
|
|
171
|
+
let colsSum = data[0] * data[2] + data[1] * data[3];
|
|
172
|
+
let rowsSum = data[0] * data[1] + data[2] * data[3];
|
|
173
|
+
let scaleBefore = rowsSum != 0 || sx == sy;
|
|
174
|
+
|
|
175
|
+
// [..., ..., ..., ..., tx, ty] → translate(tx, ty)
|
|
176
|
+
if (data[4] || data[5]) {
|
|
177
|
+
transforms.push({
|
|
178
|
+
name: 'translate',
|
|
179
|
+
data: data.slice(4, data[5] ? 6 : 5),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// [sx, 0, tan(a)·sy, sy, 0, 0] → skewX(a)·scale(sx, sy)
|
|
184
|
+
if (!data[1] && data[2]) {
|
|
185
|
+
transforms.push({
|
|
186
|
+
name: 'skewX',
|
|
187
|
+
data: [mth.atan(data[2] / sy, floatPrecision)],
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// [sx, sx·tan(a), 0, sy, 0, 0] → skewY(a)·scale(sx, sy)
|
|
191
|
+
} else if (data[1] && !data[2]) {
|
|
192
|
+
transforms.push({
|
|
193
|
+
name: 'skewY',
|
|
194
|
+
data: [mth.atan(data[1] / data[0], floatPrecision)],
|
|
195
|
+
});
|
|
196
|
+
sx = data[0];
|
|
197
|
+
sy = data[3];
|
|
198
|
+
|
|
199
|
+
// [sx·cos(a), sx·sin(a), sy·-sin(a), sy·cos(a), x, y] → rotate(a[, cx, cy])·(scale or skewX) or
|
|
200
|
+
// [sx·cos(a), sy·sin(a), sx·-sin(a), sy·cos(a), x, y] → scale(sx, sy)·rotate(a[, cx, cy]) (if !scaleBefore)
|
|
201
|
+
} else if (!colsSum || (sx == 1 && sy == 1) || !scaleBefore) {
|
|
202
|
+
if (!scaleBefore) {
|
|
203
|
+
sx = (data[0] < 0 ? -1 : 1) * Math.hypot(data[0], data[2]);
|
|
204
|
+
sy = (data[3] < 0 ? -1 : 1) * Math.hypot(data[1], data[3]);
|
|
205
|
+
transforms.push({ name: 'scale', data: [sx, sy] });
|
|
206
|
+
}
|
|
207
|
+
var angle = Math.min(Math.max(-1, data[0] / sx), 1),
|
|
208
|
+
rotate = [
|
|
209
|
+
mth.acos(angle, floatPrecision) *
|
|
210
|
+
((scaleBefore ? 1 : sy) * data[1] < 0 ? -1 : 1),
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
if (rotate[0]) transforms.push({ name: 'rotate', data: rotate });
|
|
214
|
+
|
|
215
|
+
if (rowsSum && colsSum)
|
|
216
|
+
transforms.push({
|
|
217
|
+
name: 'skewX',
|
|
218
|
+
data: [mth.atan(colsSum / (sx * sx), floatPrecision)],
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// rotate(a, cx, cy) can consume translate() within optional arguments cx, cy (rotation point)
|
|
222
|
+
if (rotate[0] && (data[4] || data[5])) {
|
|
223
|
+
transforms.shift();
|
|
224
|
+
var cos = data[0] / sx,
|
|
225
|
+
sin = data[1] / (scaleBefore ? sx : sy),
|
|
226
|
+
x = data[4] * (scaleBefore ? 1 : sy),
|
|
227
|
+
y = data[5] * (scaleBefore ? 1 : sx),
|
|
228
|
+
denom =
|
|
229
|
+
(Math.pow(1 - cos, 2) + Math.pow(sin, 2)) *
|
|
230
|
+
(scaleBefore ? 1 : sx * sy);
|
|
231
|
+
rotate.push(((1 - cos) * x - sin * y) / denom);
|
|
232
|
+
rotate.push(((1 - cos) * y + sin * x) / denom);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Too many transformations, return original matrix if it isn't just a scale/translate
|
|
236
|
+
} else if (data[1] || data[2]) {
|
|
237
|
+
return [transform];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if ((scaleBefore && (sx != 1 || sy != 1)) || !transforms.length)
|
|
241
|
+
transforms.push({
|
|
242
|
+
name: 'scale',
|
|
243
|
+
data: sx == sy ? [sx] : [sx, sy],
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
return transforms;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Convert transform to the matrix data.
|
|
251
|
+
*
|
|
252
|
+
* @type {(transform: TransformItem) => Array<number> }
|
|
253
|
+
*/
|
|
254
|
+
const transformToMatrix = (transform) => {
|
|
255
|
+
if (transform.name === 'matrix') {
|
|
256
|
+
return transform.data;
|
|
257
|
+
}
|
|
258
|
+
switch (transform.name) {
|
|
259
|
+
case 'translate':
|
|
260
|
+
// [1, 0, 0, 1, tx, ty]
|
|
261
|
+
return [1, 0, 0, 1, transform.data[0], transform.data[1] || 0];
|
|
262
|
+
case 'scale':
|
|
263
|
+
// [sx, 0, 0, sy, 0, 0]
|
|
264
|
+
return [
|
|
265
|
+
transform.data[0],
|
|
266
|
+
0,
|
|
267
|
+
0,
|
|
268
|
+
transform.data[1] || transform.data[0],
|
|
269
|
+
0,
|
|
270
|
+
0,
|
|
271
|
+
];
|
|
272
|
+
case 'rotate':
|
|
273
|
+
// [cos(a), sin(a), -sin(a), cos(a), x, y]
|
|
274
|
+
var cos = mth.cos(transform.data[0]),
|
|
275
|
+
sin = mth.sin(transform.data[0]),
|
|
276
|
+
cx = transform.data[1] || 0,
|
|
277
|
+
cy = transform.data[2] || 0;
|
|
278
|
+
return [
|
|
279
|
+
cos,
|
|
280
|
+
sin,
|
|
281
|
+
-sin,
|
|
282
|
+
cos,
|
|
283
|
+
(1 - cos) * cx + sin * cy,
|
|
284
|
+
(1 - cos) * cy - sin * cx,
|
|
285
|
+
];
|
|
286
|
+
case 'skewX':
|
|
287
|
+
// [1, 0, tan(a), 1, 0, 0]
|
|
288
|
+
return [1, 0, mth.tan(transform.data[0]), 1, 0, 0];
|
|
289
|
+
case 'skewY':
|
|
290
|
+
// [1, tan(a), 0, 1, 0, 0]
|
|
291
|
+
return [1, mth.tan(transform.data[0]), 0, 1, 0, 0];
|
|
292
|
+
default:
|
|
293
|
+
throw Error(`Unknown transform ${transform.name}`);
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Applies transformation to an arc. To do so, we represent ellipse as a matrix, multiply it
|
|
299
|
+
* by the transformation matrix and use a singular value decomposition to represent in a form
|
|
300
|
+
* rotate(θ)·scale(a b)·rotate(φ). This gives us new ellipse params a, b and θ.
|
|
301
|
+
* SVD is being done with the formulae provided by Wolffram|Alpha (svd {{m0, m2}, {m1, m3}})
|
|
302
|
+
*
|
|
303
|
+
* @type {(
|
|
304
|
+
* cursor: [x: number, y: number],
|
|
305
|
+
* arc: Array<number>,
|
|
306
|
+
* transform: Array<number>
|
|
307
|
+
* ) => Array<number>}
|
|
308
|
+
*/
|
|
309
|
+
exports.transformArc = (cursor, arc, transform) => {
|
|
310
|
+
const x = arc[5] - cursor[0];
|
|
311
|
+
const y = arc[6] - cursor[1];
|
|
312
|
+
let a = arc[0];
|
|
313
|
+
let b = arc[1];
|
|
314
|
+
const rot = (arc[2] * Math.PI) / 180;
|
|
315
|
+
const cos = Math.cos(rot);
|
|
316
|
+
const sin = Math.sin(rot);
|
|
317
|
+
// skip if radius is 0
|
|
318
|
+
if (a > 0 && b > 0) {
|
|
319
|
+
let h =
|
|
320
|
+
Math.pow(x * cos + y * sin, 2) / (4 * a * a) +
|
|
321
|
+
Math.pow(y * cos - x * sin, 2) / (4 * b * b);
|
|
322
|
+
if (h > 1) {
|
|
323
|
+
h = Math.sqrt(h);
|
|
324
|
+
a *= h;
|
|
325
|
+
b *= h;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
const ellipse = [a * cos, a * sin, -b * sin, b * cos, 0, 0];
|
|
329
|
+
const m = multiplyTransformMatrices(transform, ellipse);
|
|
330
|
+
// Decompose the new ellipse matrix
|
|
331
|
+
const lastCol = m[2] * m[2] + m[3] * m[3];
|
|
332
|
+
const squareSum = m[0] * m[0] + m[1] * m[1] + lastCol;
|
|
333
|
+
const root =
|
|
334
|
+
Math.hypot(m[0] - m[3], m[1] + m[2]) * Math.hypot(m[0] + m[3], m[1] - m[2]);
|
|
335
|
+
|
|
336
|
+
if (!root) {
|
|
337
|
+
// circle
|
|
338
|
+
arc[0] = arc[1] = Math.sqrt(squareSum / 2);
|
|
339
|
+
arc[2] = 0;
|
|
340
|
+
} else {
|
|
341
|
+
const majorAxisSqr = (squareSum + root) / 2;
|
|
342
|
+
const minorAxisSqr = (squareSum - root) / 2;
|
|
343
|
+
const major = Math.abs(majorAxisSqr - lastCol) > 1e-6;
|
|
344
|
+
const sub = (major ? majorAxisSqr : minorAxisSqr) - lastCol;
|
|
345
|
+
const rowsSum = m[0] * m[2] + m[1] * m[3];
|
|
346
|
+
const term1 = m[0] * sub + m[2] * rowsSum;
|
|
347
|
+
const term2 = m[1] * sub + m[3] * rowsSum;
|
|
348
|
+
arc[0] = Math.sqrt(majorAxisSqr);
|
|
349
|
+
arc[1] = Math.sqrt(minorAxisSqr);
|
|
350
|
+
arc[2] =
|
|
351
|
+
(((major ? term2 < 0 : term1 > 0) ? -1 : 1) *
|
|
352
|
+
Math.acos((major ? term1 : term2) / Math.hypot(term1, term2)) *
|
|
353
|
+
180) /
|
|
354
|
+
Math.PI;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (transform[0] < 0 !== transform[3] < 0) {
|
|
358
|
+
// Flip the sweep flag if coordinates are being flipped horizontally XOR vertically
|
|
359
|
+
arc[4] = 1 - arc[4];
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return arc;
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Multiply transformation matrices.
|
|
367
|
+
*
|
|
368
|
+
* @type {(a: Array<number>, b: Array<number>) => Array<number>}
|
|
369
|
+
*/
|
|
370
|
+
const multiplyTransformMatrices = (a, b) => {
|
|
371
|
+
return [
|
|
372
|
+
a[0] * b[0] + a[2] * b[1],
|
|
373
|
+
a[1] * b[0] + a[3] * b[1],
|
|
374
|
+
a[0] * b[2] + a[2] * b[3],
|
|
375
|
+
a[1] * b[2] + a[3] * b[3],
|
|
376
|
+
a[0] * b[4] + a[2] * b[5] + a[4],
|
|
377
|
+
a[1] * b[4] + a[3] * b[5] + a[5],
|
|
378
|
+
];
|
|
379
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
exports.name = 'addAttributesToSVGElement';
|
|
4
|
+
exports.type = 'visitor';
|
|
5
|
+
exports.active = false;
|
|
6
|
+
exports.description = 'adds attributes to an outer <svg> element';
|
|
7
|
+
|
|
8
|
+
var ENOCLS = `Error in plugin "addAttributesToSVGElement": absent parameters.
|
|
9
|
+
It should have a list of "attributes" or one "attribute".
|
|
10
|
+
Config example:
|
|
11
|
+
|
|
12
|
+
plugins: [
|
|
13
|
+
{
|
|
14
|
+
name: 'addAttributesToSVGElement',
|
|
15
|
+
params: {
|
|
16
|
+
attribute: "mySvg"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
plugins: [
|
|
22
|
+
{
|
|
23
|
+
name: 'addAttributesToSVGElement',
|
|
24
|
+
params: {
|
|
25
|
+
attributes: ["mySvg", "size-big"]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
plugins: [
|
|
31
|
+
{
|
|
32
|
+
name: 'addAttributesToSVGElement',
|
|
33
|
+
params: {
|
|
34
|
+
attributes: [
|
|
35
|
+
{
|
|
36
|
+
focusable: false
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
'data-image': icon
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Add attributes to an outer <svg> element. Example config:
|
|
49
|
+
*
|
|
50
|
+
* @author April Arcus
|
|
51
|
+
*
|
|
52
|
+
* @type {import('../lib/types').Plugin<{
|
|
53
|
+
* attribute?: string | Record<string, null | string>,
|
|
54
|
+
* attributes?: Array<string | Record<string, null | string>>
|
|
55
|
+
* }>}
|
|
56
|
+
*/
|
|
57
|
+
exports.fn = (root, params) => {
|
|
58
|
+
if (!Array.isArray(params.attributes) && !params.attribute) {
|
|
59
|
+
console.error(ENOCLS);
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const attributes = params.attributes || [params.attribute];
|
|
63
|
+
return {
|
|
64
|
+
element: {
|
|
65
|
+
enter: (node, parentNode) => {
|
|
66
|
+
if (node.name === 'svg' && parentNode.type === 'root') {
|
|
67
|
+
for (const attribute of attributes) {
|
|
68
|
+
if (typeof attribute === 'string') {
|
|
69
|
+
if (node.attributes[attribute] == null) {
|
|
70
|
+
// @ts-ignore disallow explicit nullable attribute value
|
|
71
|
+
node.attributes[attribute] = undefined;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (typeof attribute === 'object') {
|
|
75
|
+
for (const key of Object.keys(attribute)) {
|
|
76
|
+
if (node.attributes[key] == null) {
|
|
77
|
+
// @ts-ignore disallow explicit nullable attribute value
|
|
78
|
+
node.attributes[key] = attribute[key];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
exports.name = 'addClassesToSVGElement';
|
|
4
|
+
exports.type = 'visitor';
|
|
5
|
+
exports.active = false;
|
|
6
|
+
exports.description = 'adds classnames to an outer <svg> element';
|
|
7
|
+
|
|
8
|
+
var ENOCLS = `Error in plugin "addClassesToSVGElement": absent parameters.
|
|
9
|
+
It should have a list of classes in "classNames" or one "className".
|
|
10
|
+
Config example:
|
|
11
|
+
|
|
12
|
+
plugins: [
|
|
13
|
+
{
|
|
14
|
+
name: "addClassesToSVGElement",
|
|
15
|
+
params: {
|
|
16
|
+
className: "mySvg"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
plugins: [
|
|
22
|
+
{
|
|
23
|
+
name: "addClassesToSVGElement",
|
|
24
|
+
params: {
|
|
25
|
+
classNames: ["mySvg", "size-big"]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Add classnames to an outer <svg> element. Example config:
|
|
33
|
+
*
|
|
34
|
+
* plugins: [
|
|
35
|
+
* {
|
|
36
|
+
* name: "addClassesToSVGElement",
|
|
37
|
+
* params: {
|
|
38
|
+
* className: "mySvg"
|
|
39
|
+
* }
|
|
40
|
+
* }
|
|
41
|
+
* ]
|
|
42
|
+
*
|
|
43
|
+
* plugins: [
|
|
44
|
+
* {
|
|
45
|
+
* name: "addClassesToSVGElement",
|
|
46
|
+
* params: {
|
|
47
|
+
* classNames: ["mySvg", "size-big"]
|
|
48
|
+
* }
|
|
49
|
+
* }
|
|
50
|
+
* ]
|
|
51
|
+
*
|
|
52
|
+
* @author April Arcus
|
|
53
|
+
*
|
|
54
|
+
* @type {import('../lib/types').Plugin<{
|
|
55
|
+
* className?: string,
|
|
56
|
+
* classNames?: Array<string>
|
|
57
|
+
* }>}
|
|
58
|
+
*/
|
|
59
|
+
exports.fn = (root, params) => {
|
|
60
|
+
if (
|
|
61
|
+
!(Array.isArray(params.classNames) && params.classNames.some(String)) &&
|
|
62
|
+
!params.className
|
|
63
|
+
) {
|
|
64
|
+
console.error(ENOCLS);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const classNames = params.classNames || [params.className];
|
|
68
|
+
return {
|
|
69
|
+
element: {
|
|
70
|
+
enter: (node, parentNode) => {
|
|
71
|
+
if (node.name === 'svg' && parentNode.type === 'root') {
|
|
72
|
+
const classList = new Set(
|
|
73
|
+
node.attributes.class == null
|
|
74
|
+
? null
|
|
75
|
+
: node.attributes.class.split(' ')
|
|
76
|
+
);
|
|
77
|
+
for (const className of classNames) {
|
|
78
|
+
if (className != null) {
|
|
79
|
+
classList.add(className);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
node.attributes.class = Array.from(classList).join(' ');
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
exports.name = 'cleanupAttrs';
|
|
4
|
+
exports.type = 'visitor';
|
|
5
|
+
exports.active = true;
|
|
6
|
+
exports.description =
|
|
7
|
+
'cleanups attributes from newlines, trailing and repeating spaces';
|
|
8
|
+
|
|
9
|
+
const regNewlinesNeedSpace = /(\S)\r?\n(\S)/g;
|
|
10
|
+
const regNewlines = /\r?\n/g;
|
|
11
|
+
const regSpaces = /\s{2,}/g;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Cleanup attributes values from newlines, trailing and repeating spaces.
|
|
15
|
+
*
|
|
16
|
+
* @author Kir Belevich
|
|
17
|
+
*
|
|
18
|
+
* @type {import('../lib/types').Plugin<{
|
|
19
|
+
* newlines?: boolean,
|
|
20
|
+
* trim?: boolean,
|
|
21
|
+
* spaces?: boolean
|
|
22
|
+
* }>}
|
|
23
|
+
*/
|
|
24
|
+
exports.fn = (root, params) => {
|
|
25
|
+
const { newlines = true, trim = true, spaces = true } = params;
|
|
26
|
+
return {
|
|
27
|
+
element: {
|
|
28
|
+
enter: (node) => {
|
|
29
|
+
for (const name of Object.keys(node.attributes)) {
|
|
30
|
+
if (newlines) {
|
|
31
|
+
// new line which requires a space instead of themselve
|
|
32
|
+
node.attributes[name] = node.attributes[name].replace(
|
|
33
|
+
regNewlinesNeedSpace,
|
|
34
|
+
(match, p1, p2) => p1 + ' ' + p2
|
|
35
|
+
);
|
|
36
|
+
// simple new line
|
|
37
|
+
node.attributes[name] = node.attributes[name].replace(
|
|
38
|
+
regNewlines,
|
|
39
|
+
''
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
if (trim) {
|
|
43
|
+
node.attributes[name] = node.attributes[name].trim();
|
|
44
|
+
}
|
|
45
|
+
if (spaces) {
|
|
46
|
+
node.attributes[name] = node.attributes[name].replace(
|
|
47
|
+
regSpaces,
|
|
48
|
+
' '
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { visit } = require('../lib/xast.js');
|
|
4
|
+
|
|
5
|
+
exports.type = 'visitor';
|
|
6
|
+
exports.name = 'cleanupEnableBackground';
|
|
7
|
+
exports.active = true;
|
|
8
|
+
exports.description =
|
|
9
|
+
'remove or cleanup enable-background attribute when possible';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Remove or cleanup enable-background attr which coincides with a width/height box.
|
|
13
|
+
*
|
|
14
|
+
* @see https://www.w3.org/TR/SVG11/filters.html#EnableBackgroundProperty
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* <svg width="100" height="50" enable-background="new 0 0 100 50">
|
|
18
|
+
* ⬇
|
|
19
|
+
* <svg width="100" height="50">
|
|
20
|
+
*
|
|
21
|
+
* @author Kir Belevich
|
|
22
|
+
*
|
|
23
|
+
* @type {import('../lib/types').Plugin<void>}
|
|
24
|
+
*/
|
|
25
|
+
exports.fn = (root) => {
|
|
26
|
+
const regEnableBackground =
|
|
27
|
+
/^new\s0\s0\s([-+]?\d*\.?\d+([eE][-+]?\d+)?)\s([-+]?\d*\.?\d+([eE][-+]?\d+)?)$/;
|
|
28
|
+
|
|
29
|
+
let hasFilter = false;
|
|
30
|
+
visit(root, {
|
|
31
|
+
element: {
|
|
32
|
+
enter: (node) => {
|
|
33
|
+
if (node.name === 'filter') {
|
|
34
|
+
hasFilter = true;
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
element: {
|
|
42
|
+
enter: (node) => {
|
|
43
|
+
if (node.attributes['enable-background'] == null) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (hasFilter) {
|
|
47
|
+
if (
|
|
48
|
+
(node.name === 'svg' ||
|
|
49
|
+
node.name === 'mask' ||
|
|
50
|
+
node.name === 'pattern') &&
|
|
51
|
+
node.attributes.width != null &&
|
|
52
|
+
node.attributes.height != null
|
|
53
|
+
) {
|
|
54
|
+
const match =
|
|
55
|
+
node.attributes['enable-background'].match(regEnableBackground);
|
|
56
|
+
if (
|
|
57
|
+
match != null &&
|
|
58
|
+
node.attributes.width === match[1] &&
|
|
59
|
+
node.attributes.height === match[3]
|
|
60
|
+
) {
|
|
61
|
+
if (node.name === 'svg') {
|
|
62
|
+
delete node.attributes['enable-background'];
|
|
63
|
+
} else {
|
|
64
|
+
node.attributes['enable-background'] = 'new';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
//we don't need 'enable-background' if we have no filters
|
|
70
|
+
delete node.attributes['enable-background'];
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
};
|