first
This commit is contained in:
335
node_modules/svgo/plugins/_applyTransforms.js
generated
vendored
Normal file
335
node_modules/svgo/plugins/_applyTransforms.js
generated
vendored
Normal file
@ -0,0 +1,335 @@
|
||||
'use strict';
|
||||
|
||||
// TODO implement as separate plugin
|
||||
|
||||
const {
|
||||
transformsMultiply,
|
||||
transform2js,
|
||||
transformArc,
|
||||
} = require('./_transforms.js');
|
||||
const { removeLeadingZero } = require('../lib/svgo/tools.js');
|
||||
const { referencesProps, attrsGroupsDefaults } = require('./_collections.js');
|
||||
|
||||
const regNumericValues = /[-+]?(\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;
|
||||
const defaultStrokeWidth = attrsGroupsDefaults.presentation['stroke-width'];
|
||||
|
||||
/**
|
||||
* Apply transformation(s) to the Path data.
|
||||
*
|
||||
* @param {Object} elem current element
|
||||
* @param {Array} path input path data
|
||||
* @param {Object} params whether to apply transforms to stroked lines and transform precision (used for stroke width)
|
||||
* @return {Array} output path data
|
||||
*/
|
||||
const applyTransforms = (elem, pathData, params) => {
|
||||
// if there are no 'stroke' attr and references to other objects such as
|
||||
// gradiends or clip-path which are also subjects to transform.
|
||||
if (
|
||||
elem.attributes.transform == null ||
|
||||
elem.attributes.transform === '' ||
|
||||
// styles are not considered when applying transform
|
||||
// can be fixed properly with new style engine
|
||||
elem.attributes.style != null ||
|
||||
Object.entries(elem.attributes).some(
|
||||
([name, value]) =>
|
||||
referencesProps.includes(name) && value.includes('url(')
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const matrix = transformsMultiply(transform2js(elem.attributes.transform));
|
||||
const stroke = elem.computedAttr('stroke');
|
||||
const id = elem.computedAttr('id');
|
||||
const transformPrecision = params.transformPrecision;
|
||||
|
||||
if (stroke && stroke != 'none') {
|
||||
if (
|
||||
!params.applyTransformsStroked ||
|
||||
((matrix.data[0] != matrix.data[3] ||
|
||||
matrix.data[1] != -matrix.data[2]) &&
|
||||
(matrix.data[0] != -matrix.data[3] || matrix.data[1] != matrix.data[2]))
|
||||
)
|
||||
return;
|
||||
|
||||
// "stroke-width" should be inside the part with ID, otherwise it can be overrided in <use>
|
||||
if (id) {
|
||||
let idElem = elem;
|
||||
let hasStrokeWidth = false;
|
||||
|
||||
do {
|
||||
if (idElem.attributes['stroke-width']) {
|
||||
hasStrokeWidth = true;
|
||||
}
|
||||
} while (
|
||||
idElem.attributes.id !== id &&
|
||||
!hasStrokeWidth &&
|
||||
(idElem = idElem.parentNode)
|
||||
);
|
||||
|
||||
if (!hasStrokeWidth) return;
|
||||
}
|
||||
|
||||
const scale = +Math.sqrt(
|
||||
matrix.data[0] * matrix.data[0] + matrix.data[1] * matrix.data[1]
|
||||
).toFixed(transformPrecision);
|
||||
|
||||
if (scale !== 1) {
|
||||
const strokeWidth =
|
||||
elem.computedAttr('stroke-width') || defaultStrokeWidth;
|
||||
|
||||
if (
|
||||
elem.attributes['vector-effect'] == null ||
|
||||
elem.attributes['vector-effect'] !== 'non-scaling-stroke'
|
||||
) {
|
||||
if (elem.attributes['stroke-width'] != null) {
|
||||
elem.attributes['stroke-width'] = elem.attributes['stroke-width']
|
||||
.trim()
|
||||
.replace(regNumericValues, (num) => removeLeadingZero(num * scale));
|
||||
} else {
|
||||
elem.attributes['stroke-width'] = strokeWidth.replace(
|
||||
regNumericValues,
|
||||
(num) => removeLeadingZero(num * scale)
|
||||
);
|
||||
}
|
||||
|
||||
if (elem.attributes['stroke-dashoffset'] != null) {
|
||||
elem.attributes['stroke-dashoffset'] = elem.attributes[
|
||||
'stroke-dashoffset'
|
||||
]
|
||||
.trim()
|
||||
.replace(regNumericValues, (num) => removeLeadingZero(num * scale));
|
||||
}
|
||||
|
||||
if (elem.attributes['stroke-dasharray'] != null) {
|
||||
elem.attributes['stroke-dasharray'] = elem.attributes[
|
||||
'stroke-dasharray'
|
||||
]
|
||||
.trim()
|
||||
.replace(regNumericValues, (num) => removeLeadingZero(num * scale));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (id) {
|
||||
// Stroke and stroke-width can be redefined with <use>
|
||||
return;
|
||||
}
|
||||
|
||||
applyMatrixToPathData(pathData, matrix.data);
|
||||
|
||||
// remove transform attr
|
||||
delete elem.attributes.transform;
|
||||
|
||||
return;
|
||||
};
|
||||
exports.applyTransforms = applyTransforms;
|
||||
|
||||
const transformAbsolutePoint = (matrix, x, y) => {
|
||||
const newX = matrix[0] * x + matrix[2] * y + matrix[4];
|
||||
const newY = matrix[1] * x + matrix[3] * y + matrix[5];
|
||||
return [newX, newY];
|
||||
};
|
||||
|
||||
const transformRelativePoint = (matrix, x, y) => {
|
||||
const newX = matrix[0] * x + matrix[2] * y;
|
||||
const newY = matrix[1] * x + matrix[3] * y;
|
||||
return [newX, newY];
|
||||
};
|
||||
|
||||
const applyMatrixToPathData = (pathData, matrix) => {
|
||||
const start = [0, 0];
|
||||
const cursor = [0, 0];
|
||||
|
||||
for (const pathItem of pathData) {
|
||||
let { command, args } = pathItem;
|
||||
|
||||
// moveto (x y)
|
||||
if (command === 'M') {
|
||||
cursor[0] = args[0];
|
||||
cursor[1] = args[1];
|
||||
start[0] = cursor[0];
|
||||
start[1] = cursor[1];
|
||||
const [x, y] = transformAbsolutePoint(matrix, args[0], args[1]);
|
||||
args[0] = x;
|
||||
args[1] = y;
|
||||
}
|
||||
if (command === 'm') {
|
||||
cursor[0] += args[0];
|
||||
cursor[1] += args[1];
|
||||
start[0] = cursor[0];
|
||||
start[1] = cursor[1];
|
||||
const [x, y] = transformRelativePoint(matrix, args[0], args[1]);
|
||||
args[0] = x;
|
||||
args[1] = y;
|
||||
}
|
||||
|
||||
// horizontal lineto (x)
|
||||
// convert to lineto to handle two-dimentional transforms
|
||||
if (command === 'H') {
|
||||
command = 'L';
|
||||
args = [args[0], cursor[1]];
|
||||
}
|
||||
if (command === 'h') {
|
||||
command = 'l';
|
||||
args = [args[0], 0];
|
||||
}
|
||||
|
||||
// vertical lineto (y)
|
||||
// convert to lineto to handle two-dimentional transforms
|
||||
if (command === 'V') {
|
||||
command = 'L';
|
||||
args = [cursor[0], args[0]];
|
||||
}
|
||||
if (command === 'v') {
|
||||
command = 'l';
|
||||
args = [0, args[0]];
|
||||
}
|
||||
|
||||
// lineto (x y)
|
||||
if (command === 'L') {
|
||||
cursor[0] = args[0];
|
||||
cursor[1] = args[1];
|
||||
const [x, y] = transformAbsolutePoint(matrix, args[0], args[1]);
|
||||
args[0] = x;
|
||||
args[1] = y;
|
||||
}
|
||||
if (command === 'l') {
|
||||
cursor[0] += args[0];
|
||||
cursor[1] += args[1];
|
||||
const [x, y] = transformRelativePoint(matrix, args[0], args[1]);
|
||||
args[0] = x;
|
||||
args[1] = y;
|
||||
}
|
||||
|
||||
// curveto (x1 y1 x2 y2 x y)
|
||||
if (command === 'C') {
|
||||
cursor[0] = args[4];
|
||||
cursor[1] = args[5];
|
||||
const [x1, y1] = transformAbsolutePoint(matrix, args[0], args[1]);
|
||||
const [x2, y2] = transformAbsolutePoint(matrix, args[2], args[3]);
|
||||
const [x, y] = transformAbsolutePoint(matrix, args[4], args[5]);
|
||||
args[0] = x1;
|
||||
args[1] = y1;
|
||||
args[2] = x2;
|
||||
args[3] = y2;
|
||||
args[4] = x;
|
||||
args[5] = y;
|
||||
}
|
||||
if (command === 'c') {
|
||||
cursor[0] += args[4];
|
||||
cursor[1] += args[5];
|
||||
const [x1, y1] = transformRelativePoint(matrix, args[0], args[1]);
|
||||
const [x2, y2] = transformRelativePoint(matrix, args[2], args[3]);
|
||||
const [x, y] = transformRelativePoint(matrix, args[4], args[5]);
|
||||
args[0] = x1;
|
||||
args[1] = y1;
|
||||
args[2] = x2;
|
||||
args[3] = y2;
|
||||
args[4] = x;
|
||||
args[5] = y;
|
||||
}
|
||||
|
||||
// smooth curveto (x2 y2 x y)
|
||||
if (command === 'S') {
|
||||
cursor[0] = args[2];
|
||||
cursor[1] = args[3];
|
||||
const [x2, y2] = transformAbsolutePoint(matrix, args[0], args[1]);
|
||||
const [x, y] = transformAbsolutePoint(matrix, args[2], args[3]);
|
||||
args[0] = x2;
|
||||
args[1] = y2;
|
||||
args[2] = x;
|
||||
args[3] = y;
|
||||
}
|
||||
if (command === 's') {
|
||||
cursor[0] += args[2];
|
||||
cursor[1] += args[3];
|
||||
const [x2, y2] = transformRelativePoint(matrix, args[0], args[1]);
|
||||
const [x, y] = transformRelativePoint(matrix, args[2], args[3]);
|
||||
args[0] = x2;
|
||||
args[1] = y2;
|
||||
args[2] = x;
|
||||
args[3] = y;
|
||||
}
|
||||
|
||||
// quadratic Bézier curveto (x1 y1 x y)
|
||||
if (command === 'Q') {
|
||||
cursor[0] = args[2];
|
||||
cursor[1] = args[3];
|
||||
const [x1, y1] = transformAbsolutePoint(matrix, args[0], args[1]);
|
||||
const [x, y] = transformAbsolutePoint(matrix, args[2], args[3]);
|
||||
args[0] = x1;
|
||||
args[1] = y1;
|
||||
args[2] = x;
|
||||
args[3] = y;
|
||||
}
|
||||
if (command === 'q') {
|
||||
cursor[0] += args[2];
|
||||
cursor[1] += args[3];
|
||||
const [x1, y1] = transformRelativePoint(matrix, args[0], args[1]);
|
||||
const [x, y] = transformRelativePoint(matrix, args[2], args[3]);
|
||||
args[0] = x1;
|
||||
args[1] = y1;
|
||||
args[2] = x;
|
||||
args[3] = y;
|
||||
}
|
||||
|
||||
// smooth quadratic Bézier curveto (x y)
|
||||
if (command === 'T') {
|
||||
cursor[0] = args[0];
|
||||
cursor[1] = args[1];
|
||||
const [x, y] = transformAbsolutePoint(matrix, args[0], args[1]);
|
||||
args[0] = x;
|
||||
args[1] = y;
|
||||
}
|
||||
if (command === 't') {
|
||||
cursor[0] += args[0];
|
||||
cursor[1] += args[1];
|
||||
const [x, y] = transformRelativePoint(matrix, args[0], args[1]);
|
||||
args[0] = x;
|
||||
args[1] = y;
|
||||
}
|
||||
|
||||
// elliptical arc (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
|
||||
if (command === 'A') {
|
||||
transformArc(cursor, args, matrix);
|
||||
cursor[0] = args[5];
|
||||
cursor[1] = args[6];
|
||||
// reduce number of digits in rotation angle
|
||||
if (Math.abs(args[2]) > 80) {
|
||||
const a = args[0];
|
||||
const rotation = args[2];
|
||||
args[0] = args[1];
|
||||
args[1] = a;
|
||||
args[2] = rotation + (rotation > 0 ? -90 : 90);
|
||||
}
|
||||
const [x, y] = transformAbsolutePoint(matrix, args[5], args[6]);
|
||||
args[5] = x;
|
||||
args[6] = y;
|
||||
}
|
||||
if (command === 'a') {
|
||||
transformArc([0, 0], args, matrix);
|
||||
cursor[0] += args[5];
|
||||
cursor[1] += args[6];
|
||||
// reduce number of digits in rotation angle
|
||||
if (Math.abs(args[2]) > 80) {
|
||||
const a = args[0];
|
||||
const rotation = args[2];
|
||||
args[0] = args[1];
|
||||
args[1] = a;
|
||||
args[2] = rotation + (rotation > 0 ? -90 : 90);
|
||||
}
|
||||
const [x, y] = transformRelativePoint(matrix, args[5], args[6]);
|
||||
args[5] = x;
|
||||
args[6] = y;
|
||||
}
|
||||
|
||||
// closepath
|
||||
if (command === 'z' || command === 'Z') {
|
||||
cursor[0] = start[0];
|
||||
cursor[1] = start[1];
|
||||
}
|
||||
|
||||
pathItem.command = command;
|
||||
pathItem.args = args;
|
||||
}
|
||||
};
|
2168
node_modules/svgo/plugins/_collections.js
generated
vendored
Normal file
2168
node_modules/svgo/plugins/_collections.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
816
node_modules/svgo/plugins/_path.js
generated
vendored
Normal file
816
node_modules/svgo/plugins/_path.js
generated
vendored
Normal file
@ -0,0 +1,816 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @typedef {import('../lib/types').XastElement} XastElement
|
||||
* @typedef {import('../lib/types').PathDataItem} PathDataItem
|
||||
*/
|
||||
|
||||
const { parsePathData, stringifyPathData } = require('../lib/path.js');
|
||||
|
||||
/**
|
||||
* @type {[number, number]}
|
||||
*/
|
||||
var prevCtrlPoint;
|
||||
|
||||
/**
|
||||
* Convert path string to JS representation.
|
||||
*
|
||||
* @type {(path: XastElement) => Array<PathDataItem>}
|
||||
*/
|
||||
const path2js = (path) => {
|
||||
// @ts-ignore legacy
|
||||
if (path.pathJS) return path.pathJS;
|
||||
/**
|
||||
* @type {Array<PathDataItem>}
|
||||
*/
|
||||
const pathData = []; // JS representation of the path data
|
||||
const newPathData = parsePathData(path.attributes.d);
|
||||
for (const { command, args } of newPathData) {
|
||||
pathData.push({ command, args });
|
||||
}
|
||||
// First moveto is actually absolute. Subsequent coordinates were separated above.
|
||||
if (pathData.length && pathData[0].command == 'm') {
|
||||
pathData[0].command = 'M';
|
||||
}
|
||||
// @ts-ignore legacy
|
||||
path.pathJS = pathData;
|
||||
return pathData;
|
||||
};
|
||||
exports.path2js = path2js;
|
||||
|
||||
/**
|
||||
* Convert relative Path data to absolute.
|
||||
*
|
||||
* @type {(data: Array<PathDataItem>) => Array<PathDataItem>}
|
||||
*
|
||||
*/
|
||||
const convertRelativeToAbsolute = (data) => {
|
||||
/**
|
||||
* @type {Array<PathDataItem>}
|
||||
*/
|
||||
const newData = [];
|
||||
let start = [0, 0];
|
||||
let cursor = [0, 0];
|
||||
|
||||
for (let { command, args } of data) {
|
||||
args = args.slice();
|
||||
|
||||
// moveto (x y)
|
||||
if (command === 'm') {
|
||||
args[0] += cursor[0];
|
||||
args[1] += cursor[1];
|
||||
command = 'M';
|
||||
}
|
||||
if (command === 'M') {
|
||||
cursor[0] = args[0];
|
||||
cursor[1] = args[1];
|
||||
start[0] = cursor[0];
|
||||
start[1] = cursor[1];
|
||||
}
|
||||
|
||||
// horizontal lineto (x)
|
||||
if (command === 'h') {
|
||||
args[0] += cursor[0];
|
||||
command = 'H';
|
||||
}
|
||||
if (command === 'H') {
|
||||
cursor[0] = args[0];
|
||||
}
|
||||
|
||||
// vertical lineto (y)
|
||||
if (command === 'v') {
|
||||
args[0] += cursor[1];
|
||||
command = 'V';
|
||||
}
|
||||
if (command === 'V') {
|
||||
cursor[1] = args[0];
|
||||
}
|
||||
|
||||
// lineto (x y)
|
||||
if (command === 'l') {
|
||||
args[0] += cursor[0];
|
||||
args[1] += cursor[1];
|
||||
command = 'L';
|
||||
}
|
||||
if (command === 'L') {
|
||||
cursor[0] = args[0];
|
||||
cursor[1] = args[1];
|
||||
}
|
||||
|
||||
// curveto (x1 y1 x2 y2 x y)
|
||||
if (command === 'c') {
|
||||
args[0] += cursor[0];
|
||||
args[1] += cursor[1];
|
||||
args[2] += cursor[0];
|
||||
args[3] += cursor[1];
|
||||
args[4] += cursor[0];
|
||||
args[5] += cursor[1];
|
||||
command = 'C';
|
||||
}
|
||||
if (command === 'C') {
|
||||
cursor[0] = args[4];
|
||||
cursor[1] = args[5];
|
||||
}
|
||||
|
||||
// smooth curveto (x2 y2 x y)
|
||||
if (command === 's') {
|
||||
args[0] += cursor[0];
|
||||
args[1] += cursor[1];
|
||||
args[2] += cursor[0];
|
||||
args[3] += cursor[1];
|
||||
command = 'S';
|
||||
}
|
||||
if (command === 'S') {
|
||||
cursor[0] = args[2];
|
||||
cursor[1] = args[3];
|
||||
}
|
||||
|
||||
// quadratic Bézier curveto (x1 y1 x y)
|
||||
if (command === 'q') {
|
||||
args[0] += cursor[0];
|
||||
args[1] += cursor[1];
|
||||
args[2] += cursor[0];
|
||||
args[3] += cursor[1];
|
||||
command = 'Q';
|
||||
}
|
||||
if (command === 'Q') {
|
||||
cursor[0] = args[2];
|
||||
cursor[1] = args[3];
|
||||
}
|
||||
|
||||
// smooth quadratic Bézier curveto (x y)
|
||||
if (command === 't') {
|
||||
args[0] += cursor[0];
|
||||
args[1] += cursor[1];
|
||||
command = 'T';
|
||||
}
|
||||
if (command === 'T') {
|
||||
cursor[0] = args[0];
|
||||
cursor[1] = args[1];
|
||||
}
|
||||
|
||||
// elliptical arc (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
|
||||
if (command === 'a') {
|
||||
args[5] += cursor[0];
|
||||
args[6] += cursor[1];
|
||||
command = 'A';
|
||||
}
|
||||
if (command === 'A') {
|
||||
cursor[0] = args[5];
|
||||
cursor[1] = args[6];
|
||||
}
|
||||
|
||||
// closepath
|
||||
if (command === 'z' || command === 'Z') {
|
||||
cursor[0] = start[0];
|
||||
cursor[1] = start[1];
|
||||
command = 'z';
|
||||
}
|
||||
|
||||
newData.push({ command, args });
|
||||
}
|
||||
return newData;
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {{ floatPrecision?: number, noSpaceAfterFlags?: boolean }} Js2PathParams
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convert path array to string.
|
||||
*
|
||||
* @type {(path: XastElement, data: Array<PathDataItem>, params: Js2PathParams) => void}
|
||||
*/
|
||||
exports.js2path = function (path, data, params) {
|
||||
// @ts-ignore legacy
|
||||
path.pathJS = data;
|
||||
|
||||
const pathData = [];
|
||||
for (const item of data) {
|
||||
// remove moveto commands which are followed by moveto commands
|
||||
if (
|
||||
pathData.length !== 0 &&
|
||||
(item.command === 'M' || item.command === 'm')
|
||||
) {
|
||||
const last = pathData[pathData.length - 1];
|
||||
if (last.command === 'M' || last.command === 'm') {
|
||||
pathData.pop();
|
||||
}
|
||||
}
|
||||
pathData.push({
|
||||
command: item.command,
|
||||
args: item.args,
|
||||
});
|
||||
}
|
||||
|
||||
path.attributes.d = stringifyPathData({
|
||||
pathData,
|
||||
precision: params.floatPrecision,
|
||||
disableSpaceAfterFlags: params.noSpaceAfterFlags,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {(dest: Array<number>, source: Array<number>) => Array<number>}
|
||||
*/
|
||||
function set(dest, source) {
|
||||
dest[0] = source[source.length - 2];
|
||||
dest[1] = source[source.length - 1];
|
||||
return dest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if two paths have an intersection by checking convex hulls
|
||||
* collision using Gilbert-Johnson-Keerthi distance algorithm
|
||||
* https://web.archive.org/web/20180822200027/http://entropyinteractive.com/2011/04/gjk-algorithm/
|
||||
*
|
||||
* @type {(path1: Array<PathDataItem>, path2: Array<PathDataItem>) => boolean}
|
||||
*/
|
||||
exports.intersects = function (path1, path2) {
|
||||
// Collect points of every subpath.
|
||||
const points1 = gatherPoints(convertRelativeToAbsolute(path1));
|
||||
const points2 = gatherPoints(convertRelativeToAbsolute(path2));
|
||||
|
||||
// Axis-aligned bounding box check.
|
||||
if (
|
||||
points1.maxX <= points2.minX ||
|
||||
points2.maxX <= points1.minX ||
|
||||
points1.maxY <= points2.minY ||
|
||||
points2.maxY <= points1.minY ||
|
||||
points1.list.every((set1) => {
|
||||
return points2.list.every((set2) => {
|
||||
return (
|
||||
set1.list[set1.maxX][0] <= set2.list[set2.minX][0] ||
|
||||
set2.list[set2.maxX][0] <= set1.list[set1.minX][0] ||
|
||||
set1.list[set1.maxY][1] <= set2.list[set2.minY][1] ||
|
||||
set2.list[set2.maxY][1] <= set1.list[set1.minY][1]
|
||||
);
|
||||
});
|
||||
})
|
||||
)
|
||||
return false;
|
||||
|
||||
// Get a convex hull from points of each subpath. Has the most complexity O(n·log n).
|
||||
const hullNest1 = points1.list.map(convexHull);
|
||||
const hullNest2 = points2.list.map(convexHull);
|
||||
|
||||
// Check intersection of every subpath of the first path with every subpath of the second.
|
||||
return hullNest1.some(function (hull1) {
|
||||
if (hull1.list.length < 3) return false;
|
||||
|
||||
return hullNest2.some(function (hull2) {
|
||||
if (hull2.list.length < 3) return false;
|
||||
|
||||
var simplex = [getSupport(hull1, hull2, [1, 0])], // create the initial simplex
|
||||
direction = minus(simplex[0]); // set the direction to point towards the origin
|
||||
|
||||
var iterations = 1e4; // infinite loop protection, 10 000 iterations is more than enough
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
if (iterations-- == 0) {
|
||||
console.error(
|
||||
'Error: infinite loop while processing mergePaths plugin.'
|
||||
);
|
||||
return true; // true is the safe value that means “do nothing with paths”
|
||||
}
|
||||
// add a new point
|
||||
simplex.push(getSupport(hull1, hull2, direction));
|
||||
// see if the new point was on the correct side of the origin
|
||||
if (dot(direction, simplex[simplex.length - 1]) <= 0) return false;
|
||||
// process the simplex
|
||||
if (processSimplex(simplex, direction)) return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @type {(a: Point, b: Point, direction: Array<number>) => Array<number>}
|
||||
*/
|
||||
function getSupport(a, b, direction) {
|
||||
return sub(supportPoint(a, direction), supportPoint(b, minus(direction)));
|
||||
}
|
||||
|
||||
// Computes farthest polygon point in particular direction.
|
||||
// Thanks to knowledge of min/max x and y coordinates we can choose a quadrant to search in.
|
||||
// Since we're working on convex hull, the dot product is increasing until we find the farthest point.
|
||||
/**
|
||||
* @type {(polygon: Point, direction: Array<number>) => Array<number>}
|
||||
*/
|
||||
function supportPoint(polygon, direction) {
|
||||
var index =
|
||||
direction[1] >= 0
|
||||
? direction[0] < 0
|
||||
? polygon.maxY
|
||||
: polygon.maxX
|
||||
: direction[0] < 0
|
||||
? polygon.minX
|
||||
: polygon.minY,
|
||||
max = -Infinity,
|
||||
value;
|
||||
while ((value = dot(polygon.list[index], direction)) > max) {
|
||||
max = value;
|
||||
index = ++index % polygon.list.length;
|
||||
}
|
||||
return polygon.list[(index || polygon.list.length) - 1];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {(simplex: Array<Array<number>>, direction: Array<number>) => boolean}
|
||||
*/
|
||||
function processSimplex(simplex, direction) {
|
||||
// we only need to handle to 1-simplex and 2-simplex
|
||||
if (simplex.length == 2) {
|
||||
// 1-simplex
|
||||
let a = simplex[1],
|
||||
b = simplex[0],
|
||||
AO = minus(simplex[1]),
|
||||
AB = sub(b, a);
|
||||
// AO is in the same direction as AB
|
||||
if (dot(AO, AB) > 0) {
|
||||
// get the vector perpendicular to AB facing O
|
||||
set(direction, orth(AB, a));
|
||||
} else {
|
||||
set(direction, AO);
|
||||
// only A remains in the simplex
|
||||
simplex.shift();
|
||||
}
|
||||
} else {
|
||||
// 2-simplex
|
||||
let a = simplex[2], // [a, b, c] = simplex
|
||||
b = simplex[1],
|
||||
c = simplex[0],
|
||||
AB = sub(b, a),
|
||||
AC = sub(c, a),
|
||||
AO = minus(a),
|
||||
ACB = orth(AB, AC), // the vector perpendicular to AB facing away from C
|
||||
ABC = orth(AC, AB); // the vector perpendicular to AC facing away from B
|
||||
|
||||
if (dot(ACB, AO) > 0) {
|
||||
if (dot(AB, AO) > 0) {
|
||||
// region 4
|
||||
set(direction, ACB);
|
||||
simplex.shift(); // simplex = [b, a]
|
||||
} else {
|
||||
// region 5
|
||||
set(direction, AO);
|
||||
simplex.splice(0, 2); // simplex = [a]
|
||||
}
|
||||
} else if (dot(ABC, AO) > 0) {
|
||||
if (dot(AC, AO) > 0) {
|
||||
// region 6
|
||||
set(direction, ABC);
|
||||
simplex.splice(1, 1); // simplex = [c, a]
|
||||
} else {
|
||||
// region 5 (again)
|
||||
set(direction, AO);
|
||||
simplex.splice(0, 2); // simplex = [a]
|
||||
}
|
||||
} // region 7
|
||||
else return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {(v: Array<number>) => Array<number>}
|
||||
*/
|
||||
function minus(v) {
|
||||
return [-v[0], -v[1]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {(v1: Array<number>, v2: Array<number>) => Array<number>}
|
||||
*/
|
||||
function sub(v1, v2) {
|
||||
return [v1[0] - v2[0], v1[1] - v2[1]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {(v1: Array<number>, v2: Array<number>) => number}
|
||||
*/
|
||||
function dot(v1, v2) {
|
||||
return v1[0] * v2[0] + v1[1] * v2[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {(v1: Array<number>, v2: Array<number>) => Array<number>}
|
||||
*/
|
||||
function orth(v, from) {
|
||||
var o = [-v[1], v[0]];
|
||||
return dot(o, minus(from)) < 0 ? minus(o) : o;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* list: Array<Array<number>>,
|
||||
* minX: number,
|
||||
* minY: number,
|
||||
* maxX: number,
|
||||
* maxY: number
|
||||
* }} Point
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* list: Array<Point>,
|
||||
* minX: number,
|
||||
* minY: number,
|
||||
* maxX: number,
|
||||
* maxY: number
|
||||
* }} Points
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {(pathData: Array<PathDataItem>) => Points}
|
||||
*/
|
||||
function gatherPoints(pathData) {
|
||||
/**
|
||||
* @type {Points}
|
||||
*/
|
||||
const points = { list: [], minX: 0, minY: 0, maxX: 0, maxY: 0 };
|
||||
|
||||
// Writes data about the extreme points on each axle
|
||||
/**
|
||||
* @type {(path: Point, point: Array<number>) => void}
|
||||
*/
|
||||
const addPoint = (path, point) => {
|
||||
if (!path.list.length || point[1] > path.list[path.maxY][1]) {
|
||||
path.maxY = path.list.length;
|
||||
points.maxY = points.list.length
|
||||
? Math.max(point[1], points.maxY)
|
||||
: point[1];
|
||||
}
|
||||
if (!path.list.length || point[0] > path.list[path.maxX][0]) {
|
||||
path.maxX = path.list.length;
|
||||
points.maxX = points.list.length
|
||||
? Math.max(point[0], points.maxX)
|
||||
: point[0];
|
||||
}
|
||||
if (!path.list.length || point[1] < path.list[path.minY][1]) {
|
||||
path.minY = path.list.length;
|
||||
points.minY = points.list.length
|
||||
? Math.min(point[1], points.minY)
|
||||
: point[1];
|
||||
}
|
||||
if (!path.list.length || point[0] < path.list[path.minX][0]) {
|
||||
path.minX = path.list.length;
|
||||
points.minX = points.list.length
|
||||
? Math.min(point[0], points.minX)
|
||||
: point[0];
|
||||
}
|
||||
path.list.push(point);
|
||||
};
|
||||
|
||||
for (let i = 0; i < pathData.length; i += 1) {
|
||||
const pathDataItem = pathData[i];
|
||||
let subPath =
|
||||
points.list.length === 0
|
||||
? { list: [], minX: 0, minY: 0, maxX: 0, maxY: 0 }
|
||||
: points.list[points.list.length - 1];
|
||||
let prev = i === 0 ? null : pathData[i - 1];
|
||||
let basePoint =
|
||||
subPath.list.length === 0 ? null : subPath.list[subPath.list.length - 1];
|
||||
let data = pathDataItem.args;
|
||||
let ctrlPoint = basePoint;
|
||||
|
||||
/**
|
||||
* @type {(n: number, i: number) => number}
|
||||
* TODO fix null hack
|
||||
*/
|
||||
const toAbsolute = (n, i) => n + (basePoint == null ? 0 : basePoint[i % 2]);
|
||||
|
||||
switch (pathDataItem.command) {
|
||||
case 'M':
|
||||
subPath = { list: [], minX: 0, minY: 0, maxX: 0, maxY: 0 };
|
||||
points.list.push(subPath);
|
||||
break;
|
||||
|
||||
case 'H':
|
||||
if (basePoint != null) {
|
||||
addPoint(subPath, [data[0], basePoint[1]]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'V':
|
||||
if (basePoint != null) {
|
||||
addPoint(subPath, [basePoint[0], data[0]]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Q':
|
||||
addPoint(subPath, data.slice(0, 2));
|
||||
prevCtrlPoint = [data[2] - data[0], data[3] - data[1]]; // Save control point for shorthand
|
||||
break;
|
||||
|
||||
case 'T':
|
||||
if (
|
||||
basePoint != null &&
|
||||
prev != null &&
|
||||
(prev.command == 'Q' || prev.command == 'T')
|
||||
) {
|
||||
ctrlPoint = [
|
||||
basePoint[0] + prevCtrlPoint[0],
|
||||
basePoint[1] + prevCtrlPoint[1],
|
||||
];
|
||||
addPoint(subPath, ctrlPoint);
|
||||
prevCtrlPoint = [data[0] - ctrlPoint[0], data[1] - ctrlPoint[1]];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'C':
|
||||
if (basePoint != null) {
|
||||
// Approximate quibic Bezier curve with middle points between control points
|
||||
addPoint(subPath, [
|
||||
0.5 * (basePoint[0] + data[0]),
|
||||
0.5 * (basePoint[1] + data[1]),
|
||||
]);
|
||||
}
|
||||
addPoint(subPath, [
|
||||
0.5 * (data[0] + data[2]),
|
||||
0.5 * (data[1] + data[3]),
|
||||
]);
|
||||
addPoint(subPath, [
|
||||
0.5 * (data[2] + data[4]),
|
||||
0.5 * (data[3] + data[5]),
|
||||
]);
|
||||
prevCtrlPoint = [data[4] - data[2], data[5] - data[3]]; // Save control point for shorthand
|
||||
break;
|
||||
|
||||
case 'S':
|
||||
if (
|
||||
basePoint != null &&
|
||||
prev != null &&
|
||||
(prev.command == 'C' || prev.command == 'S')
|
||||
) {
|
||||
addPoint(subPath, [
|
||||
basePoint[0] + 0.5 * prevCtrlPoint[0],
|
||||
basePoint[1] + 0.5 * prevCtrlPoint[1],
|
||||
]);
|
||||
ctrlPoint = [
|
||||
basePoint[0] + prevCtrlPoint[0],
|
||||
basePoint[1] + prevCtrlPoint[1],
|
||||
];
|
||||
}
|
||||
if (ctrlPoint != null) {
|
||||
addPoint(subPath, [
|
||||
0.5 * (ctrlPoint[0] + data[0]),
|
||||
0.5 * (ctrlPoint[1] + data[1]),
|
||||
]);
|
||||
}
|
||||
addPoint(subPath, [
|
||||
0.5 * (data[0] + data[2]),
|
||||
0.5 * (data[1] + data[3]),
|
||||
]);
|
||||
prevCtrlPoint = [data[2] - data[0], data[3] - data[1]];
|
||||
break;
|
||||
|
||||
case 'A':
|
||||
if (basePoint != null) {
|
||||
// Convert the arc to bezier curves and use the same approximation
|
||||
// @ts-ignore no idea what's going on here
|
||||
var curves = a2c.apply(0, basePoint.concat(data));
|
||||
for (
|
||||
var cData;
|
||||
(cData = curves.splice(0, 6).map(toAbsolute)).length;
|
||||
|
||||
) {
|
||||
if (basePoint != null) {
|
||||
addPoint(subPath, [
|
||||
0.5 * (basePoint[0] + cData[0]),
|
||||
0.5 * (basePoint[1] + cData[1]),
|
||||
]);
|
||||
}
|
||||
addPoint(subPath, [
|
||||
0.5 * (cData[0] + cData[2]),
|
||||
0.5 * (cData[1] + cData[3]),
|
||||
]);
|
||||
addPoint(subPath, [
|
||||
0.5 * (cData[2] + cData[4]),
|
||||
0.5 * (cData[3] + cData[5]),
|
||||
]);
|
||||
if (curves.length) addPoint(subPath, (basePoint = cData.slice(-2)));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Save final command coordinates
|
||||
if (data.length >= 2) addPoint(subPath, data.slice(-2));
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forms a convex hull from set of points of every subpath using monotone chain convex hull algorithm.
|
||||
* https://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain
|
||||
*
|
||||
* @type {(points: Point) => Point}
|
||||
*/
|
||||
function convexHull(points) {
|
||||
points.list.sort(function (a, b) {
|
||||
return a[0] == b[0] ? a[1] - b[1] : a[0] - b[0];
|
||||
});
|
||||
|
||||
var lower = [],
|
||||
minY = 0,
|
||||
bottom = 0;
|
||||
for (let i = 0; i < points.list.length; i++) {
|
||||
while (
|
||||
lower.length >= 2 &&
|
||||
cross(lower[lower.length - 2], lower[lower.length - 1], points.list[i]) <=
|
||||
0
|
||||
) {
|
||||
lower.pop();
|
||||
}
|
||||
if (points.list[i][1] < points.list[minY][1]) {
|
||||
minY = i;
|
||||
bottom = lower.length;
|
||||
}
|
||||
lower.push(points.list[i]);
|
||||
}
|
||||
|
||||
var upper = [],
|
||||
maxY = points.list.length - 1,
|
||||
top = 0;
|
||||
for (let i = points.list.length; i--; ) {
|
||||
while (
|
||||
upper.length >= 2 &&
|
||||
cross(upper[upper.length - 2], upper[upper.length - 1], points.list[i]) <=
|
||||
0
|
||||
) {
|
||||
upper.pop();
|
||||
}
|
||||
if (points.list[i][1] > points.list[maxY][1]) {
|
||||
maxY = i;
|
||||
top = upper.length;
|
||||
}
|
||||
upper.push(points.list[i]);
|
||||
}
|
||||
|
||||
// last points are equal to starting points of the other part
|
||||
upper.pop();
|
||||
lower.pop();
|
||||
|
||||
const hullList = lower.concat(upper);
|
||||
|
||||
/**
|
||||
* @type {Point}
|
||||
*/
|
||||
const hull = {
|
||||
list: hullList,
|
||||
minX: 0, // by sorting
|
||||
maxX: lower.length,
|
||||
minY: bottom,
|
||||
maxY: (lower.length + top) % hullList.length,
|
||||
};
|
||||
|
||||
return hull;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {(o: Array<number>, a: Array<number>, b: Array<number>) => number}
|
||||
*/
|
||||
function cross(o, a, b) {
|
||||
return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on code from Snap.svg (Apache 2 license). http://snapsvg.io/
|
||||
* Thanks to Dmitry Baranovskiy for his great work!
|
||||
*
|
||||
* @type {(
|
||||
* x1: number,
|
||||
* y1: number,
|
||||
* rx: number,
|
||||
* ry: number,
|
||||
* angle: number,
|
||||
* large_arc_flag: number,
|
||||
* sweep_flag: number,
|
||||
* x2: number,
|
||||
* y2: number,
|
||||
* recursive: Array<number>
|
||||
* ) => Array<number>}
|
||||
*/
|
||||
const a2c = (
|
||||
x1,
|
||||
y1,
|
||||
rx,
|
||||
ry,
|
||||
angle,
|
||||
large_arc_flag,
|
||||
sweep_flag,
|
||||
x2,
|
||||
y2,
|
||||
recursive
|
||||
) => {
|
||||
// for more information of where this Math came from visit:
|
||||
// https://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
|
||||
const _120 = (Math.PI * 120) / 180;
|
||||
const rad = (Math.PI / 180) * (+angle || 0);
|
||||
/**
|
||||
* @type {Array<number>}
|
||||
*/
|
||||
let res = [];
|
||||
/**
|
||||
* @type {(x: number, y: number, rad: number) => number}
|
||||
*/
|
||||
const rotateX = (x, y, rad) => {
|
||||
return x * Math.cos(rad) - y * Math.sin(rad);
|
||||
};
|
||||
/**
|
||||
* @type {(x: number, y: number, rad: number) => number}
|
||||
*/
|
||||
const rotateY = (x, y, rad) => {
|
||||
return x * Math.sin(rad) + y * Math.cos(rad);
|
||||
};
|
||||
if (!recursive) {
|
||||
x1 = rotateX(x1, y1, -rad);
|
||||
y1 = rotateY(x1, y1, -rad);
|
||||
x2 = rotateX(x2, y2, -rad);
|
||||
y2 = rotateY(x2, y2, -rad);
|
||||
var x = (x1 - x2) / 2,
|
||||
y = (y1 - y2) / 2;
|
||||
var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
|
||||
if (h > 1) {
|
||||
h = Math.sqrt(h);
|
||||
rx = h * rx;
|
||||
ry = h * ry;
|
||||
}
|
||||
var rx2 = rx * rx;
|
||||
var ry2 = ry * ry;
|
||||
var k =
|
||||
(large_arc_flag == sweep_flag ? -1 : 1) *
|
||||
Math.sqrt(
|
||||
Math.abs(
|
||||
(rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)
|
||||
)
|
||||
);
|
||||
var cx = (k * rx * y) / ry + (x1 + x2) / 2;
|
||||
var cy = (k * -ry * x) / rx + (y1 + y2) / 2;
|
||||
var f1 = Math.asin(Number(((y1 - cy) / ry).toFixed(9)));
|
||||
var f2 = Math.asin(Number(((y2 - cy) / ry).toFixed(9)));
|
||||
|
||||
f1 = x1 < cx ? Math.PI - f1 : f1;
|
||||
f2 = x2 < cx ? Math.PI - f2 : f2;
|
||||
f1 < 0 && (f1 = Math.PI * 2 + f1);
|
||||
f2 < 0 && (f2 = Math.PI * 2 + f2);
|
||||
if (sweep_flag && f1 > f2) {
|
||||
f1 = f1 - Math.PI * 2;
|
||||
}
|
||||
if (!sweep_flag && f2 > f1) {
|
||||
f2 = f2 - Math.PI * 2;
|
||||
}
|
||||
} else {
|
||||
f1 = recursive[0];
|
||||
f2 = recursive[1];
|
||||
cx = recursive[2];
|
||||
cy = recursive[3];
|
||||
}
|
||||
var df = f2 - f1;
|
||||
if (Math.abs(df) > _120) {
|
||||
var f2old = f2,
|
||||
x2old = x2,
|
||||
y2old = y2;
|
||||
f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
|
||||
x2 = cx + rx * Math.cos(f2);
|
||||
y2 = cy + ry * Math.sin(f2);
|
||||
res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [
|
||||
f2,
|
||||
f2old,
|
||||
cx,
|
||||
cy,
|
||||
]);
|
||||
}
|
||||
df = f2 - f1;
|
||||
var c1 = Math.cos(f1),
|
||||
s1 = Math.sin(f1),
|
||||
c2 = Math.cos(f2),
|
||||
s2 = Math.sin(f2),
|
||||
t = Math.tan(df / 4),
|
||||
hx = (4 / 3) * rx * t,
|
||||
hy = (4 / 3) * ry * t,
|
||||
m = [
|
||||
-hx * s1,
|
||||
hy * c1,
|
||||
x2 + hx * s2 - x1,
|
||||
y2 - hy * c2 - y1,
|
||||
x2 - x1,
|
||||
y2 - y1,
|
||||
];
|
||||
if (recursive) {
|
||||
return m.concat(res);
|
||||
} else {
|
||||
res = m.concat(res);
|
||||
var newres = [];
|
||||
for (var i = 0, n = res.length; i < n; i++) {
|
||||
newres[i] =
|
||||
i % 2
|
||||
? rotateY(res[i - 1], res[i], rad)
|
||||
: rotateX(res[i], res[i + 1], rad);
|
||||
}
|
||||
return newres;
|
||||
}
|
||||
};
|
379
node_modules/svgo/plugins/_transforms.js
generated
vendored
Normal file
379
node_modules/svgo/plugins/_transforms.js
generated
vendored
Normal file
@ -0,0 +1,379 @@
|
||||
'use strict';
|
||||
|
||||
const regTransformTypes = /matrix|translate|scale|rotate|skewX|skewY/;
|
||||
const regTransformSplit =
|
||||
/\s*(matrix|translate|scale|rotate|skewX|skewY)\s*\(\s*(.+?)\s*\)[\s,]*/;
|
||||
const regNumericValues = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;
|
||||
|
||||
/**
|
||||
* @typedef {{ name: string, data: Array<number> }} TransformItem
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convert transform string to JS representation.
|
||||
*
|
||||
* @type {(transformString: string) => Array<TransformItem>}
|
||||
*/
|
||||
exports.transform2js = (transformString) => {
|
||||
// JS representation of the transform data
|
||||
/**
|
||||
* @type {Array<TransformItem>}
|
||||
*/
|
||||
const transforms = [];
|
||||
// current transform context
|
||||
/**
|
||||
* @type {null | TransformItem}
|
||||
*/
|
||||
let current = null;
|
||||
// split value into ['', 'translate', '10 50', '', 'scale', '2', '', 'rotate', '-45', '']
|
||||
for (const item of transformString.split(regTransformSplit)) {
|
||||
var num;
|
||||
if (item) {
|
||||
// if item is a translate function
|
||||
if (regTransformTypes.test(item)) {
|
||||
// then collect it and change current context
|
||||
current = { name: item, data: [] };
|
||||
transforms.push(current);
|
||||
// else if item is data
|
||||
} else {
|
||||
// then split it into [10, 50] and collect as context.data
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
while ((num = regNumericValues.exec(item))) {
|
||||
num = Number(num);
|
||||
if (current != null) {
|
||||
current.data.push(num);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// return empty array if broken transform (no data)
|
||||
return current == null || current.data.length == 0 ? [] : transforms;
|
||||
};
|
||||
|
||||
/**
|
||||
* Multiply transforms into one.
|
||||
*
|
||||
* @type {(transforms: Array<TransformItem>) => TransformItem}
|
||||
*/
|
||||
exports.transformsMultiply = (transforms) => {
|
||||
// convert transforms objects to the matrices
|
||||
const matrixData = transforms.map((transform) => {
|
||||
if (transform.name === 'matrix') {
|
||||
return transform.data;
|
||||
}
|
||||
return transformToMatrix(transform);
|
||||
});
|
||||
// multiply all matrices into one
|
||||
const matrixTransform = {
|
||||
name: 'matrix',
|
||||
data:
|
||||
matrixData.length > 0 ? matrixData.reduce(multiplyTransformMatrices) : [],
|
||||
};
|
||||
return matrixTransform;
|
||||
};
|
||||
|
||||
/**
|
||||
* math utilities in radians.
|
||||
*/
|
||||
const mth = {
|
||||
/**
|
||||
* @type {(deg: number) => number}
|
||||
*/
|
||||
rad: (deg) => {
|
||||
return (deg * Math.PI) / 180;
|
||||
},
|
||||
|
||||
/**
|
||||
* @type {(rad: number) => number}
|
||||
*/
|
||||
deg: (rad) => {
|
||||
return (rad * 180) / Math.PI;
|
||||
},
|
||||
|
||||
/**
|
||||
* @type {(deg: number) => number}
|
||||
*/
|
||||
cos: (deg) => {
|
||||
return Math.cos(mth.rad(deg));
|
||||
},
|
||||
|
||||
/**
|
||||
* @type {(val: number, floatPrecision: number) => number}
|
||||
*/
|
||||
acos: (val, floatPrecision) => {
|
||||
return Number(mth.deg(Math.acos(val)).toFixed(floatPrecision));
|
||||
},
|
||||
|
||||
/**
|
||||
* @type {(deg: number) => number}
|
||||
*/
|
||||
sin: (deg) => {
|
||||
return Math.sin(mth.rad(deg));
|
||||
},
|
||||
|
||||
/**
|
||||
* @type {(val: number, floatPrecision: number) => number}
|
||||
*/
|
||||
asin: (val, floatPrecision) => {
|
||||
return Number(mth.deg(Math.asin(val)).toFixed(floatPrecision));
|
||||
},
|
||||
|
||||
/**
|
||||
* @type {(deg: number) => number}
|
||||
*/
|
||||
tan: (deg) => {
|
||||
return Math.tan(mth.rad(deg));
|
||||
},
|
||||
|
||||
/**
|
||||
* @type {(val: number, floatPrecision: number) => number}
|
||||
*/
|
||||
atan: (val, floatPrecision) => {
|
||||
return Number(mth.deg(Math.atan(val)).toFixed(floatPrecision));
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* convertToShorts: boolean,
|
||||
* floatPrecision: number,
|
||||
* transformPrecision: number,
|
||||
* matrixToTransform: boolean,
|
||||
* shortTranslate: boolean,
|
||||
* shortScale: boolean,
|
||||
* shortRotate: boolean,
|
||||
* removeUseless: boolean,
|
||||
* collapseIntoOne: boolean,
|
||||
* leadingZero: boolean,
|
||||
* negativeExtraSpace: boolean,
|
||||
* }} TransformParams
|
||||
*/
|
||||
|
||||
/**
|
||||
* Decompose matrix into simple transforms. See
|
||||
* https://frederic-wang.fr/decomposition-of-2d-transform-matrices.html
|
||||
*
|
||||
* @type {(transform: TransformItem, params: TransformParams) => Array<TransformItem>}
|
||||
*/
|
||||
exports.matrixToTransform = (transform, params) => {
|
||||
let floatPrecision = params.floatPrecision;
|
||||
let data = transform.data;
|
||||
let transforms = [];
|
||||
let sx = Number(
|
||||
Math.hypot(data[0], data[1]).toFixed(params.transformPrecision)
|
||||
);
|
||||
let sy = Number(
|
||||
((data[0] * data[3] - data[1] * data[2]) / sx).toFixed(
|
||||
params.transformPrecision
|
||||
)
|
||||
);
|
||||
let colsSum = data[0] * data[2] + data[1] * data[3];
|
||||
let rowsSum = data[0] * data[1] + data[2] * data[3];
|
||||
let scaleBefore = rowsSum != 0 || sx == sy;
|
||||
|
||||
// [..., ..., ..., ..., tx, ty] → translate(tx, ty)
|
||||
if (data[4] || data[5]) {
|
||||
transforms.push({
|
||||
name: 'translate',
|
||||
data: data.slice(4, data[5] ? 6 : 5),
|
||||
});
|
||||
}
|
||||
|
||||
// [sx, 0, tan(a)·sy, sy, 0, 0] → skewX(a)·scale(sx, sy)
|
||||
if (!data[1] && data[2]) {
|
||||
transforms.push({
|
||||
name: 'skewX',
|
||||
data: [mth.atan(data[2] / sy, floatPrecision)],
|
||||
});
|
||||
|
||||
// [sx, sx·tan(a), 0, sy, 0, 0] → skewY(a)·scale(sx, sy)
|
||||
} else if (data[1] && !data[2]) {
|
||||
transforms.push({
|
||||
name: 'skewY',
|
||||
data: [mth.atan(data[1] / data[0], floatPrecision)],
|
||||
});
|
||||
sx = data[0];
|
||||
sy = data[3];
|
||||
|
||||
// [sx·cos(a), sx·sin(a), sy·-sin(a), sy·cos(a), x, y] → rotate(a[, cx, cy])·(scale or skewX) or
|
||||
// [sx·cos(a), sy·sin(a), sx·-sin(a), sy·cos(a), x, y] → scale(sx, sy)·rotate(a[, cx, cy]) (if !scaleBefore)
|
||||
} else if (!colsSum || (sx == 1 && sy == 1) || !scaleBefore) {
|
||||
if (!scaleBefore) {
|
||||
sx = (data[0] < 0 ? -1 : 1) * Math.hypot(data[0], data[2]);
|
||||
sy = (data[3] < 0 ? -1 : 1) * Math.hypot(data[1], data[3]);
|
||||
transforms.push({ name: 'scale', data: [sx, sy] });
|
||||
}
|
||||
var angle = Math.min(Math.max(-1, data[0] / sx), 1),
|
||||
rotate = [
|
||||
mth.acos(angle, floatPrecision) *
|
||||
((scaleBefore ? 1 : sy) * data[1] < 0 ? -1 : 1),
|
||||
];
|
||||
|
||||
if (rotate[0]) transforms.push({ name: 'rotate', data: rotate });
|
||||
|
||||
if (rowsSum && colsSum)
|
||||
transforms.push({
|
||||
name: 'skewX',
|
||||
data: [mth.atan(colsSum / (sx * sx), floatPrecision)],
|
||||
});
|
||||
|
||||
// rotate(a, cx, cy) can consume translate() within optional arguments cx, cy (rotation point)
|
||||
if (rotate[0] && (data[4] || data[5])) {
|
||||
transforms.shift();
|
||||
var cos = data[0] / sx,
|
||||
sin = data[1] / (scaleBefore ? sx : sy),
|
||||
x = data[4] * (scaleBefore ? 1 : sy),
|
||||
y = data[5] * (scaleBefore ? 1 : sx),
|
||||
denom =
|
||||
(Math.pow(1 - cos, 2) + Math.pow(sin, 2)) *
|
||||
(scaleBefore ? 1 : sx * sy);
|
||||
rotate.push(((1 - cos) * x - sin * y) / denom);
|
||||
rotate.push(((1 - cos) * y + sin * x) / denom);
|
||||
}
|
||||
|
||||
// Too many transformations, return original matrix if it isn't just a scale/translate
|
||||
} else if (data[1] || data[2]) {
|
||||
return [transform];
|
||||
}
|
||||
|
||||
if ((scaleBefore && (sx != 1 || sy != 1)) || !transforms.length)
|
||||
transforms.push({
|
||||
name: 'scale',
|
||||
data: sx == sy ? [sx] : [sx, sy],
|
||||
});
|
||||
|
||||
return transforms;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert transform to the matrix data.
|
||||
*
|
||||
* @type {(transform: TransformItem) => Array<number> }
|
||||
*/
|
||||
const transformToMatrix = (transform) => {
|
||||
if (transform.name === 'matrix') {
|
||||
return transform.data;
|
||||
}
|
||||
switch (transform.name) {
|
||||
case 'translate':
|
||||
// [1, 0, 0, 1, tx, ty]
|
||||
return [1, 0, 0, 1, transform.data[0], transform.data[1] || 0];
|
||||
case 'scale':
|
||||
// [sx, 0, 0, sy, 0, 0]
|
||||
return [
|
||||
transform.data[0],
|
||||
0,
|
||||
0,
|
||||
transform.data[1] || transform.data[0],
|
||||
0,
|
||||
0,
|
||||
];
|
||||
case 'rotate':
|
||||
// [cos(a), sin(a), -sin(a), cos(a), x, y]
|
||||
var cos = mth.cos(transform.data[0]),
|
||||
sin = mth.sin(transform.data[0]),
|
||||
cx = transform.data[1] || 0,
|
||||
cy = transform.data[2] || 0;
|
||||
return [
|
||||
cos,
|
||||
sin,
|
||||
-sin,
|
||||
cos,
|
||||
(1 - cos) * cx + sin * cy,
|
||||
(1 - cos) * cy - sin * cx,
|
||||
];
|
||||
case 'skewX':
|
||||
// [1, 0, tan(a), 1, 0, 0]
|
||||
return [1, 0, mth.tan(transform.data[0]), 1, 0, 0];
|
||||
case 'skewY':
|
||||
// [1, tan(a), 0, 1, 0, 0]
|
||||
return [1, mth.tan(transform.data[0]), 0, 1, 0, 0];
|
||||
default:
|
||||
throw Error(`Unknown transform ${transform.name}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Applies transformation to an arc. To do so, we represent ellipse as a matrix, multiply it
|
||||
* by the transformation matrix and use a singular value decomposition to represent in a form
|
||||
* rotate(θ)·scale(a b)·rotate(φ). This gives us new ellipse params a, b and θ.
|
||||
* SVD is being done with the formulae provided by Wolffram|Alpha (svd {{m0, m2}, {m1, m3}})
|
||||
*
|
||||
* @type {(
|
||||
* cursor: [x: number, y: number],
|
||||
* arc: Array<number>,
|
||||
* transform: Array<number>
|
||||
* ) => Array<number>}
|
||||
*/
|
||||
exports.transformArc = (cursor, arc, transform) => {
|
||||
const x = arc[5] - cursor[0];
|
||||
const y = arc[6] - cursor[1];
|
||||
let a = arc[0];
|
||||
let b = arc[1];
|
||||
const rot = (arc[2] * Math.PI) / 180;
|
||||
const cos = Math.cos(rot);
|
||||
const sin = Math.sin(rot);
|
||||
// skip if radius is 0
|
||||
if (a > 0 && b > 0) {
|
||||
let h =
|
||||
Math.pow(x * cos + y * sin, 2) / (4 * a * a) +
|
||||
Math.pow(y * cos - x * sin, 2) / (4 * b * b);
|
||||
if (h > 1) {
|
||||
h = Math.sqrt(h);
|
||||
a *= h;
|
||||
b *= h;
|
||||
}
|
||||
}
|
||||
const ellipse = [a * cos, a * sin, -b * sin, b * cos, 0, 0];
|
||||
const m = multiplyTransformMatrices(transform, ellipse);
|
||||
// Decompose the new ellipse matrix
|
||||
const lastCol = m[2] * m[2] + m[3] * m[3];
|
||||
const squareSum = m[0] * m[0] + m[1] * m[1] + lastCol;
|
||||
const root =
|
||||
Math.hypot(m[0] - m[3], m[1] + m[2]) * Math.hypot(m[0] + m[3], m[1] - m[2]);
|
||||
|
||||
if (!root) {
|
||||
// circle
|
||||
arc[0] = arc[1] = Math.sqrt(squareSum / 2);
|
||||
arc[2] = 0;
|
||||
} else {
|
||||
const majorAxisSqr = (squareSum + root) / 2;
|
||||
const minorAxisSqr = (squareSum - root) / 2;
|
||||
const major = Math.abs(majorAxisSqr - lastCol) > 1e-6;
|
||||
const sub = (major ? majorAxisSqr : minorAxisSqr) - lastCol;
|
||||
const rowsSum = m[0] * m[2] + m[1] * m[3];
|
||||
const term1 = m[0] * sub + m[2] * rowsSum;
|
||||
const term2 = m[1] * sub + m[3] * rowsSum;
|
||||
arc[0] = Math.sqrt(majorAxisSqr);
|
||||
arc[1] = Math.sqrt(minorAxisSqr);
|
||||
arc[2] =
|
||||
(((major ? term2 < 0 : term1 > 0) ? -1 : 1) *
|
||||
Math.acos((major ? term1 : term2) / Math.hypot(term1, term2)) *
|
||||
180) /
|
||||
Math.PI;
|
||||
}
|
||||
|
||||
if (transform[0] < 0 !== transform[3] < 0) {
|
||||
// Flip the sweep flag if coordinates are being flipped horizontally XOR vertically
|
||||
arc[4] = 1 - arc[4];
|
||||
}
|
||||
|
||||
return arc;
|
||||
};
|
||||
|
||||
/**
|
||||
* Multiply transformation matrices.
|
||||
*
|
||||
* @type {(a: Array<number>, b: Array<number>) => Array<number>}
|
||||
*/
|
||||
const multiplyTransformMatrices = (a, b) => {
|
||||
return [
|
||||
a[0] * b[0] + a[2] * b[1],
|
||||
a[1] * b[0] + a[3] * b[1],
|
||||
a[0] * b[2] + a[2] * b[3],
|
||||
a[1] * b[2] + a[3] * b[3],
|
||||
a[0] * b[4] + a[2] * b[5] + a[4],
|
||||
a[1] * b[4] + a[3] * b[5] + a[5],
|
||||
];
|
||||
};
|
87
node_modules/svgo/plugins/addAttributesToSVGElement.js
generated
vendored
Normal file
87
node_modules/svgo/plugins/addAttributesToSVGElement.js
generated
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
'use strict';
|
||||
|
||||
exports.name = 'addAttributesToSVGElement';
|
||||
exports.type = 'visitor';
|
||||
exports.active = false;
|
||||
exports.description = 'adds attributes to an outer <svg> element';
|
||||
|
||||
var ENOCLS = `Error in plugin "addAttributesToSVGElement": absent parameters.
|
||||
It should have a list of "attributes" or one "attribute".
|
||||
Config example:
|
||||
|
||||
plugins: [
|
||||
{
|
||||
name: 'addAttributesToSVGElement',
|
||||
params: {
|
||||
attribute: "mySvg"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
plugins: [
|
||||
{
|
||||
name: 'addAttributesToSVGElement',
|
||||
params: {
|
||||
attributes: ["mySvg", "size-big"]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
plugins: [
|
||||
{
|
||||
name: 'addAttributesToSVGElement',
|
||||
params: {
|
||||
attributes: [
|
||||
{
|
||||
focusable: false
|
||||
},
|
||||
{
|
||||
'data-image': icon
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
`;
|
||||
|
||||
/**
|
||||
* Add attributes to an outer <svg> element. Example config:
|
||||
*
|
||||
* @author April Arcus
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<{
|
||||
* attribute?: string | Record<string, null | string>,
|
||||
* attributes?: Array<string | Record<string, null | string>>
|
||||
* }>}
|
||||
*/
|
||||
exports.fn = (root, params) => {
|
||||
if (!Array.isArray(params.attributes) && !params.attribute) {
|
||||
console.error(ENOCLS);
|
||||
return null;
|
||||
}
|
||||
const attributes = params.attributes || [params.attribute];
|
||||
return {
|
||||
element: {
|
||||
enter: (node, parentNode) => {
|
||||
if (node.name === 'svg' && parentNode.type === 'root') {
|
||||
for (const attribute of attributes) {
|
||||
if (typeof attribute === 'string') {
|
||||
if (node.attributes[attribute] == null) {
|
||||
// @ts-ignore disallow explicit nullable attribute value
|
||||
node.attributes[attribute] = undefined;
|
||||
}
|
||||
}
|
||||
if (typeof attribute === 'object') {
|
||||
for (const key of Object.keys(attribute)) {
|
||||
if (node.attributes[key] == null) {
|
||||
// @ts-ignore disallow explicit nullable attribute value
|
||||
node.attributes[key] = attribute[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
87
node_modules/svgo/plugins/addClassesToSVGElement.js
generated
vendored
Normal file
87
node_modules/svgo/plugins/addClassesToSVGElement.js
generated
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
'use strict';
|
||||
|
||||
exports.name = 'addClassesToSVGElement';
|
||||
exports.type = 'visitor';
|
||||
exports.active = false;
|
||||
exports.description = 'adds classnames to an outer <svg> element';
|
||||
|
||||
var ENOCLS = `Error in plugin "addClassesToSVGElement": absent parameters.
|
||||
It should have a list of classes in "classNames" or one "className".
|
||||
Config example:
|
||||
|
||||
plugins: [
|
||||
{
|
||||
name: "addClassesToSVGElement",
|
||||
params: {
|
||||
className: "mySvg"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
plugins: [
|
||||
{
|
||||
name: "addClassesToSVGElement",
|
||||
params: {
|
||||
classNames: ["mySvg", "size-big"]
|
||||
}
|
||||
}
|
||||
]
|
||||
`;
|
||||
|
||||
/**
|
||||
* Add classnames to an outer <svg> element. Example config:
|
||||
*
|
||||
* plugins: [
|
||||
* {
|
||||
* name: "addClassesToSVGElement",
|
||||
* params: {
|
||||
* className: "mySvg"
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* plugins: [
|
||||
* {
|
||||
* name: "addClassesToSVGElement",
|
||||
* params: {
|
||||
* classNames: ["mySvg", "size-big"]
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* @author April Arcus
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<{
|
||||
* className?: string,
|
||||
* classNames?: Array<string>
|
||||
* }>}
|
||||
*/
|
||||
exports.fn = (root, params) => {
|
||||
if (
|
||||
!(Array.isArray(params.classNames) && params.classNames.some(String)) &&
|
||||
!params.className
|
||||
) {
|
||||
console.error(ENOCLS);
|
||||
return null;
|
||||
}
|
||||
const classNames = params.classNames || [params.className];
|
||||
return {
|
||||
element: {
|
||||
enter: (node, parentNode) => {
|
||||
if (node.name === 'svg' && parentNode.type === 'root') {
|
||||
const classList = new Set(
|
||||
node.attributes.class == null
|
||||
? null
|
||||
: node.attributes.class.split(' ')
|
||||
);
|
||||
for (const className of classNames) {
|
||||
if (className != null) {
|
||||
classList.add(className);
|
||||
}
|
||||
}
|
||||
node.attributes.class = Array.from(classList).join(' ');
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
55
node_modules/svgo/plugins/cleanupAttrs.js
generated
vendored
Normal file
55
node_modules/svgo/plugins/cleanupAttrs.js
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
|
||||
exports.name = 'cleanupAttrs';
|
||||
exports.type = 'visitor';
|
||||
exports.active = true;
|
||||
exports.description =
|
||||
'cleanups attributes from newlines, trailing and repeating spaces';
|
||||
|
||||
const regNewlinesNeedSpace = /(\S)\r?\n(\S)/g;
|
||||
const regNewlines = /\r?\n/g;
|
||||
const regSpaces = /\s{2,}/g;
|
||||
|
||||
/**
|
||||
* Cleanup attributes values from newlines, trailing and repeating spaces.
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<{
|
||||
* newlines?: boolean,
|
||||
* trim?: boolean,
|
||||
* spaces?: boolean
|
||||
* }>}
|
||||
*/
|
||||
exports.fn = (root, params) => {
|
||||
const { newlines = true, trim = true, spaces = true } = params;
|
||||
return {
|
||||
element: {
|
||||
enter: (node) => {
|
||||
for (const name of Object.keys(node.attributes)) {
|
||||
if (newlines) {
|
||||
// new line which requires a space instead of themselve
|
||||
node.attributes[name] = node.attributes[name].replace(
|
||||
regNewlinesNeedSpace,
|
||||
(match, p1, p2) => p1 + ' ' + p2
|
||||
);
|
||||
// simple new line
|
||||
node.attributes[name] = node.attributes[name].replace(
|
||||
regNewlines,
|
||||
''
|
||||
);
|
||||
}
|
||||
if (trim) {
|
||||
node.attributes[name] = node.attributes[name].trim();
|
||||
}
|
||||
if (spaces) {
|
||||
node.attributes[name] = node.attributes[name].replace(
|
||||
regSpaces,
|
||||
' '
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
75
node_modules/svgo/plugins/cleanupEnableBackground.js
generated
vendored
Normal file
75
node_modules/svgo/plugins/cleanupEnableBackground.js
generated
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
'use strict';
|
||||
|
||||
const { visit } = require('../lib/xast.js');
|
||||
|
||||
exports.type = 'visitor';
|
||||
exports.name = 'cleanupEnableBackground';
|
||||
exports.active = true;
|
||||
exports.description =
|
||||
'remove or cleanup enable-background attribute when possible';
|
||||
|
||||
/**
|
||||
* Remove or cleanup enable-background attr which coincides with a width/height box.
|
||||
*
|
||||
* @see https://www.w3.org/TR/SVG11/filters.html#EnableBackgroundProperty
|
||||
*
|
||||
* @example
|
||||
* <svg width="100" height="50" enable-background="new 0 0 100 50">
|
||||
* ⬇
|
||||
* <svg width="100" height="50">
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<void>}
|
||||
*/
|
||||
exports.fn = (root) => {
|
||||
const regEnableBackground =
|
||||
/^new\s0\s0\s([-+]?\d*\.?\d+([eE][-+]?\d+)?)\s([-+]?\d*\.?\d+([eE][-+]?\d+)?)$/;
|
||||
|
||||
let hasFilter = false;
|
||||
visit(root, {
|
||||
element: {
|
||||
enter: (node) => {
|
||||
if (node.name === 'filter') {
|
||||
hasFilter = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
element: {
|
||||
enter: (node) => {
|
||||
if (node.attributes['enable-background'] == null) {
|
||||
return;
|
||||
}
|
||||
if (hasFilter) {
|
||||
if (
|
||||
(node.name === 'svg' ||
|
||||
node.name === 'mask' ||
|
||||
node.name === 'pattern') &&
|
||||
node.attributes.width != null &&
|
||||
node.attributes.height != null
|
||||
) {
|
||||
const match =
|
||||
node.attributes['enable-background'].match(regEnableBackground);
|
||||
if (
|
||||
match != null &&
|
||||
node.attributes.width === match[1] &&
|
||||
node.attributes.height === match[3]
|
||||
) {
|
||||
if (node.name === 'svg') {
|
||||
delete node.attributes['enable-background'];
|
||||
} else {
|
||||
node.attributes['enable-background'] = 'new';
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//we don't need 'enable-background' if we have no filters
|
||||
delete node.attributes['enable-background'];
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
297
node_modules/svgo/plugins/cleanupIDs.js
generated
vendored
Normal file
297
node_modules/svgo/plugins/cleanupIDs.js
generated
vendored
Normal file
@ -0,0 +1,297 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @typedef {import('../lib/types').XastElement} XastElement
|
||||
*/
|
||||
|
||||
const { visitSkip } = require('../lib/xast.js');
|
||||
const { referencesProps } = require('./_collections.js');
|
||||
|
||||
exports.type = 'visitor';
|
||||
exports.name = 'cleanupIDs';
|
||||
exports.active = true;
|
||||
exports.description = 'removes unused IDs and minifies used';
|
||||
|
||||
const regReferencesUrl = /\burl\(("|')?#(.+?)\1\)/;
|
||||
const regReferencesHref = /^#(.+?)$/;
|
||||
const regReferencesBegin = /(\w+)\./;
|
||||
const generateIDchars = [
|
||||
'a',
|
||||
'b',
|
||||
'c',
|
||||
'd',
|
||||
'e',
|
||||
'f',
|
||||
'g',
|
||||
'h',
|
||||
'i',
|
||||
'j',
|
||||
'k',
|
||||
'l',
|
||||
'm',
|
||||
'n',
|
||||
'o',
|
||||
'p',
|
||||
'q',
|
||||
'r',
|
||||
's',
|
||||
't',
|
||||
'u',
|
||||
'v',
|
||||
'w',
|
||||
'x',
|
||||
'y',
|
||||
'z',
|
||||
'A',
|
||||
'B',
|
||||
'C',
|
||||
'D',
|
||||
'E',
|
||||
'F',
|
||||
'G',
|
||||
'H',
|
||||
'I',
|
||||
'J',
|
||||
'K',
|
||||
'L',
|
||||
'M',
|
||||
'N',
|
||||
'O',
|
||||
'P',
|
||||
'Q',
|
||||
'R',
|
||||
'S',
|
||||
'T',
|
||||
'U',
|
||||
'V',
|
||||
'W',
|
||||
'X',
|
||||
'Y',
|
||||
'Z',
|
||||
];
|
||||
const maxIDindex = generateIDchars.length - 1;
|
||||
|
||||
/**
|
||||
* Check if an ID starts with any one of a list of strings.
|
||||
*
|
||||
* @type {(string: string, prefixes: Array<string>) => boolean}
|
||||
*/
|
||||
const hasStringPrefix = (string, prefixes) => {
|
||||
for (const prefix of prefixes) {
|
||||
if (string.startsWith(prefix)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate unique minimal ID.
|
||||
*
|
||||
* @type {(currentID: null | Array<number>) => Array<number>}
|
||||
*/
|
||||
const generateID = (currentID) => {
|
||||
if (currentID == null) {
|
||||
return [0];
|
||||
}
|
||||
currentID[currentID.length - 1] += 1;
|
||||
for (let i = currentID.length - 1; i > 0; i--) {
|
||||
if (currentID[i] > maxIDindex) {
|
||||
currentID[i] = 0;
|
||||
if (currentID[i - 1] !== undefined) {
|
||||
currentID[i - 1]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentID[0] > maxIDindex) {
|
||||
currentID[0] = 0;
|
||||
currentID.unshift(0);
|
||||
}
|
||||
return currentID;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get string from generated ID array.
|
||||
*
|
||||
* @type {(arr: Array<number>, prefix: string) => string}
|
||||
*/
|
||||
const getIDstring = (arr, prefix) => {
|
||||
return prefix + arr.map((i) => generateIDchars[i]).join('');
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove unused and minify used IDs
|
||||
* (only if there are no any <style> or <script>).
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<{
|
||||
* remove?: boolean,
|
||||
* minify?: boolean,
|
||||
* prefix?: string,
|
||||
* preserve?: Array<string>,
|
||||
* preservePrefixes?: Array<string>,
|
||||
* force?: boolean,
|
||||
* }>}
|
||||
*/
|
||||
exports.fn = (_root, params) => {
|
||||
const {
|
||||
remove = true,
|
||||
minify = true,
|
||||
prefix = '',
|
||||
preserve = [],
|
||||
preservePrefixes = [],
|
||||
force = false,
|
||||
} = params;
|
||||
const preserveIDs = new Set(
|
||||
Array.isArray(preserve) ? preserve : preserve ? [preserve] : []
|
||||
);
|
||||
const preserveIDPrefixes = Array.isArray(preservePrefixes)
|
||||
? preservePrefixes
|
||||
: preservePrefixes
|
||||
? [preservePrefixes]
|
||||
: [];
|
||||
/**
|
||||
* @type {Map<string, XastElement>}
|
||||
*/
|
||||
const nodeById = new Map();
|
||||
/**
|
||||
* @type {Map<string, Array<{element: XastElement, name: string, value: string }>>}
|
||||
*/
|
||||
const referencesById = new Map();
|
||||
let deoptimized = false;
|
||||
|
||||
return {
|
||||
element: {
|
||||
enter: (node) => {
|
||||
if (force == false) {
|
||||
// deoptimize if style or script elements are present
|
||||
if (
|
||||
(node.name === 'style' || node.name === 'script') &&
|
||||
node.children.length !== 0
|
||||
) {
|
||||
deoptimized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// avoid removing IDs if the whole SVG consists only of defs
|
||||
if (node.name === 'svg') {
|
||||
let hasDefsOnly = true;
|
||||
for (const child of node.children) {
|
||||
if (child.type !== 'element' || child.name !== 'defs') {
|
||||
hasDefsOnly = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hasDefsOnly) {
|
||||
return visitSkip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const [name, value] of Object.entries(node.attributes)) {
|
||||
if (name === 'id') {
|
||||
// collect all ids
|
||||
const id = value;
|
||||
if (nodeById.has(id)) {
|
||||
delete node.attributes.id; // remove repeated id
|
||||
} else {
|
||||
nodeById.set(id, node);
|
||||
}
|
||||
} else {
|
||||
// collect all references
|
||||
/**
|
||||
* @type {null | string}
|
||||
*/
|
||||
let id = null;
|
||||
if (referencesProps.includes(name)) {
|
||||
const match = value.match(regReferencesUrl);
|
||||
if (match != null) {
|
||||
id = match[2]; // url() reference
|
||||
}
|
||||
}
|
||||
if (name === 'href' || name.endsWith(':href')) {
|
||||
const match = value.match(regReferencesHref);
|
||||
if (match != null) {
|
||||
id = match[1]; // href reference
|
||||
}
|
||||
}
|
||||
if (name === 'begin') {
|
||||
const match = value.match(regReferencesBegin);
|
||||
if (match != null) {
|
||||
id = match[1]; // href reference
|
||||
}
|
||||
}
|
||||
if (id != null) {
|
||||
let refs = referencesById.get(id);
|
||||
if (refs == null) {
|
||||
refs = [];
|
||||
referencesById.set(id, refs);
|
||||
}
|
||||
refs.push({ element: node, name, value });
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
root: {
|
||||
exit: () => {
|
||||
if (deoptimized) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* @type {(id: string) => boolean}
|
||||
**/
|
||||
const isIdPreserved = (id) =>
|
||||
preserveIDs.has(id) || hasStringPrefix(id, preserveIDPrefixes);
|
||||
/**
|
||||
* @type {null | Array<number>}
|
||||
*/
|
||||
let currentID = null;
|
||||
for (const [id, refs] of referencesById) {
|
||||
const node = nodeById.get(id);
|
||||
if (node != null) {
|
||||
// replace referenced IDs with the minified ones
|
||||
if (minify && isIdPreserved(id) === false) {
|
||||
/**
|
||||
* @type {null | string}
|
||||
*/
|
||||
let currentIDString = null;
|
||||
do {
|
||||
currentID = generateID(currentID);
|
||||
currentIDString = getIDstring(currentID, prefix);
|
||||
} while (isIdPreserved(currentIDString));
|
||||
node.attributes.id = currentIDString;
|
||||
for (const { element, name, value } of refs) {
|
||||
if (value.includes('#')) {
|
||||
// replace id in href and url()
|
||||
element.attributes[name] = value.replace(
|
||||
`#${id}`,
|
||||
`#${currentIDString}`
|
||||
);
|
||||
} else {
|
||||
// replace id in begin attribute
|
||||
element.attributes[name] = value.replace(
|
||||
`${id}.`,
|
||||
`${currentIDString}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// keep referenced node
|
||||
nodeById.delete(id);
|
||||
}
|
||||
}
|
||||
// remove non-referenced IDs attributes from elements
|
||||
if (remove) {
|
||||
for (const [id, node] of nodeById) {
|
||||
if (isIdPreserved(id) === false) {
|
||||
delete node.attributes.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
154
node_modules/svgo/plugins/cleanupListOfValues.js
generated
vendored
Normal file
154
node_modules/svgo/plugins/cleanupListOfValues.js
generated
vendored
Normal file
@ -0,0 +1,154 @@
|
||||
'use strict';
|
||||
|
||||
const { removeLeadingZero } = require('../lib/svgo/tools.js');
|
||||
|
||||
exports.name = 'cleanupListOfValues';
|
||||
exports.type = 'visitor';
|
||||
exports.active = false;
|
||||
exports.description = 'rounds list of values to the fixed precision';
|
||||
|
||||
const regNumericValues =
|
||||
/^([-+]?\d*\.?\d+([eE][-+]?\d+)?)(px|pt|pc|mm|cm|m|in|ft|em|ex|%)?$/;
|
||||
const regSeparator = /\s+,?\s*|,\s*/;
|
||||
const absoluteLengths = {
|
||||
// relative to px
|
||||
cm: 96 / 2.54,
|
||||
mm: 96 / 25.4,
|
||||
in: 96,
|
||||
pt: 4 / 3,
|
||||
pc: 16,
|
||||
px: 1,
|
||||
};
|
||||
|
||||
/**
|
||||
* Round list of values to the fixed precision.
|
||||
*
|
||||
* @example
|
||||
* <svg viewBox="0 0 200.28423 200.28423" enable-background="new 0 0 200.28423 200.28423">
|
||||
* ⬇
|
||||
* <svg viewBox="0 0 200.284 200.284" enable-background="new 0 0 200.284 200.284">
|
||||
*
|
||||
* <polygon points="208.250977 77.1308594 223.069336 ... "/>
|
||||
* ⬇
|
||||
* <polygon points="208.251 77.131 223.069 ... "/>
|
||||
*
|
||||
* @author kiyopikko
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<{
|
||||
* floatPrecision?: number,
|
||||
* leadingZero?: boolean,
|
||||
* defaultPx?: boolean,
|
||||
* convertToPx?: boolean
|
||||
* }>}
|
||||
*/
|
||||
exports.fn = (_root, params) => {
|
||||
const {
|
||||
floatPrecision = 3,
|
||||
leadingZero = true,
|
||||
defaultPx = true,
|
||||
convertToPx = true,
|
||||
} = params;
|
||||
|
||||
/**
|
||||
* @type {(lists: string) => string}
|
||||
*/
|
||||
const roundValues = (lists) => {
|
||||
const roundedList = [];
|
||||
|
||||
for (const elem of lists.split(regSeparator)) {
|
||||
const match = elem.match(regNumericValues);
|
||||
const matchNew = elem.match(/new/);
|
||||
|
||||
// if attribute value matches regNumericValues
|
||||
if (match) {
|
||||
// round it to the fixed precision
|
||||
let num = Number(Number(match[1]).toFixed(floatPrecision));
|
||||
/**
|
||||
* @type {any}
|
||||
*/
|
||||
let matchedUnit = match[3] || '';
|
||||
/**
|
||||
* @type{'' | keyof typeof absoluteLengths}
|
||||
*/
|
||||
let units = matchedUnit;
|
||||
|
||||
// convert absolute values to pixels
|
||||
if (convertToPx && units && units in absoluteLengths) {
|
||||
const pxNum = Number(
|
||||
(absoluteLengths[units] * Number(match[1])).toFixed(floatPrecision)
|
||||
);
|
||||
|
||||
if (pxNum.toString().length < match[0].length) {
|
||||
num = pxNum;
|
||||
units = 'px';
|
||||
}
|
||||
}
|
||||
|
||||
// and remove leading zero
|
||||
let str;
|
||||
if (leadingZero) {
|
||||
str = removeLeadingZero(num);
|
||||
} else {
|
||||
str = num.toString();
|
||||
}
|
||||
|
||||
// remove default 'px' units
|
||||
if (defaultPx && units === 'px') {
|
||||
units = '';
|
||||
}
|
||||
|
||||
roundedList.push(str + units);
|
||||
}
|
||||
// if attribute value is "new"(only enable-background).
|
||||
else if (matchNew) {
|
||||
roundedList.push('new');
|
||||
} else if (elem) {
|
||||
roundedList.push(elem);
|
||||
}
|
||||
}
|
||||
|
||||
return roundedList.join(' ');
|
||||
};
|
||||
|
||||
return {
|
||||
element: {
|
||||
enter: (node) => {
|
||||
if (node.attributes.points != null) {
|
||||
node.attributes.points = roundValues(node.attributes.points);
|
||||
}
|
||||
|
||||
if (node.attributes['enable-background'] != null) {
|
||||
node.attributes['enable-background'] = roundValues(
|
||||
node.attributes['enable-background']
|
||||
);
|
||||
}
|
||||
|
||||
if (node.attributes.viewBox != null) {
|
||||
node.attributes.viewBox = roundValues(node.attributes.viewBox);
|
||||
}
|
||||
|
||||
if (node.attributes['stroke-dasharray'] != null) {
|
||||
node.attributes['stroke-dasharray'] = roundValues(
|
||||
node.attributes['stroke-dasharray']
|
||||
);
|
||||
}
|
||||
|
||||
if (node.attributes.dx != null) {
|
||||
node.attributes.dx = roundValues(node.attributes.dx);
|
||||
}
|
||||
|
||||
if (node.attributes.dy != null) {
|
||||
node.attributes.dy = roundValues(node.attributes.dy);
|
||||
}
|
||||
|
||||
if (node.attributes.x != null) {
|
||||
node.attributes.x = roundValues(node.attributes.x);
|
||||
}
|
||||
|
||||
if (node.attributes.y != null) {
|
||||
node.attributes.y = roundValues(node.attributes.y);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
113
node_modules/svgo/plugins/cleanupNumericValues.js
generated
vendored
Normal file
113
node_modules/svgo/plugins/cleanupNumericValues.js
generated
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
'use strict';
|
||||
|
||||
const { removeLeadingZero } = require('../lib/svgo/tools');
|
||||
|
||||
exports.name = 'cleanupNumericValues';
|
||||
exports.type = 'visitor';
|
||||
exports.active = true;
|
||||
exports.description =
|
||||
'rounds numeric values to the fixed precision, removes default ‘px’ units';
|
||||
|
||||
const regNumericValues =
|
||||
/^([-+]?\d*\.?\d+([eE][-+]?\d+)?)(px|pt|pc|mm|cm|m|in|ft|em|ex|%)?$/;
|
||||
|
||||
const absoluteLengths = {
|
||||
// relative to px
|
||||
cm: 96 / 2.54,
|
||||
mm: 96 / 25.4,
|
||||
in: 96,
|
||||
pt: 4 / 3,
|
||||
pc: 16,
|
||||
px: 1,
|
||||
};
|
||||
|
||||
/**
|
||||
* Round numeric values to the fixed precision,
|
||||
* remove default 'px' units.
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<{
|
||||
* floatPrecision?: number,
|
||||
* leadingZero?: boolean,
|
||||
* defaultPx?: boolean,
|
||||
* convertToPx?: boolean
|
||||
* }>}
|
||||
*/
|
||||
exports.fn = (_root, params) => {
|
||||
const {
|
||||
floatPrecision = 3,
|
||||
leadingZero = true,
|
||||
defaultPx = true,
|
||||
convertToPx = true,
|
||||
} = params;
|
||||
|
||||
return {
|
||||
element: {
|
||||
enter: (node) => {
|
||||
if (node.attributes.viewBox != null) {
|
||||
const nums = node.attributes.viewBox.split(/\s,?\s*|,\s*/g);
|
||||
node.attributes.viewBox = nums
|
||||
.map((value) => {
|
||||
const num = Number(value);
|
||||
return Number.isNaN(num)
|
||||
? value
|
||||
: Number(num.toFixed(floatPrecision));
|
||||
})
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
for (const [name, value] of Object.entries(node.attributes)) {
|
||||
// The `version` attribute is a text string and cannot be rounded
|
||||
if (name === 'version') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const match = value.match(regNumericValues);
|
||||
|
||||
// if attribute value matches regNumericValues
|
||||
if (match) {
|
||||
// round it to the fixed precision
|
||||
let num = Number(Number(match[1]).toFixed(floatPrecision));
|
||||
/**
|
||||
* @type {any}
|
||||
*/
|
||||
let matchedUnit = match[3] || '';
|
||||
/**
|
||||
* @type{'' | keyof typeof absoluteLengths}
|
||||
*/
|
||||
let units = matchedUnit;
|
||||
|
||||
// convert absolute values to pixels
|
||||
if (convertToPx && units !== '' && units in absoluteLengths) {
|
||||
const pxNum = Number(
|
||||
(absoluteLengths[units] * Number(match[1])).toFixed(
|
||||
floatPrecision
|
||||
)
|
||||
);
|
||||
if (pxNum.toString().length < match[0].length) {
|
||||
num = pxNum;
|
||||
units = 'px';
|
||||
}
|
||||
}
|
||||
|
||||
// and remove leading zero
|
||||
let str;
|
||||
if (leadingZero) {
|
||||
str = removeLeadingZero(num);
|
||||
} else {
|
||||
str = num.toString();
|
||||
}
|
||||
|
||||
// remove default 'px' units
|
||||
if (defaultPx && units === 'px') {
|
||||
units = '';
|
||||
}
|
||||
|
||||
node.attributes[name] = str + units;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
135
node_modules/svgo/plugins/collapseGroups.js
generated
vendored
Normal file
135
node_modules/svgo/plugins/collapseGroups.js
generated
vendored
Normal file
@ -0,0 +1,135 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @typedef {import('../lib/types').XastNode} XastNode
|
||||
*/
|
||||
|
||||
const { inheritableAttrs, elemsGroups } = require('./_collections.js');
|
||||
|
||||
exports.type = 'visitor';
|
||||
exports.name = 'collapseGroups';
|
||||
exports.active = true;
|
||||
exports.description = 'collapses useless groups';
|
||||
|
||||
/**
|
||||
* @type {(node: XastNode, name: string) => boolean}
|
||||
*/
|
||||
const hasAnimatedAttr = (node, name) => {
|
||||
if (node.type === 'element') {
|
||||
if (
|
||||
elemsGroups.animation.includes(node.name) &&
|
||||
node.attributes.attributeName === name
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
for (const child of node.children) {
|
||||
if (hasAnimatedAttr(child, name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Collapse useless groups.
|
||||
*
|
||||
* @example
|
||||
* <g>
|
||||
* <g attr1="val1">
|
||||
* <path d="..."/>
|
||||
* </g>
|
||||
* </g>
|
||||
* ⬇
|
||||
* <g>
|
||||
* <g>
|
||||
* <path attr1="val1" d="..."/>
|
||||
* </g>
|
||||
* </g>
|
||||
* ⬇
|
||||
* <path attr1="val1" d="..."/>
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<void>}
|
||||
*/
|
||||
exports.fn = () => {
|
||||
return {
|
||||
element: {
|
||||
exit: (node, parentNode) => {
|
||||
if (parentNode.type === 'root' || parentNode.name === 'switch') {
|
||||
return;
|
||||
}
|
||||
// non-empty groups
|
||||
if (node.name !== 'g' || node.children.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// move group attibutes to the single child element
|
||||
if (
|
||||
Object.keys(node.attributes).length !== 0 &&
|
||||
node.children.length === 1
|
||||
) {
|
||||
const firstChild = node.children[0];
|
||||
// TODO untangle this mess
|
||||
if (
|
||||
firstChild.type === 'element' &&
|
||||
firstChild.attributes.id == null &&
|
||||
node.attributes.filter == null &&
|
||||
(node.attributes.class == null ||
|
||||
firstChild.attributes.class == null) &&
|
||||
((node.attributes['clip-path'] == null &&
|
||||
node.attributes.mask == null) ||
|
||||
(firstChild.name === 'g' &&
|
||||
node.attributes.transform == null &&
|
||||
firstChild.attributes.transform == null))
|
||||
) {
|
||||
for (const [name, value] of Object.entries(node.attributes)) {
|
||||
// avoid copying to not conflict with animated attribute
|
||||
if (hasAnimatedAttr(firstChild, name)) {
|
||||
return;
|
||||
}
|
||||
if (firstChild.attributes[name] == null) {
|
||||
firstChild.attributes[name] = value;
|
||||
} else if (name === 'transform') {
|
||||
firstChild.attributes[name] =
|
||||
value + ' ' + firstChild.attributes[name];
|
||||
} else if (firstChild.attributes[name] === 'inherit') {
|
||||
firstChild.attributes[name] = value;
|
||||
} else if (
|
||||
inheritableAttrs.includes(name) === false &&
|
||||
firstChild.attributes[name] !== value
|
||||
) {
|
||||
return;
|
||||
}
|
||||
delete node.attributes[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// collapse groups without attributes
|
||||
if (Object.keys(node.attributes).length === 0) {
|
||||
// animation elements "add" attributes to group
|
||||
// group should be preserved
|
||||
for (const child of node.children) {
|
||||
if (
|
||||
child.type === 'element' &&
|
||||
elemsGroups.animation.includes(child.name)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// replace current node with all its children
|
||||
const index = parentNode.children.indexOf(node);
|
||||
parentNode.children.splice(index, 1, ...node.children);
|
||||
// TODO remove in v3
|
||||
for (const child of node.children) {
|
||||
// @ts-ignore parentNode is forbidden for public usage
|
||||
// and will be moved in v3
|
||||
child.parentNode = parentNode;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
152
node_modules/svgo/plugins/convertColors.js
generated
vendored
Normal file
152
node_modules/svgo/plugins/convertColors.js
generated
vendored
Normal file
@ -0,0 +1,152 @@
|
||||
'use strict';
|
||||
|
||||
const collections = require('./_collections.js');
|
||||
|
||||
exports.type = 'visitor';
|
||||
exports.name = 'convertColors';
|
||||
exports.active = true;
|
||||
exports.description = 'converts colors: rgb() to #rrggbb and #rrggbb to #rgb';
|
||||
|
||||
const rNumber = '([+-]?(?:\\d*\\.\\d+|\\d+\\.?)%?)';
|
||||
const rComma = '\\s*,\\s*';
|
||||
const regRGB = new RegExp(
|
||||
'^rgb\\(\\s*' + rNumber + rComma + rNumber + rComma + rNumber + '\\s*\\)$'
|
||||
);
|
||||
const regHEX = /^#(([a-fA-F0-9])\2){3}$/;
|
||||
|
||||
/**
|
||||
* Convert [r, g, b] to #rrggbb.
|
||||
*
|
||||
* @see https://gist.github.com/983535
|
||||
*
|
||||
* @example
|
||||
* rgb2hex([255, 255, 255]) // '#ffffff'
|
||||
*
|
||||
* @author Jed Schmidt
|
||||
*
|
||||
* @type {(rgb: Array<number>) => string}
|
||||
*/
|
||||
const convertRgbToHex = ([r, g, b]) => {
|
||||
// combine the octets into a 32-bit integer as: [1][r][g][b]
|
||||
const hexNumber =
|
||||
// operator precedence is (+) > (<<) > (|)
|
||||
((((256 + // [1][0]
|
||||
r) << // [1][r]
|
||||
8) | // [1][r][0]
|
||||
g) << // [1][r][g]
|
||||
8) | // [1][r][g][0]
|
||||
b;
|
||||
// serialize [1][r][g][b] to a hex string, and
|
||||
// remove the 1 to get the number with 0s intact
|
||||
return '#' + hexNumber.toString(16).slice(1).toUpperCase();
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert different colors formats in element attributes to hex.
|
||||
*
|
||||
* @see https://www.w3.org/TR/SVG11/types.html#DataTypeColor
|
||||
* @see https://www.w3.org/TR/SVG11/single-page.html#types-ColorKeywords
|
||||
*
|
||||
* @example
|
||||
* Convert color name keyword to long hex:
|
||||
* fuchsia ➡ #ff00ff
|
||||
*
|
||||
* Convert rgb() to long hex:
|
||||
* rgb(255, 0, 255) ➡ #ff00ff
|
||||
* rgb(50%, 100, 100%) ➡ #7f64ff
|
||||
*
|
||||
* Convert long hex to short hex:
|
||||
* #aabbcc ➡ #abc
|
||||
*
|
||||
* Convert hex to short name
|
||||
* #000080 ➡ navy
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<{
|
||||
* currentColor?: boolean | string | RegExp,
|
||||
* names2hex?: boolean,
|
||||
* rgb2hex?: boolean,
|
||||
* shorthex?: boolean,
|
||||
* shortname?: boolean,
|
||||
* }>}
|
||||
*/
|
||||
exports.fn = (_root, params) => {
|
||||
const {
|
||||
currentColor = false,
|
||||
names2hex = true,
|
||||
rgb2hex = true,
|
||||
shorthex = true,
|
||||
shortname = true,
|
||||
} = params;
|
||||
|
||||
return {
|
||||
element: {
|
||||
enter: (node) => {
|
||||
for (const [name, value] of Object.entries(node.attributes)) {
|
||||
if (collections.colorsProps.includes(name)) {
|
||||
let val = value;
|
||||
|
||||
// convert colors to currentColor
|
||||
if (currentColor) {
|
||||
let matched;
|
||||
if (typeof currentColor === 'string') {
|
||||
matched = val === currentColor;
|
||||
} else if (currentColor instanceof RegExp) {
|
||||
matched = currentColor.exec(val) != null;
|
||||
} else {
|
||||
matched = val !== 'none';
|
||||
}
|
||||
if (matched) {
|
||||
val = 'currentColor';
|
||||
}
|
||||
}
|
||||
|
||||
// convert color name keyword to long hex
|
||||
if (names2hex) {
|
||||
const colorName = val.toLowerCase();
|
||||
if (collections.colorsNames[colorName] != null) {
|
||||
val = collections.colorsNames[colorName];
|
||||
}
|
||||
}
|
||||
|
||||
// convert rgb() to long hex
|
||||
if (rgb2hex) {
|
||||
let match = val.match(regRGB);
|
||||
if (match != null) {
|
||||
let nums = match.slice(1, 4).map((m) => {
|
||||
let n;
|
||||
if (m.indexOf('%') > -1) {
|
||||
n = Math.round(parseFloat(m) * 2.55);
|
||||
} else {
|
||||
n = Number(m);
|
||||
}
|
||||
return Math.max(0, Math.min(n, 255));
|
||||
});
|
||||
val = convertRgbToHex(nums);
|
||||
}
|
||||
}
|
||||
|
||||
// convert long hex to short hex
|
||||
if (shorthex) {
|
||||
let match = val.match(regHEX);
|
||||
if (match != null) {
|
||||
val = '#' + match[0][1] + match[0][3] + match[0][5];
|
||||
}
|
||||
}
|
||||
|
||||
// convert hex to short name
|
||||
if (shortname) {
|
||||
const colorName = val.toLowerCase();
|
||||
if (collections.colorsShortNames[colorName] != null) {
|
||||
val = collections.colorsShortNames[colorName];
|
||||
}
|
||||
}
|
||||
|
||||
node.attributes[name] = val;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
39
node_modules/svgo/plugins/convertEllipseToCircle.js
generated
vendored
Normal file
39
node_modules/svgo/plugins/convertEllipseToCircle.js
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
|
||||
exports.name = 'convertEllipseToCircle';
|
||||
exports.type = 'visitor';
|
||||
exports.active = true;
|
||||
exports.description = 'converts non-eccentric <ellipse>s to <circle>s';
|
||||
|
||||
/**
|
||||
* Converts non-eccentric <ellipse>s to <circle>s.
|
||||
*
|
||||
* @see https://www.w3.org/TR/SVG11/shapes.html
|
||||
*
|
||||
* @author Taylor Hunt
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<void>}
|
||||
*/
|
||||
exports.fn = () => {
|
||||
return {
|
||||
element: {
|
||||
enter: (node) => {
|
||||
if (node.name === 'ellipse') {
|
||||
const rx = node.attributes.rx || '0';
|
||||
const ry = node.attributes.ry || '0';
|
||||
if (
|
||||
rx === ry ||
|
||||
rx === 'auto' ||
|
||||
ry === 'auto' // SVG2
|
||||
) {
|
||||
node.name = 'circle';
|
||||
const radius = rx === 'auto' ? ry : rx;
|
||||
delete node.attributes.rx;
|
||||
delete node.attributes.ry;
|
||||
node.attributes.r = radius;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
1023
node_modules/svgo/plugins/convertPathData.js
generated
vendored
Normal file
1023
node_modules/svgo/plugins/convertPathData.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
175
node_modules/svgo/plugins/convertShapeToPath.js
generated
vendored
Normal file
175
node_modules/svgo/plugins/convertShapeToPath.js
generated
vendored
Normal file
@ -0,0 +1,175 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @typedef {import('../lib/types').PathDataItem} PathDataItem
|
||||
*/
|
||||
|
||||
const { stringifyPathData } = require('../lib/path.js');
|
||||
const { detachNodeFromParent } = require('../lib/xast.js');
|
||||
|
||||
exports.name = 'convertShapeToPath';
|
||||
exports.type = 'visitor';
|
||||
exports.active = true;
|
||||
exports.description = 'converts basic shapes to more compact path form';
|
||||
|
||||
const regNumber = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;
|
||||
|
||||
/**
|
||||
* Converts basic shape to more compact path.
|
||||
* It also allows further optimizations like
|
||||
* combining paths with similar attributes.
|
||||
*
|
||||
* @see https://www.w3.org/TR/SVG11/shapes.html
|
||||
*
|
||||
* @author Lev Solntsev
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<{
|
||||
* convertArcs?: boolean,
|
||||
* floatPrecision?: number
|
||||
* }>}
|
||||
*/
|
||||
exports.fn = (root, params) => {
|
||||
const { convertArcs = false, floatPrecision: precision } = params;
|
||||
|
||||
return {
|
||||
element: {
|
||||
enter: (node, parentNode) => {
|
||||
// convert rect to path
|
||||
if (
|
||||
node.name === 'rect' &&
|
||||
node.attributes.width != null &&
|
||||
node.attributes.height != null &&
|
||||
node.attributes.rx == null &&
|
||||
node.attributes.ry == null
|
||||
) {
|
||||
const x = Number(node.attributes.x || '0');
|
||||
const y = Number(node.attributes.y || '0');
|
||||
const width = Number(node.attributes.width);
|
||||
const height = Number(node.attributes.height);
|
||||
// Values like '100%' compute to NaN, thus running after
|
||||
// cleanupNumericValues when 'px' units has already been removed.
|
||||
// TODO: Calculate sizes from % and non-px units if possible.
|
||||
if (Number.isNaN(x - y + width - height)) return;
|
||||
/**
|
||||
* @type {Array<PathDataItem>}
|
||||
*/
|
||||
const pathData = [
|
||||
{ command: 'M', args: [x, y] },
|
||||
{ command: 'H', args: [x + width] },
|
||||
{ command: 'V', args: [y + height] },
|
||||
{ command: 'H', args: [x] },
|
||||
{ command: 'z', args: [] },
|
||||
];
|
||||
node.name = 'path';
|
||||
node.attributes.d = stringifyPathData({ pathData, precision });
|
||||
delete node.attributes.x;
|
||||
delete node.attributes.y;
|
||||
delete node.attributes.width;
|
||||
delete node.attributes.height;
|
||||
}
|
||||
|
||||
// convert line to path
|
||||
if (node.name === 'line') {
|
||||
const x1 = Number(node.attributes.x1 || '0');
|
||||
const y1 = Number(node.attributes.y1 || '0');
|
||||
const x2 = Number(node.attributes.x2 || '0');
|
||||
const y2 = Number(node.attributes.y2 || '0');
|
||||
if (Number.isNaN(x1 - y1 + x2 - y2)) return;
|
||||
/**
|
||||
* @type {Array<PathDataItem>}
|
||||
*/
|
||||
const pathData = [
|
||||
{ command: 'M', args: [x1, y1] },
|
||||
{ command: 'L', args: [x2, y2] },
|
||||
];
|
||||
node.name = 'path';
|
||||
node.attributes.d = stringifyPathData({ pathData, precision });
|
||||
delete node.attributes.x1;
|
||||
delete node.attributes.y1;
|
||||
delete node.attributes.x2;
|
||||
delete node.attributes.y2;
|
||||
}
|
||||
|
||||
// convert polyline and polygon to path
|
||||
if (
|
||||
(node.name === 'polyline' || node.name === 'polygon') &&
|
||||
node.attributes.points != null
|
||||
) {
|
||||
const coords = (node.attributes.points.match(regNumber) || []).map(
|
||||
Number
|
||||
);
|
||||
if (coords.length < 4) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* @type {Array<PathDataItem>}
|
||||
*/
|
||||
const pathData = [];
|
||||
for (let i = 0; i < coords.length; i += 2) {
|
||||
pathData.push({
|
||||
command: i === 0 ? 'M' : 'L',
|
||||
args: coords.slice(i, i + 2),
|
||||
});
|
||||
}
|
||||
if (node.name === 'polygon') {
|
||||
pathData.push({ command: 'z', args: [] });
|
||||
}
|
||||
node.name = 'path';
|
||||
node.attributes.d = stringifyPathData({ pathData, precision });
|
||||
delete node.attributes.points;
|
||||
}
|
||||
|
||||
// optionally convert circle
|
||||
if (node.name === 'circle' && convertArcs) {
|
||||
const cx = Number(node.attributes.cx || '0');
|
||||
const cy = Number(node.attributes.cy || '0');
|
||||
const r = Number(node.attributes.r || '0');
|
||||
if (Number.isNaN(cx - cy + r)) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* @type {Array<PathDataItem>}
|
||||
*/
|
||||
const pathData = [
|
||||
{ command: 'M', args: [cx, cy - r] },
|
||||
{ command: 'A', args: [r, r, 0, 1, 0, cx, cy + r] },
|
||||
{ command: 'A', args: [r, r, 0, 1, 0, cx, cy - r] },
|
||||
{ command: 'z', args: [] },
|
||||
];
|
||||
node.name = 'path';
|
||||
node.attributes.d = stringifyPathData({ pathData, precision });
|
||||
delete node.attributes.cx;
|
||||
delete node.attributes.cy;
|
||||
delete node.attributes.r;
|
||||
}
|
||||
|
||||
// optionally covert ellipse
|
||||
if (node.name === 'ellipse' && convertArcs) {
|
||||
const ecx = Number(node.attributes.cx || '0');
|
||||
const ecy = Number(node.attributes.cy || '0');
|
||||
const rx = Number(node.attributes.rx || '0');
|
||||
const ry = Number(node.attributes.ry || '0');
|
||||
if (Number.isNaN(ecx - ecy + rx - ry)) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* @type {Array<PathDataItem>}
|
||||
*/
|
||||
const pathData = [
|
||||
{ command: 'M', args: [ecx, ecy - ry] },
|
||||
{ command: 'A', args: [rx, ry, 0, 1, 0, ecx, ecy + ry] },
|
||||
{ command: 'A', args: [rx, ry, 0, 1, 0, ecx, ecy - ry] },
|
||||
{ command: 'z', args: [] },
|
||||
];
|
||||
node.name = 'path';
|
||||
node.attributes.d = stringifyPathData({ pathData, precision });
|
||||
delete node.attributes.cx;
|
||||
delete node.attributes.cy;
|
||||
delete node.attributes.rx;
|
||||
delete node.attributes.ry;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
132
node_modules/svgo/plugins/convertStyleToAttrs.js
generated
vendored
Normal file
132
node_modules/svgo/plugins/convertStyleToAttrs.js
generated
vendored
Normal file
@ -0,0 +1,132 @@
|
||||
'use strict';
|
||||
|
||||
exports.name = 'convertStyleToAttrs';
|
||||
|
||||
exports.type = 'perItem';
|
||||
|
||||
exports.active = false;
|
||||
|
||||
exports.description = 'converts style to attributes';
|
||||
|
||||
exports.params = {
|
||||
keepImportant: false,
|
||||
};
|
||||
|
||||
var stylingProps = require('./_collections').attrsGroups.presentation,
|
||||
rEscape = '\\\\(?:[0-9a-f]{1,6}\\s?|\\r\\n|.)', // Like \" or \2051. Code points consume one space.
|
||||
rAttr = '\\s*(' + g('[^:;\\\\]', rEscape) + '*?)\\s*', // attribute name like ‘fill’
|
||||
rSingleQuotes = "'(?:[^'\\n\\r\\\\]|" + rEscape + ")*?(?:'|$)", // string in single quotes: 'smth'
|
||||
rQuotes = '"(?:[^"\\n\\r\\\\]|' + rEscape + ')*?(?:"|$)', // string in double quotes: "smth"
|
||||
rQuotedString = new RegExp('^' + g(rSingleQuotes, rQuotes) + '$'),
|
||||
// Parentheses, E.g.: url(data:image/png;base64,iVBO...).
|
||||
// ':' and ';' inside of it should be threated as is. (Just like in strings.)
|
||||
rParenthesis =
|
||||
'\\(' + g('[^\'"()\\\\]+', rEscape, rSingleQuotes, rQuotes) + '*?' + '\\)',
|
||||
// The value. It can have strings and parentheses (see above). Fallbacks to anything in case of unexpected input.
|
||||
rValue =
|
||||
'\\s*(' +
|
||||
g(
|
||||
'[^!\'"();\\\\]+?',
|
||||
rEscape,
|
||||
rSingleQuotes,
|
||||
rQuotes,
|
||||
rParenthesis,
|
||||
'[^;]*?'
|
||||
) +
|
||||
'*?' +
|
||||
')',
|
||||
// End of declaration. Spaces outside of capturing groups help to do natural trimming.
|
||||
rDeclEnd = '\\s*(?:;\\s*|$)',
|
||||
// Important rule
|
||||
rImportant = '(\\s*!important(?![-(\\w]))?',
|
||||
// Final RegExp to parse CSS declarations.
|
||||
regDeclarationBlock = new RegExp(
|
||||
rAttr + ':' + rValue + rImportant + rDeclEnd,
|
||||
'ig'
|
||||
),
|
||||
// Comments expression. Honors escape sequences and strings.
|
||||
regStripComments = new RegExp(
|
||||
g(rEscape, rSingleQuotes, rQuotes, '/\\*[^]*?\\*/'),
|
||||
'ig'
|
||||
);
|
||||
|
||||
/**
|
||||
* Convert style in attributes. Cleanups comments and illegal declarations (without colon) as a side effect.
|
||||
*
|
||||
* @example
|
||||
* <g style="fill:#000; color: #fff;">
|
||||
* ⬇
|
||||
* <g fill="#000" color="#fff">
|
||||
*
|
||||
* @example
|
||||
* <g style="fill:#000; color: #fff; -webkit-blah: blah">
|
||||
* ⬇
|
||||
* <g fill="#000" color="#fff" style="-webkit-blah: blah">
|
||||
*
|
||||
* @param {Object} item current iteration item
|
||||
* @return {Boolean} if false, item will be filtered out
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*/
|
||||
exports.fn = function (item, params) {
|
||||
if (item.type === 'element' && item.attributes.style != null) {
|
||||
// ['opacity: 1', 'color: #000']
|
||||
let styles = [];
|
||||
const newAttributes = {};
|
||||
|
||||
// Strip CSS comments preserving escape sequences and strings.
|
||||
const styleValue = item.attributes.style.replace(
|
||||
regStripComments,
|
||||
(match) => {
|
||||
return match[0] == '/'
|
||||
? ''
|
||||
: match[0] == '\\' && /[-g-z]/i.test(match[1])
|
||||
? match[1]
|
||||
: match;
|
||||
}
|
||||
);
|
||||
|
||||
regDeclarationBlock.lastIndex = 0;
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
for (var rule; (rule = regDeclarationBlock.exec(styleValue)); ) {
|
||||
if (!params.keepImportant || !rule[3]) {
|
||||
styles.push([rule[1], rule[2]]);
|
||||
}
|
||||
}
|
||||
|
||||
if (styles.length) {
|
||||
styles = styles.filter(function (style) {
|
||||
if (style[0]) {
|
||||
var prop = style[0].toLowerCase(),
|
||||
val = style[1];
|
||||
|
||||
if (rQuotedString.test(val)) {
|
||||
val = val.slice(1, -1);
|
||||
}
|
||||
|
||||
if (stylingProps.includes(prop)) {
|
||||
newAttributes[prop] = val;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
Object.assign(item.attributes, newAttributes);
|
||||
|
||||
if (styles.length) {
|
||||
item.attributes.style = styles
|
||||
.map((declaration) => declaration.join(':'))
|
||||
.join(';');
|
||||
} else {
|
||||
delete item.attributes.style;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function g() {
|
||||
return '(?:' + Array.prototype.join.call(arguments, '|') + ')';
|
||||
}
|
432
node_modules/svgo/plugins/convertTransform.js
generated
vendored
Normal file
432
node_modules/svgo/plugins/convertTransform.js
generated
vendored
Normal file
@ -0,0 +1,432 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @typedef {import('../lib/types').XastElement} XastElement
|
||||
*/
|
||||
|
||||
const { cleanupOutData } = require('../lib/svgo/tools.js');
|
||||
const {
|
||||
transform2js,
|
||||
transformsMultiply,
|
||||
matrixToTransform,
|
||||
} = require('./_transforms.js');
|
||||
|
||||
exports.type = 'visitor';
|
||||
exports.name = 'convertTransform';
|
||||
exports.active = true;
|
||||
exports.description = 'collapses multiple transformations and optimizes it';
|
||||
|
||||
/**
|
||||
* Convert matrices to the short aliases,
|
||||
* convert long translate, scale or rotate transform notations to the shorts ones,
|
||||
* convert transforms to the matrices and multiply them all into one,
|
||||
* remove useless transforms.
|
||||
*
|
||||
* @see https://www.w3.org/TR/SVG11/coords.html#TransformMatrixDefined
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<{
|
||||
* convertToShorts?: boolean,
|
||||
* degPrecision?: number,
|
||||
* floatPrecision?: number,
|
||||
* transformPrecision?: number,
|
||||
* matrixToTransform?: boolean,
|
||||
* shortTranslate?: boolean,
|
||||
* shortScale?: boolean,
|
||||
* shortRotate?: boolean,
|
||||
* removeUseless?: boolean,
|
||||
* collapseIntoOne?: boolean,
|
||||
* leadingZero?: boolean,
|
||||
* negativeExtraSpace?: boolean,
|
||||
* }>}
|
||||
*/
|
||||
exports.fn = (_root, params) => {
|
||||
const {
|
||||
convertToShorts = true,
|
||||
// degPrecision = 3, // transformPrecision (or matrix precision) - 2 by default
|
||||
degPrecision,
|
||||
floatPrecision = 3,
|
||||
transformPrecision = 5,
|
||||
matrixToTransform = true,
|
||||
shortTranslate = true,
|
||||
shortScale = true,
|
||||
shortRotate = true,
|
||||
removeUseless = true,
|
||||
collapseIntoOne = true,
|
||||
leadingZero = true,
|
||||
negativeExtraSpace = false,
|
||||
} = params;
|
||||
const newParams = {
|
||||
convertToShorts,
|
||||
degPrecision,
|
||||
floatPrecision,
|
||||
transformPrecision,
|
||||
matrixToTransform,
|
||||
shortTranslate,
|
||||
shortScale,
|
||||
shortRotate,
|
||||
removeUseless,
|
||||
collapseIntoOne,
|
||||
leadingZero,
|
||||
negativeExtraSpace,
|
||||
};
|
||||
return {
|
||||
element: {
|
||||
enter: (node) => {
|
||||
// transform
|
||||
if (node.attributes.transform != null) {
|
||||
convertTransform(node, 'transform', newParams);
|
||||
}
|
||||
// gradientTransform
|
||||
if (node.attributes.gradientTransform != null) {
|
||||
convertTransform(node, 'gradientTransform', newParams);
|
||||
}
|
||||
// patternTransform
|
||||
if (node.attributes.patternTransform != null) {
|
||||
convertTransform(node, 'patternTransform', newParams);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* convertToShorts: boolean,
|
||||
* degPrecision?: number,
|
||||
* floatPrecision: number,
|
||||
* transformPrecision: number,
|
||||
* matrixToTransform: boolean,
|
||||
* shortTranslate: boolean,
|
||||
* shortScale: boolean,
|
||||
* shortRotate: boolean,
|
||||
* removeUseless: boolean,
|
||||
* collapseIntoOne: boolean,
|
||||
* leadingZero: boolean,
|
||||
* negativeExtraSpace: boolean,
|
||||
* }} TransformParams
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {{ name: string, data: Array<number> }} TransformItem
|
||||
*/
|
||||
|
||||
/**
|
||||
* Main function.
|
||||
*
|
||||
* @type {(item: XastElement, attrName: string, params: TransformParams) => void}
|
||||
*/
|
||||
const convertTransform = (item, attrName, params) => {
|
||||
let data = transform2js(item.attributes[attrName]);
|
||||
params = definePrecision(data, params);
|
||||
|
||||
if (params.collapseIntoOne && data.length > 1) {
|
||||
data = [transformsMultiply(data)];
|
||||
}
|
||||
|
||||
if (params.convertToShorts) {
|
||||
data = convertToShorts(data, params);
|
||||
} else {
|
||||
data.forEach((item) => roundTransform(item, params));
|
||||
}
|
||||
|
||||
if (params.removeUseless) {
|
||||
data = removeUseless(data);
|
||||
}
|
||||
|
||||
if (data.length) {
|
||||
item.attributes[attrName] = js2transform(data, params);
|
||||
} else {
|
||||
delete item.attributes[attrName];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines precision to work with certain parts.
|
||||
* transformPrecision - for scale and four first matrix parameters (needs a better precision due to multiplying),
|
||||
* floatPrecision - for translate including two last matrix and rotate parameters,
|
||||
* degPrecision - for rotate and skew. By default it's equal to (rougly)
|
||||
* transformPrecision - 2 or floatPrecision whichever is lower. Can be set in params.
|
||||
*
|
||||
* @type {(data: Array<TransformItem>, params: TransformParams) => TransformParams}
|
||||
*
|
||||
* clone params so it don't affect other elements transformations.
|
||||
*/
|
||||
const definePrecision = (data, { ...newParams }) => {
|
||||
const matrixData = [];
|
||||
for (const item of data) {
|
||||
if (item.name == 'matrix') {
|
||||
matrixData.push(...item.data.slice(0, 4));
|
||||
}
|
||||
}
|
||||
let significantDigits = newParams.transformPrecision;
|
||||
// Limit transform precision with matrix one. Calculating with larger precision doesn't add any value.
|
||||
if (matrixData.length) {
|
||||
newParams.transformPrecision = Math.min(
|
||||
newParams.transformPrecision,
|
||||
Math.max.apply(Math, matrixData.map(floatDigits)) ||
|
||||
newParams.transformPrecision
|
||||
);
|
||||
significantDigits = Math.max.apply(
|
||||
Math,
|
||||
matrixData.map(
|
||||
(n) => n.toString().replace(/\D+/g, '').length // Number of digits in a number. 123.45 → 5
|
||||
)
|
||||
);
|
||||
}
|
||||
// No sense in angle precision more then number of significant digits in matrix.
|
||||
if (newParams.degPrecision == null) {
|
||||
newParams.degPrecision = Math.max(
|
||||
0,
|
||||
Math.min(newParams.floatPrecision, significantDigits - 2)
|
||||
);
|
||||
}
|
||||
return newParams;
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {(data: Array<number>, params: TransformParams) => Array<number>}
|
||||
*/
|
||||
const degRound = (data, params) => {
|
||||
if (
|
||||
params.degPrecision != null &&
|
||||
params.degPrecision >= 1 &&
|
||||
params.floatPrecision < 20
|
||||
) {
|
||||
return smartRound(params.degPrecision, data);
|
||||
} else {
|
||||
return round(data);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @type {(data: Array<number>, params: TransformParams) => Array<number>}
|
||||
*/
|
||||
const floatRound = (data, params) => {
|
||||
if (params.floatPrecision >= 1 && params.floatPrecision < 20) {
|
||||
return smartRound(params.floatPrecision, data);
|
||||
} else {
|
||||
return round(data);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {(data: Array<number>, params: TransformParams) => Array<number>}
|
||||
*/
|
||||
const transformRound = (data, params) => {
|
||||
if (params.transformPrecision >= 1 && params.floatPrecision < 20) {
|
||||
return smartRound(params.transformPrecision, data);
|
||||
} else {
|
||||
return round(data);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns number of digits after the point. 0.125 → 3
|
||||
*
|
||||
* @type {(n: number) => number}
|
||||
*/
|
||||
const floatDigits = (n) => {
|
||||
const str = n.toString();
|
||||
return str.slice(str.indexOf('.')).length - 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert transforms to the shorthand alternatives.
|
||||
*
|
||||
* @type {(transforms: Array<TransformItem>, params: TransformParams) => Array<TransformItem>}
|
||||
*/
|
||||
const convertToShorts = (transforms, params) => {
|
||||
for (var i = 0; i < transforms.length; i++) {
|
||||
var transform = transforms[i];
|
||||
|
||||
// convert matrix to the short aliases
|
||||
if (params.matrixToTransform && transform.name === 'matrix') {
|
||||
var decomposed = matrixToTransform(transform, params);
|
||||
if (
|
||||
js2transform(decomposed, params).length <=
|
||||
js2transform([transform], params).length
|
||||
) {
|
||||
transforms.splice(i, 1, ...decomposed);
|
||||
}
|
||||
transform = transforms[i];
|
||||
}
|
||||
|
||||
// fixed-point numbers
|
||||
// 12.754997 → 12.755
|
||||
roundTransform(transform, params);
|
||||
|
||||
// convert long translate transform notation to the shorts one
|
||||
// translate(10 0) → translate(10)
|
||||
if (
|
||||
params.shortTranslate &&
|
||||
transform.name === 'translate' &&
|
||||
transform.data.length === 2 &&
|
||||
!transform.data[1]
|
||||
) {
|
||||
transform.data.pop();
|
||||
}
|
||||
|
||||
// convert long scale transform notation to the shorts one
|
||||
// scale(2 2) → scale(2)
|
||||
if (
|
||||
params.shortScale &&
|
||||
transform.name === 'scale' &&
|
||||
transform.data.length === 2 &&
|
||||
transform.data[0] === transform.data[1]
|
||||
) {
|
||||
transform.data.pop();
|
||||
}
|
||||
|
||||
// convert long rotate transform notation to the short one
|
||||
// translate(cx cy) rotate(a) translate(-cx -cy) → rotate(a cx cy)
|
||||
if (
|
||||
params.shortRotate &&
|
||||
transforms[i - 2] &&
|
||||
transforms[i - 2].name === 'translate' &&
|
||||
transforms[i - 1].name === 'rotate' &&
|
||||
transforms[i].name === 'translate' &&
|
||||
transforms[i - 2].data[0] === -transforms[i].data[0] &&
|
||||
transforms[i - 2].data[1] === -transforms[i].data[1]
|
||||
) {
|
||||
transforms.splice(i - 2, 3, {
|
||||
name: 'rotate',
|
||||
data: [
|
||||
transforms[i - 1].data[0],
|
||||
transforms[i - 2].data[0],
|
||||
transforms[i - 2].data[1],
|
||||
],
|
||||
});
|
||||
|
||||
// splice compensation
|
||||
i -= 2;
|
||||
}
|
||||
}
|
||||
|
||||
return transforms;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove useless transforms.
|
||||
*
|
||||
* @type {(trasforms: Array<TransformItem>) => Array<TransformItem>}
|
||||
*/
|
||||
const removeUseless = (transforms) => {
|
||||
return transforms.filter((transform) => {
|
||||
// translate(0), rotate(0[, cx, cy]), skewX(0), skewY(0)
|
||||
if (
|
||||
(['translate', 'rotate', 'skewX', 'skewY'].indexOf(transform.name) > -1 &&
|
||||
(transform.data.length == 1 || transform.name == 'rotate') &&
|
||||
!transform.data[0]) ||
|
||||
// translate(0, 0)
|
||||
(transform.name == 'translate' &&
|
||||
!transform.data[0] &&
|
||||
!transform.data[1]) ||
|
||||
// scale(1)
|
||||
(transform.name == 'scale' &&
|
||||
transform.data[0] == 1 &&
|
||||
(transform.data.length < 2 || transform.data[1] == 1)) ||
|
||||
// matrix(1 0 0 1 0 0)
|
||||
(transform.name == 'matrix' &&
|
||||
transform.data[0] == 1 &&
|
||||
transform.data[3] == 1 &&
|
||||
!(
|
||||
transform.data[1] ||
|
||||
transform.data[2] ||
|
||||
transform.data[4] ||
|
||||
transform.data[5]
|
||||
))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert transforms JS representation to string.
|
||||
*
|
||||
* @type {(transformJS: Array<TransformItem>, params: TransformParams) => string}
|
||||
*/
|
||||
const js2transform = (transformJS, params) => {
|
||||
var transformString = '';
|
||||
|
||||
// collect output value string
|
||||
transformJS.forEach((transform) => {
|
||||
roundTransform(transform, params);
|
||||
transformString +=
|
||||
(transformString && ' ') +
|
||||
transform.name +
|
||||
'(' +
|
||||
cleanupOutData(transform.data, params) +
|
||||
')';
|
||||
});
|
||||
|
||||
return transformString;
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {(transform: TransformItem, params: TransformParams) => TransformItem}
|
||||
*/
|
||||
const roundTransform = (transform, params) => {
|
||||
switch (transform.name) {
|
||||
case 'translate':
|
||||
transform.data = floatRound(transform.data, params);
|
||||
break;
|
||||
case 'rotate':
|
||||
transform.data = [
|
||||
...degRound(transform.data.slice(0, 1), params),
|
||||
...floatRound(transform.data.slice(1), params),
|
||||
];
|
||||
break;
|
||||
case 'skewX':
|
||||
case 'skewY':
|
||||
transform.data = degRound(transform.data, params);
|
||||
break;
|
||||
case 'scale':
|
||||
transform.data = transformRound(transform.data, params);
|
||||
break;
|
||||
case 'matrix':
|
||||
transform.data = [
|
||||
...transformRound(transform.data.slice(0, 4), params),
|
||||
...floatRound(transform.data.slice(4), params),
|
||||
];
|
||||
break;
|
||||
}
|
||||
return transform;
|
||||
};
|
||||
|
||||
/**
|
||||
* Rounds numbers in array.
|
||||
*
|
||||
* @type {(data: Array<number>) => Array<number>}
|
||||
*/
|
||||
const round = (data) => {
|
||||
return data.map(Math.round);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrease accuracy of floating-point numbers
|
||||
* in transforms keeping a specified number of decimals.
|
||||
* Smart rounds values like 2.349 to 2.35.
|
||||
*
|
||||
* @type {(precision: number, data: Array<number>) => Array<number>}
|
||||
*/
|
||||
const smartRound = (precision, data) => {
|
||||
for (
|
||||
var i = data.length,
|
||||
tolerance = +Math.pow(0.1, precision).toFixed(precision);
|
||||
i--;
|
||||
|
||||
) {
|
||||
if (Number(data[i].toFixed(precision)) !== data[i]) {
|
||||
var rounded = +data[i].toFixed(precision - 1);
|
||||
data[i] =
|
||||
+Math.abs(rounded - data[i]).toFixed(precision + 1) >= tolerance
|
||||
? +data[i].toFixed(precision)
|
||||
: rounded;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
};
|
379
node_modules/svgo/plugins/inlineStyles.js
generated
vendored
Normal file
379
node_modules/svgo/plugins/inlineStyles.js
generated
vendored
Normal file
@ -0,0 +1,379 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @typedef {import('../lib/types').Specificity} Specificity
|
||||
* @typedef {import('../lib/types').XastElement} XastElement
|
||||
* @typedef {import('../lib/types').XastParent} XastParent
|
||||
*/
|
||||
|
||||
const csstree = require('css-tree');
|
||||
// @ts-ignore not defined in @types/csso
|
||||
const specificity = require('csso/lib/restructure/prepare/specificity');
|
||||
const stable = require('stable');
|
||||
const {
|
||||
visitSkip,
|
||||
querySelectorAll,
|
||||
detachNodeFromParent,
|
||||
} = require('../lib/xast.js');
|
||||
|
||||
exports.type = 'visitor';
|
||||
exports.name = 'inlineStyles';
|
||||
exports.active = true;
|
||||
exports.description = 'inline styles (additional options)';
|
||||
|
||||
/**
|
||||
* Compares two selector specificities.
|
||||
* extracted from https://github.com/keeganstreet/specificity/blob/master/specificity.js#L211
|
||||
*
|
||||
* @type {(a: Specificity, b: Specificity) => number}
|
||||
*/
|
||||
const compareSpecificity = (a, b) => {
|
||||
for (var i = 0; i < 4; i += 1) {
|
||||
if (a[i] < b[i]) {
|
||||
return -1;
|
||||
} else if (a[i] > b[i]) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Moves + merges styles from style elements to element styles
|
||||
*
|
||||
* Options
|
||||
* onlyMatchedOnce (default: true)
|
||||
* inline only selectors that match once
|
||||
*
|
||||
* removeMatchedSelectors (default: true)
|
||||
* clean up matched selectors,
|
||||
* leave selectors that hadn't matched
|
||||
*
|
||||
* useMqs (default: ['', 'screen'])
|
||||
* what media queries to be used
|
||||
* empty string element for styles outside media queries
|
||||
*
|
||||
* usePseudos (default: [''])
|
||||
* what pseudo-classes/-elements to be used
|
||||
* empty string element for all non-pseudo-classes and/or -elements
|
||||
*
|
||||
* @author strarsis <strarsis@gmail.com>
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<{
|
||||
* onlyMatchedOnce?: boolean,
|
||||
* removeMatchedSelectors?: boolean,
|
||||
* useMqs?: Array<string>,
|
||||
* usePseudos?: Array<string>
|
||||
* }>}
|
||||
*/
|
||||
exports.fn = (root, params) => {
|
||||
const {
|
||||
onlyMatchedOnce = true,
|
||||
removeMatchedSelectors = true,
|
||||
useMqs = ['', 'screen'],
|
||||
usePseudos = [''],
|
||||
} = params;
|
||||
|
||||
/**
|
||||
* @type {Array<{ node: XastElement, parentNode: XastParent, cssAst: csstree.StyleSheet }>}
|
||||
*/
|
||||
const styles = [];
|
||||
/**
|
||||
* @type {Array<{
|
||||
* node: csstree.Selector,
|
||||
* item: csstree.ListItem<csstree.CssNode>,
|
||||
* rule: csstree.Rule,
|
||||
* matchedElements?: Array<XastElement>
|
||||
* }>}
|
||||
*/
|
||||
let selectors = [];
|
||||
|
||||
return {
|
||||
element: {
|
||||
enter: (node, parentNode) => {
|
||||
// skip <foreignObject /> content
|
||||
if (node.name === 'foreignObject') {
|
||||
return visitSkip;
|
||||
}
|
||||
// collect only non-empty <style /> elements
|
||||
if (node.name !== 'style' || node.children.length === 0) {
|
||||
return;
|
||||
}
|
||||
// values other than the empty string or text/css are not used
|
||||
if (
|
||||
node.attributes.type != null &&
|
||||
node.attributes.type !== '' &&
|
||||
node.attributes.type !== 'text/css'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// parse css in style element
|
||||
let cssText = '';
|
||||
for (const child of node.children) {
|
||||
if (child.type === 'text' || child.type === 'cdata') {
|
||||
cssText += child.value;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @type {null | csstree.CssNode}
|
||||
*/
|
||||
let cssAst = null;
|
||||
try {
|
||||
cssAst = csstree.parse(cssText, {
|
||||
parseValue: false,
|
||||
parseCustomProperty: false,
|
||||
});
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (cssAst.type === 'StyleSheet') {
|
||||
styles.push({ node, parentNode, cssAst });
|
||||
}
|
||||
|
||||
// collect selectors
|
||||
csstree.walk(cssAst, {
|
||||
visit: 'Selector',
|
||||
enter(node, item) {
|
||||
const atrule = this.atrule;
|
||||
const rule = this.rule;
|
||||
if (rule == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// skip media queries not included into useMqs param
|
||||
let mq = '';
|
||||
if (atrule != null) {
|
||||
mq = atrule.name;
|
||||
if (atrule.prelude != null) {
|
||||
mq += ` ${csstree.generate(atrule.prelude)}`;
|
||||
}
|
||||
}
|
||||
if (useMqs.includes(mq) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Array<{
|
||||
* item: csstree.ListItem<csstree.CssNode>,
|
||||
* list: csstree.List<csstree.CssNode>
|
||||
* }>}
|
||||
*/
|
||||
const pseudos = [];
|
||||
if (node.type === 'Selector') {
|
||||
node.children.each((childNode, childItem, childList) => {
|
||||
if (
|
||||
childNode.type === 'PseudoClassSelector' ||
|
||||
childNode.type === 'PseudoElementSelector'
|
||||
) {
|
||||
pseudos.push({ item: childItem, list: childList });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// skip pseudo classes and pseudo elements not includes into usePseudos param
|
||||
const pseudoSelectors = csstree.generate({
|
||||
type: 'Selector',
|
||||
children: new csstree.List().fromArray(
|
||||
pseudos.map((pseudo) => pseudo.item.data)
|
||||
),
|
||||
});
|
||||
if (usePseudos.includes(pseudoSelectors) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// remove pseudo classes and elements to allow querySelector match elements
|
||||
// TODO this is not very accurate since some pseudo classes like first-child
|
||||
// are used for selection
|
||||
for (const pseudo of pseudos) {
|
||||
pseudo.list.remove(pseudo.item);
|
||||
}
|
||||
|
||||
selectors.push({ node, item, rule });
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
root: {
|
||||
exit: () => {
|
||||
if (styles.length === 0) {
|
||||
return;
|
||||
}
|
||||
// stable sort selectors
|
||||
const sortedSelectors = stable(selectors, (a, b) => {
|
||||
const aSpecificity = specificity(a.item.data);
|
||||
const bSpecificity = specificity(b.item.data);
|
||||
return compareSpecificity(aSpecificity, bSpecificity);
|
||||
}).reverse();
|
||||
|
||||
for (const selector of sortedSelectors) {
|
||||
// match selectors
|
||||
const selectorText = csstree.generate(selector.item.data);
|
||||
/**
|
||||
* @type {Array<XastElement>}
|
||||
*/
|
||||
const matchedElements = [];
|
||||
try {
|
||||
for (const node of querySelectorAll(root, selectorText)) {
|
||||
if (node.type === 'element') {
|
||||
matchedElements.push(node);
|
||||
}
|
||||
}
|
||||
} catch (selectError) {
|
||||
continue;
|
||||
}
|
||||
// nothing selected
|
||||
if (matchedElements.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// apply styles to matched elements
|
||||
// skip selectors that match more than once if option onlyMatchedOnce is enabled
|
||||
if (onlyMatchedOnce && matchedElements.length > 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// apply <style/> to matched elements
|
||||
for (const selectedEl of matchedElements) {
|
||||
const styleDeclarationList = csstree.parse(
|
||||
selectedEl.attributes.style == null
|
||||
? ''
|
||||
: selectedEl.attributes.style,
|
||||
{
|
||||
context: 'declarationList',
|
||||
parseValue: false,
|
||||
}
|
||||
);
|
||||
if (styleDeclarationList.type !== 'DeclarationList') {
|
||||
continue;
|
||||
}
|
||||
const styleDeclarationItems = new Map();
|
||||
csstree.walk(styleDeclarationList, {
|
||||
visit: 'Declaration',
|
||||
enter(node, item) {
|
||||
styleDeclarationItems.set(node.property, item);
|
||||
},
|
||||
});
|
||||
// merge declarations
|
||||
csstree.walk(selector.rule, {
|
||||
visit: 'Declaration',
|
||||
enter(ruleDeclaration) {
|
||||
// existing inline styles have higher priority
|
||||
// no inline styles, external styles, external styles used
|
||||
// inline styles, external styles same priority as inline styles, inline styles used
|
||||
// inline styles, external styles higher priority than inline styles, external styles used
|
||||
const matchedItem = styleDeclarationItems.get(
|
||||
ruleDeclaration.property
|
||||
);
|
||||
const ruleDeclarationItem =
|
||||
styleDeclarationList.children.createItem(ruleDeclaration);
|
||||
if (matchedItem == null) {
|
||||
styleDeclarationList.children.append(ruleDeclarationItem);
|
||||
} else if (
|
||||
matchedItem.data.important !== true &&
|
||||
ruleDeclaration.important === true
|
||||
) {
|
||||
styleDeclarationList.children.replace(
|
||||
matchedItem,
|
||||
ruleDeclarationItem
|
||||
);
|
||||
styleDeclarationItems.set(
|
||||
ruleDeclaration.property,
|
||||
ruleDeclarationItem
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
selectedEl.attributes.style =
|
||||
csstree.generate(styleDeclarationList);
|
||||
}
|
||||
|
||||
if (
|
||||
removeMatchedSelectors &&
|
||||
matchedElements.length !== 0 &&
|
||||
selector.rule.prelude.type === 'SelectorList'
|
||||
) {
|
||||
// clean up matching simple selectors if option removeMatchedSelectors is enabled
|
||||
selector.rule.prelude.children.remove(selector.item);
|
||||
}
|
||||
selector.matchedElements = matchedElements;
|
||||
}
|
||||
|
||||
// no further processing required
|
||||
if (removeMatchedSelectors === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// clean up matched class + ID attribute values
|
||||
for (const selector of sortedSelectors) {
|
||||
if (selector.matchedElements == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (onlyMatchedOnce && selector.matchedElements.length > 1) {
|
||||
// skip selectors that match more than once if option onlyMatchedOnce is enabled
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const selectedEl of selector.matchedElements) {
|
||||
// class
|
||||
const classList = new Set(
|
||||
selectedEl.attributes.class == null
|
||||
? null
|
||||
: selectedEl.attributes.class.split(' ')
|
||||
);
|
||||
const firstSubSelector = selector.node.children.first();
|
||||
if (
|
||||
firstSubSelector != null &&
|
||||
firstSubSelector.type === 'ClassSelector'
|
||||
) {
|
||||
classList.delete(firstSubSelector.name);
|
||||
}
|
||||
if (classList.size === 0) {
|
||||
delete selectedEl.attributes.class;
|
||||
} else {
|
||||
selectedEl.attributes.class = Array.from(classList).join(' ');
|
||||
}
|
||||
|
||||
// ID
|
||||
if (
|
||||
firstSubSelector != null &&
|
||||
firstSubSelector.type === 'IdSelector'
|
||||
) {
|
||||
if (selectedEl.attributes.id === firstSubSelector.name) {
|
||||
delete selectedEl.attributes.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const style of styles) {
|
||||
csstree.walk(style.cssAst, {
|
||||
visit: 'Rule',
|
||||
enter: function (node, item, list) {
|
||||
// clean up <style/> rulesets without any css selectors left
|
||||
if (
|
||||
node.type === 'Rule' &&
|
||||
node.prelude.type === 'SelectorList' &&
|
||||
node.prelude.children.isEmpty()
|
||||
) {
|
||||
list.remove(item);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (style.cssAst.children.isEmpty()) {
|
||||
// remove emtpy style element
|
||||
detachNodeFromParent(style.node, style.parentNode);
|
||||
} else {
|
||||
// update style element if any styles left
|
||||
const firstChild = style.node.children[0];
|
||||
if (firstChild.type === 'text' || firstChild.type === 'cdata') {
|
||||
firstChild.value = csstree.generate(style.cssAst);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
104
node_modules/svgo/plugins/mergePaths.js
generated
vendored
Normal file
104
node_modules/svgo/plugins/mergePaths.js
generated
vendored
Normal file
@ -0,0 +1,104 @@
|
||||
'use strict';
|
||||
|
||||
const { detachNodeFromParent } = require('../lib/xast.js');
|
||||
const { collectStylesheet, computeStyle } = require('../lib/style.js');
|
||||
const { path2js, js2path, intersects } = require('./_path.js');
|
||||
|
||||
exports.type = 'visitor';
|
||||
exports.name = 'mergePaths';
|
||||
exports.active = true;
|
||||
exports.description = 'merges multiple paths in one if possible';
|
||||
|
||||
/**
|
||||
* Merge multiple Paths into one.
|
||||
*
|
||||
* @author Kir Belevich, Lev Solntsev
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<{
|
||||
* force?: boolean,
|
||||
* floatPrecision?: number,
|
||||
* noSpaceAfterFlags?: boolean
|
||||
* }>}
|
||||
*/
|
||||
exports.fn = (root, params) => {
|
||||
const {
|
||||
force = false,
|
||||
floatPrecision,
|
||||
noSpaceAfterFlags = false, // a20 60 45 0 1 30 20 → a20 60 45 0130 20
|
||||
} = params;
|
||||
const stylesheet = collectStylesheet(root);
|
||||
|
||||
return {
|
||||
element: {
|
||||
enter: (node) => {
|
||||
let prevChild = null;
|
||||
|
||||
for (const child of node.children) {
|
||||
// skip if previous element is not path or contains animation elements
|
||||
if (
|
||||
prevChild == null ||
|
||||
prevChild.type !== 'element' ||
|
||||
prevChild.name !== 'path' ||
|
||||
prevChild.children.length !== 0 ||
|
||||
prevChild.attributes.d == null
|
||||
) {
|
||||
prevChild = child;
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip if element is not path or contains animation elements
|
||||
if (
|
||||
child.type !== 'element' ||
|
||||
child.name !== 'path' ||
|
||||
child.children.length !== 0 ||
|
||||
child.attributes.d == null
|
||||
) {
|
||||
prevChild = child;
|
||||
continue;
|
||||
}
|
||||
|
||||
// preserve paths with markers
|
||||
const computedStyle = computeStyle(stylesheet, child);
|
||||
if (
|
||||
computedStyle['marker-start'] ||
|
||||
computedStyle['marker-mid'] ||
|
||||
computedStyle['marker-end']
|
||||
) {
|
||||
prevChild = child;
|
||||
continue;
|
||||
}
|
||||
|
||||
const prevChildAttrs = Object.keys(prevChild.attributes);
|
||||
const childAttrs = Object.keys(child.attributes);
|
||||
let attributesAreEqual = prevChildAttrs.length === childAttrs.length;
|
||||
for (const name of childAttrs) {
|
||||
if (name !== 'd') {
|
||||
if (
|
||||
prevChild.attributes[name] == null ||
|
||||
prevChild.attributes[name] !== child.attributes[name]
|
||||
) {
|
||||
attributesAreEqual = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
const prevPathJS = path2js(prevChild);
|
||||
const curPathJS = path2js(child);
|
||||
|
||||
if (
|
||||
attributesAreEqual &&
|
||||
(force || !intersects(prevPathJS, curPathJS))
|
||||
) {
|
||||
js2path(prevChild, prevPathJS.concat(curPathJS), {
|
||||
floatPrecision,
|
||||
noSpaceAfterFlags,
|
||||
});
|
||||
detachNodeFromParent(child, node);
|
||||
continue;
|
||||
}
|
||||
|
||||
prevChild = child;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
93
node_modules/svgo/plugins/mergeStyles.js
generated
vendored
Normal file
93
node_modules/svgo/plugins/mergeStyles.js
generated
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @typedef {import('../lib/types').XastElement} XastElement
|
||||
*/
|
||||
|
||||
const { visitSkip, detachNodeFromParent } = require('../lib/xast.js');
|
||||
const JSAPI = require('../lib/svgo/jsAPI.js');
|
||||
|
||||
exports.name = 'mergeStyles';
|
||||
exports.type = 'visitor';
|
||||
exports.active = true;
|
||||
exports.description = 'merge multiple style elements into one';
|
||||
|
||||
/**
|
||||
* Merge multiple style elements into one.
|
||||
*
|
||||
* @author strarsis <strarsis@gmail.com>
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<void>}
|
||||
*/
|
||||
exports.fn = () => {
|
||||
/**
|
||||
* @type {null | XastElement}
|
||||
*/
|
||||
let firstStyleElement = null;
|
||||
let collectedStyles = '';
|
||||
let styleContentType = 'text';
|
||||
|
||||
return {
|
||||
element: {
|
||||
enter: (node, parentNode) => {
|
||||
// skip <foreignObject> content
|
||||
if (node.name === 'foreignObject') {
|
||||
return visitSkip;
|
||||
}
|
||||
|
||||
// collect style elements
|
||||
if (node.name !== 'style') {
|
||||
return;
|
||||
}
|
||||
|
||||
// skip <style> with invalid type attribute
|
||||
if (
|
||||
node.attributes.type != null &&
|
||||
node.attributes.type !== '' &&
|
||||
node.attributes.type !== 'text/css'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// extract style element content
|
||||
let css = '';
|
||||
for (const child of node.children) {
|
||||
if (child.type === 'text') {
|
||||
css += child.value;
|
||||
}
|
||||
if (child.type === 'cdata') {
|
||||
styleContentType = 'cdata';
|
||||
css += child.value;
|
||||
}
|
||||
}
|
||||
|
||||
// remove empty style elements
|
||||
if (css.trim().length === 0) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
return;
|
||||
}
|
||||
|
||||
// collect css and wrap with media query if present in attribute
|
||||
if (node.attributes.media == null) {
|
||||
collectedStyles += css;
|
||||
} else {
|
||||
collectedStyles += `@media ${node.attributes.media}{${css}}`;
|
||||
delete node.attributes.media;
|
||||
}
|
||||
|
||||
// combine collected styles in the first style element
|
||||
if (firstStyleElement == null) {
|
||||
firstStyleElement = node;
|
||||
} else {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
firstStyleElement.children = [
|
||||
new JSAPI(
|
||||
{ type: styleContentType, value: collectedStyles },
|
||||
firstStyleElement
|
||||
),
|
||||
];
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
148
node_modules/svgo/plugins/minifyStyles.js
generated
vendored
Normal file
148
node_modules/svgo/plugins/minifyStyles.js
generated
vendored
Normal file
@ -0,0 +1,148 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @typedef {import('../lib/types').XastElement} XastElement
|
||||
*/
|
||||
|
||||
const csso = require('csso');
|
||||
|
||||
exports.type = 'visitor';
|
||||
exports.name = 'minifyStyles';
|
||||
exports.active = true;
|
||||
exports.description =
|
||||
'minifies styles and removes unused styles based on usage data';
|
||||
|
||||
/**
|
||||
* Minifies styles (<style> element + style attribute) using CSSO
|
||||
*
|
||||
* @author strarsis <strarsis@gmail.com>
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<csso.MinifyOptions & Omit<csso.CompressOptions, 'usage'> & {
|
||||
* usage?: boolean | {
|
||||
* force?: boolean,
|
||||
* ids?: boolean,
|
||||
* classes?: boolean,
|
||||
* tags?: boolean
|
||||
* }
|
||||
* }>}
|
||||
*/
|
||||
exports.fn = (_root, { usage, ...params }) => {
|
||||
let enableTagsUsage = true;
|
||||
let enableIdsUsage = true;
|
||||
let enableClassesUsage = true;
|
||||
// force to use usage data even if it unsafe (document contains <script> or on* attributes)
|
||||
let forceUsageDeoptimized = false;
|
||||
if (typeof usage === 'boolean') {
|
||||
enableTagsUsage = usage;
|
||||
enableIdsUsage = usage;
|
||||
enableClassesUsage = usage;
|
||||
} else if (usage) {
|
||||
enableTagsUsage = usage.tags == null ? true : usage.tags;
|
||||
enableIdsUsage = usage.ids == null ? true : usage.ids;
|
||||
enableClassesUsage = usage.classes == null ? true : usage.classes;
|
||||
forceUsageDeoptimized = usage.force == null ? false : usage.force;
|
||||
}
|
||||
/**
|
||||
* @type {Array<XastElement>}
|
||||
*/
|
||||
const styleElements = [];
|
||||
/**
|
||||
* @type {Array<XastElement>}
|
||||
*/
|
||||
const elementsWithStyleAttributes = [];
|
||||
let deoptimized = false;
|
||||
/**
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
const tagsUsage = new Set();
|
||||
/**
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
const idsUsage = new Set();
|
||||
/**
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
const classesUsage = new Set();
|
||||
|
||||
return {
|
||||
element: {
|
||||
enter: (node) => {
|
||||
// detect deoptimisations
|
||||
if (node.name === 'script') {
|
||||
deoptimized = true;
|
||||
}
|
||||
for (const name of Object.keys(node.attributes)) {
|
||||
if (name.startsWith('on')) {
|
||||
deoptimized = true;
|
||||
}
|
||||
}
|
||||
// collect tags, ids and classes usage
|
||||
tagsUsage.add(node.name);
|
||||
if (node.attributes.id != null) {
|
||||
idsUsage.add(node.attributes.id);
|
||||
}
|
||||
if (node.attributes.class != null) {
|
||||
for (const className of node.attributes.class.split(/\s+/)) {
|
||||
classesUsage.add(className);
|
||||
}
|
||||
}
|
||||
// collect style elements or elements with style attribute
|
||||
if (node.name === 'style' && node.children.length !== 0) {
|
||||
styleElements.push(node);
|
||||
} else if (node.attributes.style != null) {
|
||||
elementsWithStyleAttributes.push(node);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
root: {
|
||||
exit: () => {
|
||||
/**
|
||||
* @type {csso.Usage}
|
||||
*/
|
||||
const cssoUsage = {};
|
||||
if (deoptimized === false || forceUsageDeoptimized === true) {
|
||||
if (enableTagsUsage && tagsUsage.size !== 0) {
|
||||
cssoUsage.tags = Array.from(tagsUsage);
|
||||
}
|
||||
if (enableIdsUsage && idsUsage.size !== 0) {
|
||||
cssoUsage.ids = Array.from(idsUsage);
|
||||
}
|
||||
if (enableClassesUsage && classesUsage.size !== 0) {
|
||||
cssoUsage.classes = Array.from(classesUsage);
|
||||
}
|
||||
}
|
||||
// minify style elements
|
||||
for (const node of styleElements) {
|
||||
if (
|
||||
node.children[0].type === 'text' ||
|
||||
node.children[0].type === 'cdata'
|
||||
) {
|
||||
const cssText = node.children[0].value;
|
||||
const minified = csso.minify(cssText, {
|
||||
...params,
|
||||
usage: cssoUsage,
|
||||
}).css;
|
||||
// preserve cdata if necessary
|
||||
// TODO split cdata -> text optimisation into separate plugin
|
||||
if (cssText.indexOf('>') >= 0 || cssText.indexOf('<') >= 0) {
|
||||
node.children[0].type = 'cdata';
|
||||
node.children[0].value = minified;
|
||||
} else {
|
||||
node.children[0].type = 'text';
|
||||
node.children[0].value = minified;
|
||||
}
|
||||
}
|
||||
}
|
||||
// minify style attributes
|
||||
for (const node of elementsWithStyleAttributes) {
|
||||
// style attribute
|
||||
const elemStyle = node.attributes.style;
|
||||
node.attributes.style = csso.minifyBlock(elemStyle, {
|
||||
...params,
|
||||
}).css;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
130
node_modules/svgo/plugins/moveElemsAttrsToGroup.js
generated
vendored
Normal file
130
node_modules/svgo/plugins/moveElemsAttrsToGroup.js
generated
vendored
Normal file
@ -0,0 +1,130 @@
|
||||
'use strict';
|
||||
|
||||
const { visit } = require('../lib/xast.js');
|
||||
const { inheritableAttrs, pathElems } = require('./_collections.js');
|
||||
|
||||
exports.type = 'visitor';
|
||||
exports.name = 'moveElemsAttrsToGroup';
|
||||
exports.active = true;
|
||||
exports.description = 'Move common attributes of group children to the group';
|
||||
|
||||
/**
|
||||
* Move common attributes of group children to the group
|
||||
*
|
||||
* @example
|
||||
* <g attr1="val1">
|
||||
* <g attr2="val2">
|
||||
* text
|
||||
* </g>
|
||||
* <circle attr2="val2" attr3="val3"/>
|
||||
* </g>
|
||||
* ⬇
|
||||
* <g attr1="val1" attr2="val2">
|
||||
* <g>
|
||||
* text
|
||||
* </g>
|
||||
* <circle attr3="val3"/>
|
||||
* </g>
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<void>}
|
||||
*/
|
||||
exports.fn = (root) => {
|
||||
// find if any style element is present
|
||||
let deoptimizedWithStyles = false;
|
||||
visit(root, {
|
||||
element: {
|
||||
enter: (node) => {
|
||||
if (node.name === 'style') {
|
||||
deoptimizedWithStyles = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
element: {
|
||||
exit: (node) => {
|
||||
// process only groups with more than 1 children
|
||||
if (node.name !== 'g' || node.children.length <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// deoptimize the plugin when style elements are present
|
||||
// selectors may rely on id, classes or tag names
|
||||
if (deoptimizedWithStyles) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* find common attributes in group children
|
||||
* @type {Map<string, string>}
|
||||
*/
|
||||
const commonAttributes = new Map();
|
||||
let initial = true;
|
||||
let everyChildIsPath = true;
|
||||
for (const child of node.children) {
|
||||
if (child.type === 'element') {
|
||||
if (pathElems.includes(child.name) === false) {
|
||||
everyChildIsPath = false;
|
||||
}
|
||||
if (initial) {
|
||||
initial = false;
|
||||
// collect all inheritable attributes from first child element
|
||||
for (const [name, value] of Object.entries(child.attributes)) {
|
||||
// consider only inheritable attributes
|
||||
if (inheritableAttrs.includes(name)) {
|
||||
commonAttributes.set(name, value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// exclude uncommon attributes from initial list
|
||||
for (const [name, value] of commonAttributes) {
|
||||
if (child.attributes[name] !== value) {
|
||||
commonAttributes.delete(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// preserve transform on children when group has clip-path or mask
|
||||
if (
|
||||
node.attributes['clip-path'] != null ||
|
||||
node.attributes.mask != null
|
||||
) {
|
||||
commonAttributes.delete('transform');
|
||||
}
|
||||
|
||||
// preserve transform when all children are paths
|
||||
// so the transform could be applied to path data by other plugins
|
||||
if (everyChildIsPath) {
|
||||
commonAttributes.delete('transform');
|
||||
}
|
||||
|
||||
// add common children attributes to group
|
||||
for (const [name, value] of commonAttributes) {
|
||||
if (name === 'transform') {
|
||||
if (node.attributes.transform != null) {
|
||||
node.attributes.transform = `${node.attributes.transform} ${value}`;
|
||||
} else {
|
||||
node.attributes.transform = value;
|
||||
}
|
||||
} else {
|
||||
node.attributes[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// delete common attributes from children
|
||||
for (const child of node.children) {
|
||||
if (child.type === 'element') {
|
||||
for (const [name] of commonAttributes) {
|
||||
delete child.attributes[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
62
node_modules/svgo/plugins/moveGroupAttrsToElems.js
generated
vendored
Normal file
62
node_modules/svgo/plugins/moveGroupAttrsToElems.js
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
'use strict';
|
||||
|
||||
const { pathElems, referencesProps } = require('./_collections.js');
|
||||
|
||||
exports.name = 'moveGroupAttrsToElems';
|
||||
|
||||
exports.type = 'perItem';
|
||||
|
||||
exports.active = true;
|
||||
|
||||
exports.description = 'moves some group attributes to the content elements';
|
||||
|
||||
const pathElemsWithGroupsAndText = [...pathElems, 'g', 'text'];
|
||||
|
||||
/**
|
||||
* Move group attrs to the content elements.
|
||||
*
|
||||
* @example
|
||||
* <g transform="scale(2)">
|
||||
* <path transform="rotate(45)" d="M0,0 L10,20"/>
|
||||
* <path transform="translate(10, 20)" d="M0,10 L20,30"/>
|
||||
* </g>
|
||||
* ⬇
|
||||
* <g>
|
||||
* <path transform="scale(2) rotate(45)" d="M0,0 L10,20"/>
|
||||
* <path transform="scale(2) translate(10, 20)" d="M0,10 L20,30"/>
|
||||
* </g>
|
||||
*
|
||||
* @param {Object} item current iteration item
|
||||
* @return {Boolean} if false, item will be filtered out
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*/
|
||||
exports.fn = function (item) {
|
||||
// move group transform attr to content's pathElems
|
||||
if (
|
||||
item.type === 'element' &&
|
||||
item.name === 'g' &&
|
||||
item.children.length !== 0 &&
|
||||
item.attributes.transform != null &&
|
||||
Object.entries(item.attributes).some(
|
||||
([name, value]) =>
|
||||
referencesProps.includes(name) && value.includes('url(')
|
||||
) === false &&
|
||||
item.children.every(
|
||||
(inner) =>
|
||||
pathElemsWithGroupsAndText.includes(inner.name) &&
|
||||
inner.attributes.id == null
|
||||
)
|
||||
) {
|
||||
for (const inner of item.children) {
|
||||
const value = item.attributes.transform;
|
||||
if (inner.attributes.transform != null) {
|
||||
inner.attributes.transform = value + ' ' + inner.attributes.transform;
|
||||
} else {
|
||||
inner.attributes.transform = value;
|
||||
}
|
||||
}
|
||||
|
||||
delete item.attributes.transform;
|
||||
}
|
||||
};
|
56
node_modules/svgo/plugins/plugins.js
generated
vendored
Normal file
56
node_modules/svgo/plugins/plugins.js
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
'use strict';
|
||||
|
||||
// builtin presets
|
||||
exports['preset-default'] = require('./preset-default.js');
|
||||
|
||||
// builtin plugins
|
||||
exports.addAttributesToSVGElement = require('./addAttributesToSVGElement.js');
|
||||
exports.addClassesToSVGElement = require('./addClassesToSVGElement.js');
|
||||
exports.cleanupAttrs = require('./cleanupAttrs.js');
|
||||
exports.cleanupEnableBackground = require('./cleanupEnableBackground.js');
|
||||
exports.cleanupIDs = require('./cleanupIDs.js');
|
||||
exports.cleanupListOfValues = require('./cleanupListOfValues.js');
|
||||
exports.cleanupNumericValues = require('./cleanupNumericValues.js');
|
||||
exports.collapseGroups = require('./collapseGroups.js');
|
||||
exports.convertColors = require('./convertColors.js');
|
||||
exports.convertEllipseToCircle = require('./convertEllipseToCircle.js');
|
||||
exports.convertPathData = require('./convertPathData.js');
|
||||
exports.convertShapeToPath = require('./convertShapeToPath.js');
|
||||
exports.convertStyleToAttrs = require('./convertStyleToAttrs.js');
|
||||
exports.convertTransform = require('./convertTransform.js');
|
||||
exports.mergeStyles = require('./mergeStyles.js');
|
||||
exports.inlineStyles = require('./inlineStyles.js');
|
||||
exports.mergePaths = require('./mergePaths.js');
|
||||
exports.minifyStyles = require('./minifyStyles.js');
|
||||
exports.moveElemsAttrsToGroup = require('./moveElemsAttrsToGroup.js');
|
||||
exports.moveGroupAttrsToElems = require('./moveGroupAttrsToElems.js');
|
||||
exports.prefixIds = require('./prefixIds.js');
|
||||
exports.removeAttributesBySelector = require('./removeAttributesBySelector.js');
|
||||
exports.removeAttrs = require('./removeAttrs.js');
|
||||
exports.removeComments = require('./removeComments.js');
|
||||
exports.removeDesc = require('./removeDesc.js');
|
||||
exports.removeDimensions = require('./removeDimensions.js');
|
||||
exports.removeDoctype = require('./removeDoctype.js');
|
||||
exports.removeEditorsNSData = require('./removeEditorsNSData.js');
|
||||
exports.removeElementsByAttr = require('./removeElementsByAttr.js');
|
||||
exports.removeEmptyAttrs = require('./removeEmptyAttrs.js');
|
||||
exports.removeEmptyContainers = require('./removeEmptyContainers.js');
|
||||
exports.removeEmptyText = require('./removeEmptyText.js');
|
||||
exports.removeHiddenElems = require('./removeHiddenElems.js');
|
||||
exports.removeMetadata = require('./removeMetadata.js');
|
||||
exports.removeNonInheritableGroupAttrs = require('./removeNonInheritableGroupAttrs.js');
|
||||
exports.removeOffCanvasPaths = require('./removeOffCanvasPaths.js');
|
||||
exports.removeRasterImages = require('./removeRasterImages.js');
|
||||
exports.removeScriptElement = require('./removeScriptElement.js');
|
||||
exports.removeStyleElement = require('./removeStyleElement.js');
|
||||
exports.removeTitle = require('./removeTitle.js');
|
||||
exports.removeUnknownsAndDefaults = require('./removeUnknownsAndDefaults.js');
|
||||
exports.removeUnusedNS = require('./removeUnusedNS.js');
|
||||
exports.removeUselessDefs = require('./removeUselessDefs.js');
|
||||
exports.removeUselessStrokeAndFill = require('./removeUselessStrokeAndFill.js');
|
||||
exports.removeViewBox = require('./removeViewBox.js');
|
||||
exports.removeXMLNS = require('./removeXMLNS.js');
|
||||
exports.removeXMLProcInst = require('./removeXMLProcInst.js');
|
||||
exports.reusePaths = require('./reusePaths.js');
|
||||
exports.sortAttrs = require('./sortAttrs.js');
|
||||
exports.sortDefsChildren = require('./sortDefsChildren.js');
|
241
node_modules/svgo/plugins/prefixIds.js
generated
vendored
Normal file
241
node_modules/svgo/plugins/prefixIds.js
generated
vendored
Normal file
@ -0,0 +1,241 @@
|
||||
'use strict';
|
||||
|
||||
const csstree = require('css-tree');
|
||||
const { referencesProps } = require('./_collections.js');
|
||||
|
||||
/**
|
||||
* @typedef {import('../lib/types').XastElement} XastElement
|
||||
* @typedef {import('../lib/types').PluginInfo} PluginInfo
|
||||
*/
|
||||
|
||||
exports.type = 'visitor';
|
||||
exports.name = 'prefixIds';
|
||||
exports.active = false;
|
||||
exports.description = 'prefix IDs';
|
||||
|
||||
/**
|
||||
* extract basename from path
|
||||
* @type {(path: string) => string}
|
||||
*/
|
||||
const getBasename = (path) => {
|
||||
// extract everything after latest slash or backslash
|
||||
const matched = path.match(/[/\\]?([^/\\]+)$/);
|
||||
if (matched) {
|
||||
return matched[1];
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* escapes a string for being used as ID
|
||||
* @type {(string: string) => string}
|
||||
*/
|
||||
const escapeIdentifierName = (str) => {
|
||||
return str.replace(/[. ]/g, '_');
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {(string: string) => string}
|
||||
*/
|
||||
const unquote = (string) => {
|
||||
if (
|
||||
(string.startsWith('"') && string.endsWith('"')) ||
|
||||
(string.startsWith("'") && string.endsWith("'"))
|
||||
) {
|
||||
return string.slice(1, -1);
|
||||
}
|
||||
return string;
|
||||
};
|
||||
|
||||
/**
|
||||
* prefix an ID
|
||||
* @type {(prefix: string, name: string) => string}
|
||||
*/
|
||||
const prefixId = (prefix, value) => {
|
||||
if (value.startsWith(prefix)) {
|
||||
return value;
|
||||
}
|
||||
return prefix + value;
|
||||
};
|
||||
|
||||
/**
|
||||
* prefix an #ID
|
||||
* @type {(prefix: string, name: string) => string | null}
|
||||
*/
|
||||
const prefixReference = (prefix, value) => {
|
||||
if (value.startsWith('#')) {
|
||||
return '#' + prefixId(prefix, value.slice(1));
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Prefixes identifiers
|
||||
*
|
||||
* @author strarsis <strarsis@gmail.com>
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<{
|
||||
* prefix?: boolean | string | ((node: XastElement, info: PluginInfo) => string),
|
||||
* delim?: string,
|
||||
* prefixIds?: boolean,
|
||||
* prefixClassNames?: boolean,
|
||||
* }>}
|
||||
*/
|
||||
exports.fn = (_root, params, info) => {
|
||||
const { delim = '__', prefixIds = true, prefixClassNames = true } = params;
|
||||
|
||||
return {
|
||||
element: {
|
||||
enter: (node) => {
|
||||
/**
|
||||
* prefix, from file name or option
|
||||
* @type {string}
|
||||
*/
|
||||
let prefix = 'prefix' + delim;
|
||||
if (typeof params.prefix === 'function') {
|
||||
prefix = params.prefix(node, info) + delim;
|
||||
} else if (typeof params.prefix === 'string') {
|
||||
prefix = params.prefix + delim;
|
||||
} else if (params.prefix === false) {
|
||||
prefix = '';
|
||||
} else if (info.path != null && info.path.length > 0) {
|
||||
prefix = escapeIdentifierName(getBasename(info.path)) + delim;
|
||||
}
|
||||
|
||||
// prefix id/class selectors and url() references in styles
|
||||
if (node.name === 'style') {
|
||||
// skip empty <style/> elements
|
||||
if (node.children.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// parse styles
|
||||
let cssText = '';
|
||||
if (
|
||||
node.children[0].type === 'text' ||
|
||||
node.children[0].type === 'cdata'
|
||||
) {
|
||||
cssText = node.children[0].value;
|
||||
}
|
||||
/**
|
||||
* @type {null | csstree.CssNode}
|
||||
*/
|
||||
let cssAst = null;
|
||||
try {
|
||||
cssAst = csstree.parse(cssText, {
|
||||
parseValue: true,
|
||||
parseCustomProperty: false,
|
||||
});
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
csstree.walk(cssAst, (node) => {
|
||||
// #ID, .class selectors
|
||||
if (
|
||||
(prefixIds && node.type === 'IdSelector') ||
|
||||
(prefixClassNames && node.type === 'ClassSelector')
|
||||
) {
|
||||
node.name = prefixId(prefix, node.name);
|
||||
return;
|
||||
}
|
||||
// url(...) references
|
||||
if (
|
||||
node.type === 'Url' &&
|
||||
node.value.value &&
|
||||
node.value.value.length > 0
|
||||
) {
|
||||
const prefixed = prefixReference(
|
||||
prefix,
|
||||
unquote(node.value.value)
|
||||
);
|
||||
if (prefixed != null) {
|
||||
node.value.value = prefixed;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// update styles
|
||||
if (
|
||||
node.children[0].type === 'text' ||
|
||||
node.children[0].type === 'cdata'
|
||||
) {
|
||||
node.children[0].value = csstree.generate(cssAst);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// prefix an ID attribute value
|
||||
if (
|
||||
prefixIds &&
|
||||
node.attributes.id != null &&
|
||||
node.attributes.id.length !== 0
|
||||
) {
|
||||
node.attributes.id = prefixId(prefix, node.attributes.id);
|
||||
}
|
||||
|
||||
// prefix a class attribute value
|
||||
if (
|
||||
prefixClassNames &&
|
||||
node.attributes.class != null &&
|
||||
node.attributes.class.length !== 0
|
||||
) {
|
||||
node.attributes.class = node.attributes.class
|
||||
.split(/\s+/)
|
||||
.map((name) => prefixId(prefix, name))
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
// prefix a href attribute value
|
||||
// xlink:href is deprecated, must be still supported
|
||||
for (const name of ['href', 'xlink:href']) {
|
||||
if (
|
||||
node.attributes[name] != null &&
|
||||
node.attributes[name].length !== 0
|
||||
) {
|
||||
const prefixed = prefixReference(prefix, node.attributes[name]);
|
||||
if (prefixed != null) {
|
||||
node.attributes[name] = prefixed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// prefix an URL attribute value
|
||||
for (const name of referencesProps) {
|
||||
if (
|
||||
node.attributes[name] != null &&
|
||||
node.attributes[name].length !== 0
|
||||
) {
|
||||
node.attributes[name] = node.attributes[name].replace(
|
||||
/url\((.*?)\)/gi,
|
||||
(match, url) => {
|
||||
const prefixed = prefixReference(prefix, url);
|
||||
if (prefixed == null) {
|
||||
return match;
|
||||
}
|
||||
return `url(${prefixed})`;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// prefix begin/end attribute value
|
||||
for (const name of ['begin', 'end']) {
|
||||
if (
|
||||
node.attributes[name] != null &&
|
||||
node.attributes[name].length !== 0
|
||||
) {
|
||||
const parts = node.attributes[name].split(/\s*;\s+/).map((val) => {
|
||||
if (val.endsWith('.end') || val.endsWith('.start')) {
|
||||
const [id, postfix] = val.split('.');
|
||||
return `${prefixId(prefix, id)}.${postfix}`;
|
||||
}
|
||||
return val;
|
||||
});
|
||||
node.attributes[name] = parts.join('; ');
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
80
node_modules/svgo/plugins/preset-default.js
generated
vendored
Normal file
80
node_modules/svgo/plugins/preset-default.js
generated
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
'use strict';
|
||||
|
||||
const { createPreset } = require('../lib/svgo/plugins.js');
|
||||
|
||||
const removeDoctype = require('./removeDoctype.js');
|
||||
const removeXMLProcInst = require('./removeXMLProcInst.js');
|
||||
const removeComments = require('./removeComments.js');
|
||||
const removeMetadata = require('./removeMetadata.js');
|
||||
const removeEditorsNSData = require('./removeEditorsNSData.js');
|
||||
const cleanupAttrs = require('./cleanupAttrs.js');
|
||||
const mergeStyles = require('./mergeStyles.js');
|
||||
const inlineStyles = require('./inlineStyles.js');
|
||||
const minifyStyles = require('./minifyStyles.js');
|
||||
const cleanupIDs = require('./cleanupIDs.js');
|
||||
const removeUselessDefs = require('./removeUselessDefs.js');
|
||||
const cleanupNumericValues = require('./cleanupNumericValues.js');
|
||||
const convertColors = require('./convertColors.js');
|
||||
const removeUnknownsAndDefaults = require('./removeUnknownsAndDefaults.js');
|
||||
const removeNonInheritableGroupAttrs = require('./removeNonInheritableGroupAttrs.js');
|
||||
const removeUselessStrokeAndFill = require('./removeUselessStrokeAndFill.js');
|
||||
const removeViewBox = require('./removeViewBox.js');
|
||||
const cleanupEnableBackground = require('./cleanupEnableBackground.js');
|
||||
const removeHiddenElems = require('./removeHiddenElems.js');
|
||||
const removeEmptyText = require('./removeEmptyText.js');
|
||||
const convertShapeToPath = require('./convertShapeToPath.js');
|
||||
const convertEllipseToCircle = require('./convertEllipseToCircle.js');
|
||||
const moveElemsAttrsToGroup = require('./moveElemsAttrsToGroup.js');
|
||||
const moveGroupAttrsToElems = require('./moveGroupAttrsToElems.js');
|
||||
const collapseGroups = require('./collapseGroups.js');
|
||||
const convertPathData = require('./convertPathData.js');
|
||||
const convertTransform = require('./convertTransform.js');
|
||||
const removeEmptyAttrs = require('./removeEmptyAttrs.js');
|
||||
const removeEmptyContainers = require('./removeEmptyContainers.js');
|
||||
const mergePaths = require('./mergePaths.js');
|
||||
const removeUnusedNS = require('./removeUnusedNS.js');
|
||||
const sortDefsChildren = require('./sortDefsChildren.js');
|
||||
const removeTitle = require('./removeTitle.js');
|
||||
const removeDesc = require('./removeDesc.js');
|
||||
|
||||
const presetDefault = createPreset({
|
||||
name: 'presetDefault',
|
||||
plugins: [
|
||||
removeDoctype,
|
||||
removeXMLProcInst,
|
||||
removeComments,
|
||||
removeMetadata,
|
||||
removeEditorsNSData,
|
||||
cleanupAttrs,
|
||||
mergeStyles,
|
||||
inlineStyles,
|
||||
minifyStyles,
|
||||
cleanupIDs,
|
||||
removeUselessDefs,
|
||||
cleanupNumericValues,
|
||||
convertColors,
|
||||
removeUnknownsAndDefaults,
|
||||
removeNonInheritableGroupAttrs,
|
||||
removeUselessStrokeAndFill,
|
||||
removeViewBox,
|
||||
cleanupEnableBackground,
|
||||
removeHiddenElems,
|
||||
removeEmptyText,
|
||||
convertShapeToPath,
|
||||
convertEllipseToCircle,
|
||||
moveElemsAttrsToGroup,
|
||||
moveGroupAttrsToElems,
|
||||
collapseGroups,
|
||||
convertPathData,
|
||||
convertTransform,
|
||||
removeEmptyAttrs,
|
||||
removeEmptyContainers,
|
||||
mergePaths,
|
||||
removeUnusedNS,
|
||||
sortDefsChildren,
|
||||
removeTitle,
|
||||
removeDesc,
|
||||
],
|
||||
});
|
||||
|
||||
module.exports = presetDefault;
|
99
node_modules/svgo/plugins/removeAttributesBySelector.js
generated
vendored
Normal file
99
node_modules/svgo/plugins/removeAttributesBySelector.js
generated
vendored
Normal file
@ -0,0 +1,99 @@
|
||||
'use strict';
|
||||
|
||||
const { querySelectorAll } = require('../lib/xast.js');
|
||||
|
||||
exports.name = 'removeAttributesBySelector';
|
||||
exports.type = 'visitor';
|
||||
exports.active = false;
|
||||
exports.description =
|
||||
'removes attributes of elements that match a css selector';
|
||||
|
||||
/**
|
||||
* Removes attributes of elements that match a css selector.
|
||||
*
|
||||
* @example
|
||||
* <caption>A selector removing a single attribute</caption>
|
||||
* plugins: [
|
||||
* {
|
||||
* name: "removeAttributesBySelector",
|
||||
* params: {
|
||||
* selector: "[fill='#00ff00']"
|
||||
* attributes: "fill"
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* <rect x="0" y="0" width="100" height="100" fill="#00ff00" stroke="#00ff00"/>
|
||||
* ↓
|
||||
* <rect x="0" y="0" width="100" height="100" stroke="#00ff00"/>
|
||||
*
|
||||
* <caption>A selector removing multiple attributes</caption>
|
||||
* plugins: [
|
||||
* {
|
||||
* name: "removeAttributesBySelector",
|
||||
* params: {
|
||||
* selector: "[fill='#00ff00']",
|
||||
* attributes: [
|
||||
* "fill",
|
||||
* "stroke"
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* <rect x="0" y="0" width="100" height="100" fill="#00ff00" stroke="#00ff00"/>
|
||||
* ↓
|
||||
* <rect x="0" y="0" width="100" height="100"/>
|
||||
*
|
||||
* <caption>Multiple selectors removing attributes</caption>
|
||||
* plugins: [
|
||||
* {
|
||||
* name: "removeAttributesBySelector",
|
||||
* params: {
|
||||
* selectors: [
|
||||
* {
|
||||
* selector: "[fill='#00ff00']",
|
||||
* attributes: "fill"
|
||||
* },
|
||||
* {
|
||||
* selector: "#remove",
|
||||
* attributes: [
|
||||
* "stroke",
|
||||
* "id"
|
||||
* ]
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* <rect x="0" y="0" width="100" height="100" fill="#00ff00" stroke="#00ff00"/>
|
||||
* ↓
|
||||
* <rect x="0" y="0" width="100" height="100"/>
|
||||
*
|
||||
* @link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|MDN CSS Selectors
|
||||
*
|
||||
* @author Bradley Mease
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<any>}
|
||||
*/
|
||||
exports.fn = (root, params) => {
|
||||
const selectors = Array.isArray(params.selectors)
|
||||
? params.selectors
|
||||
: [params];
|
||||
for (const { selector, attributes } of selectors) {
|
||||
const nodes = querySelectorAll(root, selector);
|
||||
for (const node of nodes) {
|
||||
if (node.type === 'element') {
|
||||
if (Array.isArray(attributes)) {
|
||||
for (const name of attributes) {
|
||||
delete node.attributes[name];
|
||||
}
|
||||
} else {
|
||||
delete node.attributes[attributes];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
};
|
159
node_modules/svgo/plugins/removeAttrs.js
generated
vendored
Normal file
159
node_modules/svgo/plugins/removeAttrs.js
generated
vendored
Normal file
@ -0,0 +1,159 @@
|
||||
'use strict';
|
||||
|
||||
exports.name = 'removeAttrs';
|
||||
exports.type = 'visitor';
|
||||
exports.active = false;
|
||||
exports.description = 'removes specified attributes';
|
||||
|
||||
const DEFAULT_SEPARATOR = ':';
|
||||
const ENOATTRS = `Warning: The plugin "removeAttrs" requires the "attrs" parameter.
|
||||
It should have a pattern to remove, otherwise the plugin is a noop.
|
||||
Config example:
|
||||
|
||||
plugins: [
|
||||
{
|
||||
name: "removeAttrs",
|
||||
params: {
|
||||
attrs: "(fill|stroke)"
|
||||
}
|
||||
}
|
||||
]
|
||||
`;
|
||||
|
||||
/**
|
||||
* Remove attributes
|
||||
*
|
||||
* @example elemSeparator
|
||||
* format: string
|
||||
*
|
||||
* @example preserveCurrentColor
|
||||
* format: boolean
|
||||
*
|
||||
* @example attrs:
|
||||
*
|
||||
* format: [ element* : attribute* : value* ]
|
||||
*
|
||||
* element : regexp (wrapped into ^...$), single * or omitted > all elements (must be present when value is used)
|
||||
* attribute : regexp (wrapped into ^...$)
|
||||
* value : regexp (wrapped into ^...$), single * or omitted > all values
|
||||
*
|
||||
* examples:
|
||||
*
|
||||
* > basic: remove fill attribute
|
||||
* ---
|
||||
* removeAttrs:
|
||||
* attrs: 'fill'
|
||||
*
|
||||
* > remove fill attribute on path element
|
||||
* ---
|
||||
* attrs: 'path:fill'
|
||||
*
|
||||
* > remove fill attribute on path element where value is none
|
||||
* ---
|
||||
* attrs: 'path:fill:none'
|
||||
*
|
||||
*
|
||||
* > remove all fill and stroke attribute
|
||||
* ---
|
||||
* attrs:
|
||||
* - 'fill'
|
||||
* - 'stroke'
|
||||
*
|
||||
* [is same as]
|
||||
*
|
||||
* attrs: '(fill|stroke)'
|
||||
*
|
||||
* [is same as]
|
||||
*
|
||||
* attrs: '*:(fill|stroke)'
|
||||
*
|
||||
* [is same as]
|
||||
*
|
||||
* attrs: '.*:(fill|stroke)'
|
||||
*
|
||||
* [is same as]
|
||||
*
|
||||
* attrs: '.*:(fill|stroke):.*'
|
||||
*
|
||||
*
|
||||
* > remove all stroke related attributes
|
||||
* ----
|
||||
* attrs: 'stroke.*'
|
||||
*
|
||||
*
|
||||
* @author Benny Schudel
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<{
|
||||
* elemSeparator?: string,
|
||||
* preserveCurrentColor?: boolean,
|
||||
* attrs: string | Array<string>
|
||||
* }>}
|
||||
*/
|
||||
exports.fn = (root, params) => {
|
||||
if (typeof params.attrs == 'undefined') {
|
||||
console.warn(ENOATTRS);
|
||||
return null;
|
||||
}
|
||||
|
||||
const elemSeparator =
|
||||
typeof params.elemSeparator == 'string'
|
||||
? params.elemSeparator
|
||||
: DEFAULT_SEPARATOR;
|
||||
const preserveCurrentColor =
|
||||
typeof params.preserveCurrentColor == 'boolean'
|
||||
? params.preserveCurrentColor
|
||||
: false;
|
||||
const attrs = Array.isArray(params.attrs) ? params.attrs : [params.attrs];
|
||||
|
||||
return {
|
||||
element: {
|
||||
enter: (node) => {
|
||||
for (let pattern of attrs) {
|
||||
// if no element separators (:), assume it's attribute name, and apply to all elements *regardless of value*
|
||||
if (pattern.includes(elemSeparator) === false) {
|
||||
pattern = ['.*', elemSeparator, pattern, elemSeparator, '.*'].join(
|
||||
''
|
||||
);
|
||||
// if only 1 separator, assume it's element and attribute name, and apply regardless of attribute value
|
||||
} else if (pattern.split(elemSeparator).length < 3) {
|
||||
pattern = [pattern, elemSeparator, '.*'].join('');
|
||||
}
|
||||
|
||||
// create regexps for element, attribute name, and attribute value
|
||||
const list = pattern.split(elemSeparator).map((value) => {
|
||||
// adjust single * to match anything
|
||||
if (value === '*') {
|
||||
value = '.*';
|
||||
}
|
||||
return new RegExp(['^', value, '$'].join(''), 'i');
|
||||
});
|
||||
|
||||
// matches element
|
||||
if (list[0].test(node.name)) {
|
||||
// loop attributes
|
||||
for (const [name, value] of Object.entries(node.attributes)) {
|
||||
const isFillCurrentColor =
|
||||
preserveCurrentColor &&
|
||||
name == 'fill' &&
|
||||
value == 'currentColor';
|
||||
const isStrokeCurrentColor =
|
||||
preserveCurrentColor &&
|
||||
name == 'stroke' &&
|
||||
value == 'currentColor';
|
||||
if (
|
||||
!isFillCurrentColor &&
|
||||
!isStrokeCurrentColor &&
|
||||
// matches attribute name
|
||||
list[1].test(name) &&
|
||||
// matches attribute value
|
||||
list[2].test(value)
|
||||
) {
|
||||
delete node.attributes[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
31
node_modules/svgo/plugins/removeComments.js
generated
vendored
Normal file
31
node_modules/svgo/plugins/removeComments.js
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
'use strict';
|
||||
|
||||
const { detachNodeFromParent } = require('../lib/xast.js');
|
||||
|
||||
exports.name = 'removeComments';
|
||||
exports.type = 'visitor';
|
||||
exports.active = true;
|
||||
exports.description = 'removes comments';
|
||||
|
||||
/**
|
||||
* Remove comments.
|
||||
*
|
||||
* @example
|
||||
* <!-- Generator: Adobe Illustrator 15.0.0, SVG Export
|
||||
* Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<void>}
|
||||
*/
|
||||
exports.fn = () => {
|
||||
return {
|
||||
comment: {
|
||||
enter: (node, parentNode) => {
|
||||
if (node.value.charAt(0) !== '!') {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
41
node_modules/svgo/plugins/removeDesc.js
generated
vendored
Normal file
41
node_modules/svgo/plugins/removeDesc.js
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
'use strict';
|
||||
|
||||
const { detachNodeFromParent } = require('../lib/xast.js');
|
||||
|
||||
exports.name = 'removeDesc';
|
||||
exports.type = 'visitor';
|
||||
exports.active = true;
|
||||
exports.description = 'removes <desc>';
|
||||
|
||||
const standardDescs = /^(Created with|Created using)/;
|
||||
|
||||
/**
|
||||
* Removes <desc>.
|
||||
* Removes only standard editors content or empty elements 'cause it can be used for accessibility.
|
||||
* Enable parameter 'removeAny' to remove any description.
|
||||
*
|
||||
* https://developer.mozilla.org/en-US/docs/Web/SVG/Element/desc
|
||||
*
|
||||
* @author Daniel Wabyick
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<{ removeAny?: boolean }>}
|
||||
*/
|
||||
exports.fn = (root, params) => {
|
||||
const { removeAny = true } = params;
|
||||
return {
|
||||
element: {
|
||||
enter: (node, parentNode) => {
|
||||
if (node.name === 'desc') {
|
||||
if (
|
||||
removeAny ||
|
||||
node.children.length === 0 ||
|
||||
(node.children[0].type === 'text' &&
|
||||
standardDescs.test(node.children[0].value))
|
||||
) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
43
node_modules/svgo/plugins/removeDimensions.js
generated
vendored
Normal file
43
node_modules/svgo/plugins/removeDimensions.js
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
'use strict';
|
||||
|
||||
exports.name = 'removeDimensions';
|
||||
|
||||
exports.type = 'perItem';
|
||||
|
||||
exports.active = false;
|
||||
|
||||
exports.description =
|
||||
'removes width and height in presence of viewBox (opposite to removeViewBox, disable it first)';
|
||||
|
||||
/**
|
||||
* Remove width/height attributes and add the viewBox attribute if it's missing
|
||||
*
|
||||
* @example
|
||||
* <svg width="100" height="50" />
|
||||
* ↓
|
||||
* <svg viewBox="0 0 100 50" />
|
||||
*
|
||||
* @param {Object} item current iteration item
|
||||
* @return {Boolean} if true, with and height will be filtered out
|
||||
*
|
||||
* @author Benny Schudel
|
||||
*/
|
||||
exports.fn = function (item) {
|
||||
if (item.type === 'element' && item.name === 'svg') {
|
||||
if (item.attributes.viewBox != null) {
|
||||
delete item.attributes.width;
|
||||
delete item.attributes.height;
|
||||
} else if (
|
||||
item.attributes.width != null &&
|
||||
item.attributes.height != null &&
|
||||
Number.isNaN(Number(item.attributes.width)) === false &&
|
||||
Number.isNaN(Number(item.attributes.height)) === false
|
||||
) {
|
||||
const width = Number(item.attributes.width);
|
||||
const height = Number(item.attributes.height);
|
||||
item.attributes.viewBox = `0 0 ${width} ${height}`;
|
||||
delete item.attributes.width;
|
||||
delete item.attributes.height;
|
||||
}
|
||||
}
|
||||
};
|
42
node_modules/svgo/plugins/removeDoctype.js
generated
vendored
Normal file
42
node_modules/svgo/plugins/removeDoctype.js
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
'use strict';
|
||||
|
||||
const { detachNodeFromParent } = require('../lib/xast.js');
|
||||
|
||||
exports.name = 'removeDoctype';
|
||||
exports.type = 'visitor';
|
||||
exports.active = true;
|
||||
exports.description = 'removes doctype declaration';
|
||||
|
||||
/**
|
||||
* Remove DOCTYPE declaration.
|
||||
*
|
||||
* "Unfortunately the SVG DTDs are a source of so many
|
||||
* issues that the SVG WG has decided not to write one
|
||||
* for the upcoming SVG 1.2 standard. In fact SVG WG
|
||||
* members are even telling people not to use a DOCTYPE
|
||||
* declaration in SVG 1.0 and 1.1 documents"
|
||||
* https://jwatt.org/svg/authoring/#doctype-declaration
|
||||
*
|
||||
* @example
|
||||
* <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
* q"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
*
|
||||
* @example
|
||||
* <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
* "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" [
|
||||
* <!-- an internal subset can be embedded here -->
|
||||
* ]>
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<void>}
|
||||
*/
|
||||
exports.fn = () => {
|
||||
return {
|
||||
doctype: {
|
||||
enter: (node, parentNode) => {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
68
node_modules/svgo/plugins/removeEditorsNSData.js
generated
vendored
Normal file
68
node_modules/svgo/plugins/removeEditorsNSData.js
generated
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
'use strict';
|
||||
|
||||
const { detachNodeFromParent } = require('../lib/xast.js');
|
||||
const { editorNamespaces } = require('./_collections.js');
|
||||
|
||||
exports.type = 'visitor';
|
||||
exports.name = 'removeEditorsNSData';
|
||||
exports.active = true;
|
||||
exports.description = 'removes editors namespaces, elements and attributes';
|
||||
|
||||
/**
|
||||
* Remove editors namespaces, elements and attributes.
|
||||
*
|
||||
* @example
|
||||
* <svg xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd">
|
||||
* <sodipodi:namedview/>
|
||||
* <path sodipodi:nodetypes="cccc"/>
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<{
|
||||
* additionalNamespaces?: Array<string>
|
||||
* }>}
|
||||
*/
|
||||
exports.fn = (_root, params) => {
|
||||
let namespaces = editorNamespaces;
|
||||
if (Array.isArray(params.additionalNamespaces)) {
|
||||
namespaces = [...editorNamespaces, ...params.additionalNamespaces];
|
||||
}
|
||||
/**
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
const prefixes = [];
|
||||
return {
|
||||
element: {
|
||||
enter: (node, parentNode) => {
|
||||
// collect namespace aliases from svg element
|
||||
if (node.name === 'svg') {
|
||||
for (const [name, value] of Object.entries(node.attributes)) {
|
||||
if (name.startsWith('xmlns:') && namespaces.includes(value)) {
|
||||
prefixes.push(name.slice('xmlns:'.length));
|
||||
// <svg xmlns:sodipodi="">
|
||||
delete node.attributes[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
// remove editor attributes, for example
|
||||
// <* sodipodi:*="">
|
||||
for (const name of Object.keys(node.attributes)) {
|
||||
if (name.includes(':')) {
|
||||
const [prefix] = name.split(':');
|
||||
if (prefixes.includes(prefix)) {
|
||||
delete node.attributes[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
// remove editor elements, for example
|
||||
// <sodipodi:*>
|
||||
if (node.name.includes(':')) {
|
||||
const [prefix] = node.name.split(':');
|
||||
if (prefixes.includes(prefix)) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
78
node_modules/svgo/plugins/removeElementsByAttr.js
generated
vendored
Normal file
78
node_modules/svgo/plugins/removeElementsByAttr.js
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
'use strict';
|
||||
|
||||
const { detachNodeFromParent } = require('../lib/xast.js');
|
||||
|
||||
exports.name = 'removeElementsByAttr';
|
||||
exports.type = 'visitor';
|
||||
exports.active = false;
|
||||
exports.description =
|
||||
'removes arbitrary elements by ID or className (disabled by default)';
|
||||
|
||||
/**
|
||||
* Remove arbitrary SVG elements by ID or className.
|
||||
*
|
||||
* @example id
|
||||
* > single: remove element with ID of `elementID`
|
||||
* ---
|
||||
* removeElementsByAttr:
|
||||
* id: 'elementID'
|
||||
*
|
||||
* > list: remove multiple elements by ID
|
||||
* ---
|
||||
* removeElementsByAttr:
|
||||
* id:
|
||||
* - 'elementID'
|
||||
* - 'anotherID'
|
||||
*
|
||||
* @example class
|
||||
* > single: remove all elements with class of `elementClass`
|
||||
* ---
|
||||
* removeElementsByAttr:
|
||||
* class: 'elementClass'
|
||||
*
|
||||
* > list: remove all elements with class of `elementClass` or `anotherClass`
|
||||
* ---
|
||||
* removeElementsByAttr:
|
||||
* class:
|
||||
* - 'elementClass'
|
||||
* - 'anotherClass'
|
||||
*
|
||||
* @author Eli Dupuis (@elidupuis)
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<{
|
||||
* id?: string | Array<string>,
|
||||
* class?: string | Array<string>
|
||||
* }>}
|
||||
*/
|
||||
exports.fn = (root, params) => {
|
||||
const ids =
|
||||
params.id == null ? [] : Array.isArray(params.id) ? params.id : [params.id];
|
||||
const classes =
|
||||
params.class == null
|
||||
? []
|
||||
: Array.isArray(params.class)
|
||||
? params.class
|
||||
: [params.class];
|
||||
return {
|
||||
element: {
|
||||
enter: (node, parentNode) => {
|
||||
// remove element if it's `id` matches configured `id` params
|
||||
if (node.attributes.id != null && ids.length !== 0) {
|
||||
if (ids.includes(node.attributes.id)) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
}
|
||||
}
|
||||
// remove element if it's `class` contains any of the configured `class` params
|
||||
if (node.attributes.class && classes.length !== 0) {
|
||||
const classList = node.attributes.class.split(' ');
|
||||
for (const item of classes) {
|
||||
if (classList.includes(item)) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
33
node_modules/svgo/plugins/removeEmptyAttrs.js
generated
vendored
Normal file
33
node_modules/svgo/plugins/removeEmptyAttrs.js
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
const { attrsGroups } = require('./_collections.js');
|
||||
|
||||
exports.type = 'visitor';
|
||||
exports.name = 'removeEmptyAttrs';
|
||||
exports.active = true;
|
||||
exports.description = 'removes empty attributes';
|
||||
|
||||
/**
|
||||
* Remove attributes with empty values.
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<void>}
|
||||
*/
|
||||
exports.fn = () => {
|
||||
return {
|
||||
element: {
|
||||
enter: (node) => {
|
||||
for (const [name, value] of Object.entries(node.attributes)) {
|
||||
if (
|
||||
value === '' &&
|
||||
// empty conditional processing attributes prevents elements from rendering
|
||||
attrsGroups.conditionalProcessing.includes(name) === false
|
||||
) {
|
||||
delete node.attributes[name];
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
58
node_modules/svgo/plugins/removeEmptyContainers.js
generated
vendored
Normal file
58
node_modules/svgo/plugins/removeEmptyContainers.js
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
'use strict';
|
||||
|
||||
const { detachNodeFromParent } = require('../lib/xast.js');
|
||||
const { elemsGroups } = require('./_collections.js');
|
||||
|
||||
exports.type = 'visitor';
|
||||
exports.name = 'removeEmptyContainers';
|
||||
exports.active = true;
|
||||
exports.description = 'removes empty container elements';
|
||||
|
||||
/**
|
||||
* Remove empty containers.
|
||||
*
|
||||
* @see https://www.w3.org/TR/SVG11/intro.html#TermContainerElement
|
||||
*
|
||||
* @example
|
||||
* <defs/>
|
||||
*
|
||||
* @example
|
||||
* <g><marker><a/></marker></g>
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<void>}
|
||||
*/
|
||||
exports.fn = () => {
|
||||
return {
|
||||
element: {
|
||||
exit: (node, parentNode) => {
|
||||
// remove only empty non-svg containers
|
||||
if (
|
||||
node.name === 'svg' ||
|
||||
elemsGroups.container.includes(node.name) === false ||
|
||||
node.children.length !== 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// empty patterns may contain reusable configuration
|
||||
if (
|
||||
node.name === 'pattern' &&
|
||||
Object.keys(node.attributes).length !== 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// The <g> may not have content, but the filter may cause a rectangle
|
||||
// to be created and filled with pattern.
|
||||
if (node.name === 'g' && node.attributes.filter != null) {
|
||||
return;
|
||||
}
|
||||
// empty <mask> hides masked element
|
||||
if (node.name === 'mask' && node.attributes.id != null) {
|
||||
return;
|
||||
}
|
||||
detachNodeFromParent(node, parentNode);
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
57
node_modules/svgo/plugins/removeEmptyText.js
generated
vendored
Normal file
57
node_modules/svgo/plugins/removeEmptyText.js
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
'use strict';
|
||||
|
||||
const { detachNodeFromParent } = require('../lib/xast.js');
|
||||
|
||||
exports.name = 'removeEmptyText';
|
||||
exports.type = 'visitor';
|
||||
exports.active = true;
|
||||
exports.description = 'removes empty <text> elements';
|
||||
|
||||
/**
|
||||
* Remove empty Text elements.
|
||||
*
|
||||
* @see https://www.w3.org/TR/SVG11/text.html
|
||||
*
|
||||
* @example
|
||||
* Remove empty text element:
|
||||
* <text/>
|
||||
*
|
||||
* Remove empty tspan element:
|
||||
* <tspan/>
|
||||
*
|
||||
* Remove tref with empty xlink:href attribute:
|
||||
* <tref xlink:href=""/>
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<{
|
||||
* text?: boolean,
|
||||
* tspan?: boolean,
|
||||
* tref?: boolean
|
||||
* }>}
|
||||
*/
|
||||
exports.fn = (root, params) => {
|
||||
const { text = true, tspan = true, tref = true } = params;
|
||||
return {
|
||||
element: {
|
||||
enter: (node, parentNode) => {
|
||||
// Remove empty text element
|
||||
if (text && node.name === 'text' && node.children.length === 0) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
}
|
||||
// Remove empty tspan element
|
||||
if (tspan && node.name === 'tspan' && node.children.length === 0) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
}
|
||||
// Remove tref with empty xlink:href attribute
|
||||
if (
|
||||
tref &&
|
||||
node.name === 'tref' &&
|
||||
node.attributes['xlink:href'] == null
|
||||
) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
318
node_modules/svgo/plugins/removeHiddenElems.js
generated
vendored
Normal file
318
node_modules/svgo/plugins/removeHiddenElems.js
generated
vendored
Normal file
@ -0,0 +1,318 @@
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
querySelector,
|
||||
closestByName,
|
||||
detachNodeFromParent,
|
||||
} = require('../lib/xast.js');
|
||||
const { collectStylesheet, computeStyle } = require('../lib/style.js');
|
||||
const { parsePathData } = require('../lib/path.js');
|
||||
|
||||
exports.name = 'removeHiddenElems';
|
||||
exports.type = 'visitor';
|
||||
exports.active = true;
|
||||
exports.description =
|
||||
'removes hidden elements (zero sized, with absent attributes)';
|
||||
|
||||
/**
|
||||
* Remove hidden elements with disabled rendering:
|
||||
* - display="none"
|
||||
* - opacity="0"
|
||||
* - circle with zero radius
|
||||
* - ellipse with zero x-axis or y-axis radius
|
||||
* - rectangle with zero width or height
|
||||
* - pattern with zero width or height
|
||||
* - image with zero width or height
|
||||
* - path with empty data
|
||||
* - polyline with empty points
|
||||
* - polygon with empty points
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<{
|
||||
* isHidden: boolean,
|
||||
* displayNone: boolean,
|
||||
* opacity0: boolean,
|
||||
* circleR0: boolean,
|
||||
* ellipseRX0: boolean,
|
||||
* ellipseRY0: boolean,
|
||||
* rectWidth0: boolean,
|
||||
* rectHeight0: boolean,
|
||||
* patternWidth0: boolean,
|
||||
* patternHeight0: boolean,
|
||||
* imageWidth0: boolean,
|
||||
* imageHeight0: boolean,
|
||||
* pathEmptyD: boolean,
|
||||
* polylineEmptyPoints: boolean,
|
||||
* polygonEmptyPoints: boolean,
|
||||
* }>}
|
||||
*/
|
||||
exports.fn = (root, params) => {
|
||||
const {
|
||||
isHidden = true,
|
||||
displayNone = true,
|
||||
opacity0 = true,
|
||||
circleR0 = true,
|
||||
ellipseRX0 = true,
|
||||
ellipseRY0 = true,
|
||||
rectWidth0 = true,
|
||||
rectHeight0 = true,
|
||||
patternWidth0 = true,
|
||||
patternHeight0 = true,
|
||||
imageWidth0 = true,
|
||||
imageHeight0 = true,
|
||||
pathEmptyD = true,
|
||||
polylineEmptyPoints = true,
|
||||
polygonEmptyPoints = true,
|
||||
} = params;
|
||||
const stylesheet = collectStylesheet(root);
|
||||
|
||||
return {
|
||||
element: {
|
||||
enter: (node, parentNode) => {
|
||||
// Removes hidden elements
|
||||
// https://www.w3schools.com/cssref/pr_class_visibility.asp
|
||||
const computedStyle = computeStyle(stylesheet, node);
|
||||
if (
|
||||
isHidden &&
|
||||
computedStyle.visibility &&
|
||||
computedStyle.visibility.type === 'static' &&
|
||||
computedStyle.visibility.value === 'hidden' &&
|
||||
// keep if any descendant enables visibility
|
||||
querySelector(node, '[visibility=visible]') == null
|
||||
) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
return;
|
||||
}
|
||||
|
||||
// display="none"
|
||||
//
|
||||
// https://www.w3.org/TR/SVG11/painting.html#DisplayProperty
|
||||
// "A value of display: none indicates that the given element
|
||||
// and its children shall not be rendered directly"
|
||||
if (
|
||||
displayNone &&
|
||||
computedStyle.display &&
|
||||
computedStyle.display.type === 'static' &&
|
||||
computedStyle.display.value === 'none' &&
|
||||
// markers with display: none still rendered
|
||||
node.name !== 'marker'
|
||||
) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
return;
|
||||
}
|
||||
|
||||
// opacity="0"
|
||||
//
|
||||
// https://www.w3.org/TR/SVG11/masking.html#ObjectAndGroupOpacityProperties
|
||||
if (
|
||||
opacity0 &&
|
||||
computedStyle.opacity &&
|
||||
computedStyle.opacity.type === 'static' &&
|
||||
computedStyle.opacity.value === '0' &&
|
||||
// transparent element inside clipPath still affect clipped elements
|
||||
closestByName(node, 'clipPath') == null
|
||||
) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
return;
|
||||
}
|
||||
|
||||
// Circles with zero radius
|
||||
//
|
||||
// https://www.w3.org/TR/SVG11/shapes.html#CircleElementRAttribute
|
||||
// "A value of zero disables rendering of the element"
|
||||
//
|
||||
// <circle r="0">
|
||||
if (
|
||||
circleR0 &&
|
||||
node.name === 'circle' &&
|
||||
node.children.length === 0 &&
|
||||
node.attributes.r === '0'
|
||||
) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ellipse with zero x-axis radius
|
||||
//
|
||||
// https://www.w3.org/TR/SVG11/shapes.html#EllipseElementRXAttribute
|
||||
// "A value of zero disables rendering of the element"
|
||||
//
|
||||
// <ellipse rx="0">
|
||||
if (
|
||||
ellipseRX0 &&
|
||||
node.name === 'ellipse' &&
|
||||
node.children.length === 0 &&
|
||||
node.attributes.rx === '0'
|
||||
) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ellipse with zero y-axis radius
|
||||
//
|
||||
// https://www.w3.org/TR/SVG11/shapes.html#EllipseElementRYAttribute
|
||||
// "A value of zero disables rendering of the element"
|
||||
//
|
||||
// <ellipse ry="0">
|
||||
if (
|
||||
ellipseRY0 &&
|
||||
node.name === 'ellipse' &&
|
||||
node.children.length === 0 &&
|
||||
node.attributes.ry === '0'
|
||||
) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
return;
|
||||
}
|
||||
|
||||
// Rectangle with zero width
|
||||
//
|
||||
// https://www.w3.org/TR/SVG11/shapes.html#RectElementWidthAttribute
|
||||
// "A value of zero disables rendering of the element"
|
||||
//
|
||||
// <rect width="0">
|
||||
if (
|
||||
rectWidth0 &&
|
||||
node.name === 'rect' &&
|
||||
node.children.length === 0 &&
|
||||
node.attributes.width === '0'
|
||||
) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
return;
|
||||
}
|
||||
|
||||
// Rectangle with zero height
|
||||
//
|
||||
// https://www.w3.org/TR/SVG11/shapes.html#RectElementHeightAttribute
|
||||
// "A value of zero disables rendering of the element"
|
||||
//
|
||||
// <rect height="0">
|
||||
if (
|
||||
rectHeight0 &&
|
||||
rectWidth0 &&
|
||||
node.name === 'rect' &&
|
||||
node.children.length === 0 &&
|
||||
node.attributes.height === '0'
|
||||
) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
return;
|
||||
}
|
||||
|
||||
// Pattern with zero width
|
||||
//
|
||||
// https://www.w3.org/TR/SVG11/pservers.html#PatternElementWidthAttribute
|
||||
// "A value of zero disables rendering of the element (i.e., no paint is applied)"
|
||||
//
|
||||
// <pattern width="0">
|
||||
if (
|
||||
patternWidth0 &&
|
||||
node.name === 'pattern' &&
|
||||
node.attributes.width === '0'
|
||||
) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
return;
|
||||
}
|
||||
|
||||
// Pattern with zero height
|
||||
//
|
||||
// https://www.w3.org/TR/SVG11/pservers.html#PatternElementHeightAttribute
|
||||
// "A value of zero disables rendering of the element (i.e., no paint is applied)"
|
||||
//
|
||||
// <pattern height="0">
|
||||
if (
|
||||
patternHeight0 &&
|
||||
node.name === 'pattern' &&
|
||||
node.attributes.height === '0'
|
||||
) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
return;
|
||||
}
|
||||
|
||||
// Image with zero width
|
||||
//
|
||||
// https://www.w3.org/TR/SVG11/struct.html#ImageElementWidthAttribute
|
||||
// "A value of zero disables rendering of the element"
|
||||
//
|
||||
// <image width="0">
|
||||
if (
|
||||
imageWidth0 &&
|
||||
node.name === 'image' &&
|
||||
node.attributes.width === '0'
|
||||
) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
return;
|
||||
}
|
||||
|
||||
// Image with zero height
|
||||
//
|
||||
// https://www.w3.org/TR/SVG11/struct.html#ImageElementHeightAttribute
|
||||
// "A value of zero disables rendering of the element"
|
||||
//
|
||||
// <image height="0">
|
||||
if (
|
||||
imageHeight0 &&
|
||||
node.name === 'image' &&
|
||||
node.attributes.height === '0'
|
||||
) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
return;
|
||||
}
|
||||
|
||||
// Path with empty data
|
||||
//
|
||||
// https://www.w3.org/TR/SVG11/paths.html#DAttribute
|
||||
//
|
||||
// <path d=""/>
|
||||
if (pathEmptyD && node.name === 'path') {
|
||||
if (node.attributes.d == null) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
return;
|
||||
}
|
||||
const pathData = parsePathData(node.attributes.d);
|
||||
if (pathData.length === 0) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
return;
|
||||
}
|
||||
// keep single point paths for markers
|
||||
if (
|
||||
pathData.length === 1 &&
|
||||
computedStyle['marker-start'] == null &&
|
||||
computedStyle['marker-end'] == null
|
||||
) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Polyline with empty points
|
||||
//
|
||||
// https://www.w3.org/TR/SVG11/shapes.html#PolylineElementPointsAttribute
|
||||
//
|
||||
// <polyline points="">
|
||||
if (
|
||||
polylineEmptyPoints &&
|
||||
node.name === 'polyline' &&
|
||||
node.attributes.points == null
|
||||
) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
return;
|
||||
}
|
||||
|
||||
// Polygon with empty points
|
||||
//
|
||||
// https://www.w3.org/TR/SVG11/shapes.html#PolygonElementPointsAttribute
|
||||
//
|
||||
// <polygon points="">
|
||||
if (
|
||||
polygonEmptyPoints &&
|
||||
node.name === 'polygon' &&
|
||||
node.attributes.points == null
|
||||
) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
return;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
29
node_modules/svgo/plugins/removeMetadata.js
generated
vendored
Normal file
29
node_modules/svgo/plugins/removeMetadata.js
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
const { detachNodeFromParent } = require('../lib/xast.js');
|
||||
|
||||
exports.name = 'removeMetadata';
|
||||
exports.type = 'visitor';
|
||||
exports.active = true;
|
||||
exports.description = 'removes <metadata>';
|
||||
|
||||
/**
|
||||
* Remove <metadata>.
|
||||
*
|
||||
* https://www.w3.org/TR/SVG11/metadata.html
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<void>}
|
||||
*/
|
||||
exports.fn = () => {
|
||||
return {
|
||||
element: {
|
||||
enter: (node, parentNode) => {
|
||||
if (node.name === 'metadata') {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
38
node_modules/svgo/plugins/removeNonInheritableGroupAttrs.js
generated
vendored
Normal file
38
node_modules/svgo/plugins/removeNonInheritableGroupAttrs.js
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
exports.name = 'removeNonInheritableGroupAttrs';
|
||||
|
||||
exports.type = 'perItem';
|
||||
|
||||
exports.active = true;
|
||||
|
||||
exports.description =
|
||||
'removes non-inheritable group’s presentational attributes';
|
||||
|
||||
const {
|
||||
inheritableAttrs,
|
||||
attrsGroups,
|
||||
presentationNonInheritableGroupAttrs,
|
||||
} = require('./_collections');
|
||||
|
||||
/**
|
||||
* Remove non-inheritable group's "presentation" attributes.
|
||||
*
|
||||
* @param {Object} item current iteration item
|
||||
* @return {Boolean} if false, item will be filtered out
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*/
|
||||
exports.fn = function (item) {
|
||||
if (item.type === 'element' && item.name === 'g') {
|
||||
for (const name of Object.keys(item.attributes)) {
|
||||
if (
|
||||
attrsGroups.presentation.includes(name) === true &&
|
||||
inheritableAttrs.includes(name) === false &&
|
||||
presentationNonInheritableGroupAttrs.includes(name) === false
|
||||
) {
|
||||
delete item.attributes[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
138
node_modules/svgo/plugins/removeOffCanvasPaths.js
generated
vendored
Normal file
138
node_modules/svgo/plugins/removeOffCanvasPaths.js
generated
vendored
Normal file
@ -0,0 +1,138 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @typedef {import('../lib/types').PathDataItem} PathDataItem
|
||||
*/
|
||||
|
||||
const { visitSkip, detachNodeFromParent } = require('../lib/xast.js');
|
||||
const { parsePathData } = require('../lib/path.js');
|
||||
const { intersects } = require('./_path.js');
|
||||
|
||||
exports.type = 'visitor';
|
||||
exports.name = 'removeOffCanvasPaths';
|
||||
exports.active = false;
|
||||
exports.description =
|
||||
'removes elements that are drawn outside of the viewbox (disabled by default)';
|
||||
|
||||
/**
|
||||
* Remove elements that are drawn outside of the viewbox.
|
||||
*
|
||||
* @author JoshyPHP
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<void>}
|
||||
*/
|
||||
exports.fn = () => {
|
||||
/**
|
||||
* @type {null | {
|
||||
* top: number,
|
||||
* right: number,
|
||||
* bottom: number,
|
||||
* left: number,
|
||||
* width: number,
|
||||
* height: number
|
||||
* }}
|
||||
*/
|
||||
let viewBoxData = null;
|
||||
|
||||
return {
|
||||
element: {
|
||||
enter: (node, parentNode) => {
|
||||
if (node.name === 'svg' && parentNode.type === 'root') {
|
||||
let viewBox = '';
|
||||
// find viewbox
|
||||
if (node.attributes.viewBox != null) {
|
||||
// remove commas and plus signs, normalize and trim whitespace
|
||||
viewBox = node.attributes.viewBox;
|
||||
} else if (
|
||||
node.attributes.height != null &&
|
||||
node.attributes.width != null
|
||||
) {
|
||||
viewBox = `0 0 ${node.attributes.width} ${node.attributes.height}`;
|
||||
}
|
||||
|
||||
// parse viewbox
|
||||
// remove commas and plus signs, normalize and trim whitespace
|
||||
viewBox = viewBox
|
||||
.replace(/[,+]|px/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.replace(/^\s*|\s*$/g, '');
|
||||
// ensure that the dimensions are 4 values separated by space
|
||||
const m =
|
||||
/^(-?\d*\.?\d+) (-?\d*\.?\d+) (\d*\.?\d+) (\d*\.?\d+)$/.exec(
|
||||
viewBox
|
||||
);
|
||||
if (m == null) {
|
||||
return;
|
||||
}
|
||||
const left = Number.parseFloat(m[1]);
|
||||
const top = Number.parseFloat(m[2]);
|
||||
const width = Number.parseFloat(m[3]);
|
||||
const height = Number.parseFloat(m[4]);
|
||||
|
||||
// store the viewBox boundaries
|
||||
viewBoxData = {
|
||||
left,
|
||||
top,
|
||||
right: left + width,
|
||||
bottom: top + height,
|
||||
width,
|
||||
height,
|
||||
};
|
||||
}
|
||||
|
||||
// consider that any item with a transform attribute is visible
|
||||
if (node.attributes.transform != null) {
|
||||
return visitSkip;
|
||||
}
|
||||
|
||||
if (
|
||||
node.name === 'path' &&
|
||||
node.attributes.d != null &&
|
||||
viewBoxData != null
|
||||
) {
|
||||
const pathData = parsePathData(node.attributes.d);
|
||||
|
||||
// consider that a M command within the viewBox is visible
|
||||
let visible = false;
|
||||
for (const pathDataItem of pathData) {
|
||||
if (pathDataItem.command === 'M') {
|
||||
const [x, y] = pathDataItem.args;
|
||||
if (
|
||||
x >= viewBoxData.left &&
|
||||
x <= viewBoxData.right &&
|
||||
y >= viewBoxData.top &&
|
||||
y <= viewBoxData.bottom
|
||||
) {
|
||||
visible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathData.length === 2) {
|
||||
// close the path too short for intersects()
|
||||
pathData.push({ command: 'z', args: [] });
|
||||
}
|
||||
|
||||
const { left, top, width, height } = viewBoxData;
|
||||
/**
|
||||
* @type {Array<PathDataItem>}
|
||||
*/
|
||||
const viewBoxPathData = [
|
||||
{ command: 'M', args: [left, top] },
|
||||
{ command: 'h', args: [width] },
|
||||
{ command: 'v', args: [height] },
|
||||
{ command: 'H', args: [left] },
|
||||
{ command: 'z', args: [] },
|
||||
];
|
||||
|
||||
if (intersects(viewBoxPathData, pathData) === false) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
33
node_modules/svgo/plugins/removeRasterImages.js
generated
vendored
Normal file
33
node_modules/svgo/plugins/removeRasterImages.js
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
const { detachNodeFromParent } = require('../lib/xast.js');
|
||||
|
||||
exports.name = 'removeRasterImages';
|
||||
exports.type = 'visitor';
|
||||
exports.active = false;
|
||||
exports.description = 'removes raster images (disabled by default)';
|
||||
|
||||
/**
|
||||
* Remove raster images references in <image>.
|
||||
*
|
||||
* @see https://bugs.webkit.org/show_bug.cgi?id=63548
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<void>}
|
||||
*/
|
||||
exports.fn = () => {
|
||||
return {
|
||||
element: {
|
||||
enter: (node, parentNode) => {
|
||||
if (
|
||||
node.name === 'image' &&
|
||||
node.attributes['xlink:href'] != null &&
|
||||
/(\.|image\/)(jpg|png|gif)/.test(node.attributes['xlink:href'])
|
||||
) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
29
node_modules/svgo/plugins/removeScriptElement.js
generated
vendored
Normal file
29
node_modules/svgo/plugins/removeScriptElement.js
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
const { detachNodeFromParent } = require('../lib/xast.js');
|
||||
|
||||
exports.name = 'removeScriptElement';
|
||||
exports.type = 'visitor';
|
||||
exports.active = false;
|
||||
exports.description = 'removes <script> elements (disabled by default)';
|
||||
|
||||
/**
|
||||
* Remove <script>.
|
||||
*
|
||||
* https://www.w3.org/TR/SVG11/script.html
|
||||
*
|
||||
* @author Patrick Klingemann
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<void>}
|
||||
*/
|
||||
exports.fn = () => {
|
||||
return {
|
||||
element: {
|
||||
enter: (node, parentNode) => {
|
||||
if (node.name === 'script') {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
29
node_modules/svgo/plugins/removeStyleElement.js
generated
vendored
Normal file
29
node_modules/svgo/plugins/removeStyleElement.js
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
const { detachNodeFromParent } = require('../lib/xast.js');
|
||||
|
||||
exports.name = 'removeStyleElement';
|
||||
exports.type = 'visitor';
|
||||
exports.active = false;
|
||||
exports.description = 'removes <style> element (disabled by default)';
|
||||
|
||||
/**
|
||||
* Remove <style>.
|
||||
*
|
||||
* https://www.w3.org/TR/SVG11/styling.html#StyleElement
|
||||
*
|
||||
* @author Betsy Dupuis
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<void>}
|
||||
*/
|
||||
exports.fn = () => {
|
||||
return {
|
||||
element: {
|
||||
enter: (node, parentNode) => {
|
||||
if (node.name === 'style') {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
29
node_modules/svgo/plugins/removeTitle.js
generated
vendored
Normal file
29
node_modules/svgo/plugins/removeTitle.js
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
const { detachNodeFromParent } = require('../lib/xast.js');
|
||||
|
||||
exports.name = 'removeTitle';
|
||||
exports.type = 'visitor';
|
||||
exports.active = true;
|
||||
exports.description = 'removes <title>';
|
||||
|
||||
/**
|
||||
* Remove <title>.
|
||||
*
|
||||
* https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title
|
||||
*
|
||||
* @author Igor Kalashnikov
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<void>}
|
||||
*/
|
||||
exports.fn = () => {
|
||||
return {
|
||||
element: {
|
||||
enter: (node, parentNode) => {
|
||||
if (node.name === 'title') {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
218
node_modules/svgo/plugins/removeUnknownsAndDefaults.js
generated
vendored
Normal file
218
node_modules/svgo/plugins/removeUnknownsAndDefaults.js
generated
vendored
Normal file
@ -0,0 +1,218 @@
|
||||
'use strict';
|
||||
|
||||
const { visitSkip, detachNodeFromParent } = require('../lib/xast.js');
|
||||
const { collectStylesheet, computeStyle } = require('../lib/style.js');
|
||||
const {
|
||||
elems,
|
||||
attrsGroups,
|
||||
elemsGroups,
|
||||
attrsGroupsDefaults,
|
||||
presentationNonInheritableGroupAttrs,
|
||||
} = require('./_collections');
|
||||
|
||||
exports.type = 'visitor';
|
||||
exports.name = 'removeUnknownsAndDefaults';
|
||||
exports.active = true;
|
||||
exports.description =
|
||||
'removes unknown elements content and attributes, removes attrs with default values';
|
||||
|
||||
// resolve all groups references
|
||||
|
||||
/**
|
||||
* @type {Map<string, Set<string>>}
|
||||
*/
|
||||
const allowedChildrenPerElement = new Map();
|
||||
/**
|
||||
* @type {Map<string, Set<string>>}
|
||||
*/
|
||||
const allowedAttributesPerElement = new Map();
|
||||
/**
|
||||
* @type {Map<string, Map<string, string>>}
|
||||
*/
|
||||
const attributesDefaultsPerElement = new Map();
|
||||
|
||||
for (const [name, config] of Object.entries(elems)) {
|
||||
/**
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
const allowedChildren = new Set();
|
||||
if (config.content) {
|
||||
for (const elementName of config.content) {
|
||||
allowedChildren.add(elementName);
|
||||
}
|
||||
}
|
||||
if (config.contentGroups) {
|
||||
for (const contentGroupName of config.contentGroups) {
|
||||
const elemsGroup = elemsGroups[contentGroupName];
|
||||
if (elemsGroup) {
|
||||
for (const elementName of elemsGroup) {
|
||||
allowedChildren.add(elementName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
const allowedAttributes = new Set();
|
||||
if (config.attrs) {
|
||||
for (const attrName of config.attrs) {
|
||||
allowedAttributes.add(attrName);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @type {Map<string, string>}
|
||||
*/
|
||||
const attributesDefaults = new Map();
|
||||
if (config.defaults) {
|
||||
for (const [attrName, defaultValue] of Object.entries(config.defaults)) {
|
||||
attributesDefaults.set(attrName, defaultValue);
|
||||
}
|
||||
}
|
||||
for (const attrsGroupName of config.attrsGroups) {
|
||||
const attrsGroup = attrsGroups[attrsGroupName];
|
||||
if (attrsGroup) {
|
||||
for (const attrName of attrsGroup) {
|
||||
allowedAttributes.add(attrName);
|
||||
}
|
||||
}
|
||||
const groupDefaults = attrsGroupsDefaults[attrsGroupName];
|
||||
if (groupDefaults) {
|
||||
for (const [attrName, defaultValue] of Object.entries(groupDefaults)) {
|
||||
attributesDefaults.set(attrName, defaultValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
allowedChildrenPerElement.set(name, allowedChildren);
|
||||
allowedAttributesPerElement.set(name, allowedAttributes);
|
||||
attributesDefaultsPerElement.set(name, attributesDefaults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove unknown elements content and attributes,
|
||||
* remove attributes with default values.
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<{
|
||||
* unknownContent?: boolean,
|
||||
* unknownAttrs?: boolean,
|
||||
* defaultAttrs?: boolean,
|
||||
* uselessOverrides?: boolean,
|
||||
* keepDataAttrs?: boolean,
|
||||
* keepAriaAttrs?: boolean,
|
||||
* keepRoleAttr?: boolean,
|
||||
* }>}
|
||||
*/
|
||||
exports.fn = (root, params) => {
|
||||
const {
|
||||
unknownContent = true,
|
||||
unknownAttrs = true,
|
||||
defaultAttrs = true,
|
||||
uselessOverrides = true,
|
||||
keepDataAttrs = true,
|
||||
keepAriaAttrs = true,
|
||||
keepRoleAttr = false,
|
||||
} = params;
|
||||
const stylesheet = collectStylesheet(root);
|
||||
|
||||
return {
|
||||
element: {
|
||||
enter: (node, parentNode) => {
|
||||
// skip namespaced elements
|
||||
if (node.name.includes(':')) {
|
||||
return;
|
||||
}
|
||||
// skip visiting foreignObject subtree
|
||||
if (node.name === 'foreignObject') {
|
||||
return visitSkip;
|
||||
}
|
||||
|
||||
// remove unknown element's content
|
||||
if (unknownContent && parentNode.type === 'element') {
|
||||
const allowedChildren = allowedChildrenPerElement.get(
|
||||
parentNode.name
|
||||
);
|
||||
if (allowedChildren == null || allowedChildren.size === 0) {
|
||||
// remove unknown elements
|
||||
if (allowedChildrenPerElement.get(node.name) == null) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// remove not allowed children
|
||||
if (allowedChildren.has(node.name) === false) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const allowedAttributes = allowedAttributesPerElement.get(node.name);
|
||||
const attributesDefaults = attributesDefaultsPerElement.get(node.name);
|
||||
const computedParentStyle =
|
||||
parentNode.type === 'element'
|
||||
? computeStyle(stylesheet, parentNode)
|
||||
: null;
|
||||
|
||||
// remove element's unknown attrs and attrs with default values
|
||||
for (const [name, value] of Object.entries(node.attributes)) {
|
||||
if (keepDataAttrs && name.startsWith('data-')) {
|
||||
continue;
|
||||
}
|
||||
if (keepAriaAttrs && name.startsWith('aria-')) {
|
||||
continue;
|
||||
}
|
||||
if (keepRoleAttr && name === 'role') {
|
||||
continue;
|
||||
}
|
||||
// skip xmlns attribute
|
||||
if (name === 'xmlns') {
|
||||
continue;
|
||||
}
|
||||
// skip namespaced attributes except xml:* and xlink:*
|
||||
if (name.includes(':')) {
|
||||
const [prefix] = name.split(':');
|
||||
if (prefix !== 'xml' && prefix !== 'xlink') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
unknownAttrs &&
|
||||
allowedAttributes &&
|
||||
allowedAttributes.has(name) === false
|
||||
) {
|
||||
delete node.attributes[name];
|
||||
}
|
||||
if (
|
||||
defaultAttrs &&
|
||||
node.attributes.id == null &&
|
||||
attributesDefaults &&
|
||||
attributesDefaults.get(name) === value
|
||||
) {
|
||||
// keep defaults if parent has own or inherited style
|
||||
if (
|
||||
computedParentStyle == null ||
|
||||
computedParentStyle[name] == null
|
||||
) {
|
||||
delete node.attributes[name];
|
||||
}
|
||||
}
|
||||
if (uselessOverrides && node.attributes.id == null) {
|
||||
const style =
|
||||
computedParentStyle == null ? null : computedParentStyle[name];
|
||||
if (
|
||||
presentationNonInheritableGroupAttrs.includes(name) === false &&
|
||||
style != null &&
|
||||
style.type === 'static' &&
|
||||
style.value === value
|
||||
) {
|
||||
delete node.attributes[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
61
node_modules/svgo/plugins/removeUnusedNS.js
generated
vendored
Normal file
61
node_modules/svgo/plugins/removeUnusedNS.js
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
'use strict';
|
||||
|
||||
exports.type = 'visitor';
|
||||
exports.name = 'removeUnusedNS';
|
||||
exports.active = true;
|
||||
exports.description = 'removes unused namespaces declaration';
|
||||
|
||||
/**
|
||||
* Remove unused namespaces declaration from svg element
|
||||
* which are not used in elements or attributes
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<void>}
|
||||
*/
|
||||
exports.fn = () => {
|
||||
/**
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
const unusedNamespaces = new Set();
|
||||
return {
|
||||
element: {
|
||||
enter: (node, parentNode) => {
|
||||
// collect all namespaces from svg element
|
||||
// (such as xmlns:xlink="http://www.w3.org/1999/xlink")
|
||||
if (node.name === 'svg' && parentNode.type === 'root') {
|
||||
for (const name of Object.keys(node.attributes)) {
|
||||
if (name.startsWith('xmlns:')) {
|
||||
const local = name.slice('xmlns:'.length);
|
||||
unusedNamespaces.add(local);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (unusedNamespaces.size !== 0) {
|
||||
// preserve namespace used in nested elements names
|
||||
if (node.name.includes(':')) {
|
||||
const [ns] = node.name.split(':');
|
||||
if (unusedNamespaces.has(ns)) {
|
||||
unusedNamespaces.delete(ns);
|
||||
}
|
||||
}
|
||||
// preserve namespace used in nested elements attributes
|
||||
for (const name of Object.keys(node.attributes)) {
|
||||
if (name.includes(':')) {
|
||||
const [ns] = name.split(':');
|
||||
unusedNamespaces.delete(ns);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
exit: (node, parentNode) => {
|
||||
// remove unused namespace attributes from svg element
|
||||
if (node.name === 'svg' && parentNode.type === 'root') {
|
||||
for (const name of unusedNamespaces) {
|
||||
delete node.attributes[`xmlns:${name}`];
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
65
node_modules/svgo/plugins/removeUselessDefs.js
generated
vendored
Normal file
65
node_modules/svgo/plugins/removeUselessDefs.js
generated
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @typedef {import('../lib/types').XastElement} XastElement
|
||||
*/
|
||||
|
||||
const { detachNodeFromParent } = require('../lib/xast.js');
|
||||
const { elemsGroups } = require('./_collections.js');
|
||||
|
||||
exports.type = 'visitor';
|
||||
exports.name = 'removeUselessDefs';
|
||||
exports.active = true;
|
||||
exports.description = 'removes elements in <defs> without id';
|
||||
|
||||
/**
|
||||
* Removes content of defs and properties that aren't rendered directly without ids.
|
||||
*
|
||||
* @author Lev Solntsev
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<void>}
|
||||
*/
|
||||
exports.fn = () => {
|
||||
return {
|
||||
element: {
|
||||
enter: (node, parentNode) => {
|
||||
if (node.name === 'defs') {
|
||||
/**
|
||||
* @type {Array<XastElement>}
|
||||
*/
|
||||
const usefulNodes = [];
|
||||
collectUsefulNodes(node, usefulNodes);
|
||||
if (usefulNodes.length === 0) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
}
|
||||
// TODO remove in SVGO 3
|
||||
for (const usefulNode of usefulNodes) {
|
||||
// @ts-ignore parentNode is legacy
|
||||
usefulNode.parentNode = node;
|
||||
}
|
||||
node.children = usefulNodes;
|
||||
} else if (
|
||||
elemsGroups.nonRendering.includes(node.name) &&
|
||||
node.attributes.id == null
|
||||
) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {(node: XastElement, usefulNodes: Array<XastElement>) => void}
|
||||
*/
|
||||
const collectUsefulNodes = (node, usefulNodes) => {
|
||||
for (const child of node.children) {
|
||||
if (child.type === 'element') {
|
||||
if (child.attributes.id != null || child.name === 'style') {
|
||||
usefulNodes.push(child);
|
||||
} else {
|
||||
collectUsefulNodes(child, usefulNodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
144
node_modules/svgo/plugins/removeUselessStrokeAndFill.js
generated
vendored
Normal file
144
node_modules/svgo/plugins/removeUselessStrokeAndFill.js
generated
vendored
Normal file
@ -0,0 +1,144 @@
|
||||
'use strict';
|
||||
|
||||
const { visit, visitSkip, detachNodeFromParent } = require('../lib/xast.js');
|
||||
const { collectStylesheet, computeStyle } = require('../lib/style.js');
|
||||
const { elemsGroups } = require('./_collections.js');
|
||||
|
||||
exports.type = 'visitor';
|
||||
exports.name = 'removeUselessStrokeAndFill';
|
||||
exports.active = true;
|
||||
exports.description = 'removes useless stroke and fill attributes';
|
||||
|
||||
/**
|
||||
* Remove useless stroke and fill attrs.
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<{
|
||||
* stroke?: boolean,
|
||||
* fill?: boolean,
|
||||
* removeNone?: boolean
|
||||
* }>}
|
||||
*/
|
||||
exports.fn = (root, params) => {
|
||||
const {
|
||||
stroke: removeStroke = true,
|
||||
fill: removeFill = true,
|
||||
removeNone = false,
|
||||
} = params;
|
||||
|
||||
// style and script elements deoptimise this plugin
|
||||
let hasStyleOrScript = false;
|
||||
visit(root, {
|
||||
element: {
|
||||
enter: (node) => {
|
||||
if (node.name === 'style' || node.name === 'script') {
|
||||
hasStyleOrScript = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
if (hasStyleOrScript) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const stylesheet = collectStylesheet(root);
|
||||
|
||||
return {
|
||||
element: {
|
||||
enter: (node, parentNode) => {
|
||||
// id attribute deoptimise the whole subtree
|
||||
if (node.attributes.id != null) {
|
||||
return visitSkip;
|
||||
}
|
||||
if (elemsGroups.shape.includes(node.name) == false) {
|
||||
return;
|
||||
}
|
||||
const computedStyle = computeStyle(stylesheet, node);
|
||||
const stroke = computedStyle.stroke;
|
||||
const strokeOpacity = computedStyle['stroke-opacity'];
|
||||
const strokeWidth = computedStyle['stroke-width'];
|
||||
const markerEnd = computedStyle['marker-end'];
|
||||
const fill = computedStyle.fill;
|
||||
const fillOpacity = computedStyle['fill-opacity'];
|
||||
const computedParentStyle =
|
||||
parentNode.type === 'element'
|
||||
? computeStyle(stylesheet, parentNode)
|
||||
: null;
|
||||
const parentStroke =
|
||||
computedParentStyle == null ? null : computedParentStyle.stroke;
|
||||
|
||||
// remove stroke*
|
||||
if (removeStroke) {
|
||||
if (
|
||||
stroke == null ||
|
||||
(stroke.type === 'static' && stroke.value == 'none') ||
|
||||
(strokeOpacity != null &&
|
||||
strokeOpacity.type === 'static' &&
|
||||
strokeOpacity.value === '0') ||
|
||||
(strokeWidth != null &&
|
||||
strokeWidth.type === 'static' &&
|
||||
strokeWidth.value === '0')
|
||||
) {
|
||||
// stroke-width may affect the size of marker-end
|
||||
// marker is not visible when stroke-width is 0
|
||||
if (
|
||||
(strokeWidth != null &&
|
||||
strokeWidth.type === 'static' &&
|
||||
strokeWidth.value === '0') ||
|
||||
markerEnd == null
|
||||
) {
|
||||
for (const name of Object.keys(node.attributes)) {
|
||||
if (name.startsWith('stroke')) {
|
||||
delete node.attributes[name];
|
||||
}
|
||||
}
|
||||
// set explicit none to not inherit from parent
|
||||
if (
|
||||
parentStroke != null &&
|
||||
parentStroke.type === 'static' &&
|
||||
parentStroke.value !== 'none'
|
||||
) {
|
||||
node.attributes.stroke = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove fill*
|
||||
if (removeFill) {
|
||||
if (
|
||||
(fill != null && fill.type === 'static' && fill.value === 'none') ||
|
||||
(fillOpacity != null &&
|
||||
fillOpacity.type === 'static' &&
|
||||
fillOpacity.value === '0')
|
||||
) {
|
||||
for (const name of Object.keys(node.attributes)) {
|
||||
if (name.startsWith('fill-')) {
|
||||
delete node.attributes[name];
|
||||
}
|
||||
}
|
||||
if (
|
||||
fill == null ||
|
||||
(fill.type === 'static' && fill.value !== 'none')
|
||||
) {
|
||||
node.attributes.fill = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (removeNone) {
|
||||
if (
|
||||
(stroke == null || node.attributes.stroke === 'none') &&
|
||||
((fill != null &&
|
||||
fill.type === 'static' &&
|
||||
fill.value === 'none') ||
|
||||
node.attributes.fill === 'none')
|
||||
) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
51
node_modules/svgo/plugins/removeViewBox.js
generated
vendored
Normal file
51
node_modules/svgo/plugins/removeViewBox.js
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
'use strict';
|
||||
|
||||
exports.type = 'visitor';
|
||||
exports.name = 'removeViewBox';
|
||||
exports.active = true;
|
||||
exports.description = 'removes viewBox attribute when possible';
|
||||
|
||||
const viewBoxElems = ['svg', 'pattern', 'symbol'];
|
||||
|
||||
/**
|
||||
* Remove viewBox attr which coincides with a width/height box.
|
||||
*
|
||||
* @see https://www.w3.org/TR/SVG11/coords.html#ViewBoxAttribute
|
||||
*
|
||||
* @example
|
||||
* <svg width="100" height="50" viewBox="0 0 100 50">
|
||||
* ⬇
|
||||
* <svg width="100" height="50">
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<void>}
|
||||
*/
|
||||
exports.fn = () => {
|
||||
return {
|
||||
element: {
|
||||
enter: (node, parentNode) => {
|
||||
if (
|
||||
viewBoxElems.includes(node.name) &&
|
||||
node.attributes.viewBox != null &&
|
||||
node.attributes.width != null &&
|
||||
node.attributes.height != null
|
||||
) {
|
||||
// TODO remove width/height for such case instead
|
||||
if (node.name === 'svg' && parentNode.type !== 'root') {
|
||||
return;
|
||||
}
|
||||
const nums = node.attributes.viewBox.split(/[ ,]+/g);
|
||||
if (
|
||||
nums[0] === '0' &&
|
||||
nums[1] === '0' &&
|
||||
node.attributes.width.replace(/px$/, '') === nums[2] && // could use parseFloat too
|
||||
node.attributes.height.replace(/px$/, '') === nums[3]
|
||||
) {
|
||||
delete node.attributes.viewBox;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
30
node_modules/svgo/plugins/removeXMLNS.js
generated
vendored
Normal file
30
node_modules/svgo/plugins/removeXMLNS.js
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
|
||||
exports.name = 'removeXMLNS';
|
||||
|
||||
exports.type = 'perItem';
|
||||
|
||||
exports.active = false;
|
||||
|
||||
exports.description =
|
||||
'removes xmlns attribute (for inline svg, disabled by default)';
|
||||
|
||||
/**
|
||||
* Remove the xmlns attribute when present.
|
||||
*
|
||||
* @example
|
||||
* <svg viewBox="0 0 100 50" xmlns="http://www.w3.org/2000/svg">
|
||||
* ↓
|
||||
* <svg viewBox="0 0 100 50">
|
||||
*
|
||||
* @param {Object} item current iteration item
|
||||
* @return {Boolean} if true, xmlns will be filtered out
|
||||
*
|
||||
* @author Ricardo Tomasi
|
||||
*/
|
||||
exports.fn = function (item) {
|
||||
if (item.type === 'element' && item.name === 'svg') {
|
||||
delete item.attributes.xmlns;
|
||||
delete item.attributes['xmlns:xlink'];
|
||||
}
|
||||
};
|
30
node_modules/svgo/plugins/removeXMLProcInst.js
generated
vendored
Normal file
30
node_modules/svgo/plugins/removeXMLProcInst.js
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
|
||||
const { detachNodeFromParent } = require('../lib/xast.js');
|
||||
|
||||
exports.name = 'removeXMLProcInst';
|
||||
exports.type = 'visitor';
|
||||
exports.active = true;
|
||||
exports.description = 'removes XML processing instructions';
|
||||
|
||||
/**
|
||||
* Remove XML Processing Instruction.
|
||||
*
|
||||
* @example
|
||||
* <?xml version="1.0" encoding="utf-8"?>
|
||||
*
|
||||
* @author Kir Belevich
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<void>}
|
||||
*/
|
||||
exports.fn = () => {
|
||||
return {
|
||||
instruction: {
|
||||
enter: (node, parentNode) => {
|
||||
if (node.name === 'xml') {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
113
node_modules/svgo/plugins/reusePaths.js
generated
vendored
Normal file
113
node_modules/svgo/plugins/reusePaths.js
generated
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @typedef {import('../lib/types').XastElement} XastElement
|
||||
* @typedef {import('../lib/types').XastParent} XastParent
|
||||
* @typedef {import('../lib/types').XastNode} XastNode
|
||||
*/
|
||||
|
||||
const JSAPI = require('../lib/svgo/jsAPI.js');
|
||||
|
||||
exports.type = 'visitor';
|
||||
exports.name = 'reusePaths';
|
||||
exports.active = false;
|
||||
exports.description =
|
||||
'Finds <path> elements with the same d, fill, and ' +
|
||||
'stroke, and converts them to <use> elements ' +
|
||||
'referencing a single <path> def.';
|
||||
|
||||
/**
|
||||
* Finds <path> elements with the same d, fill, and stroke, and converts them to
|
||||
* <use> elements referencing a single <path> def.
|
||||
*
|
||||
* @author Jacob Howcroft
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<void>}
|
||||
*/
|
||||
exports.fn = () => {
|
||||
/**
|
||||
* @type {Map<string, Array<XastElement>>}
|
||||
*/
|
||||
const paths = new Map();
|
||||
|
||||
return {
|
||||
element: {
|
||||
enter: (node) => {
|
||||
if (node.name === 'path' && node.attributes.d != null) {
|
||||
const d = node.attributes.d;
|
||||
const fill = node.attributes.fill || '';
|
||||
const stroke = node.attributes.stroke || '';
|
||||
const key = d + ';s:' + stroke + ';f:' + fill;
|
||||
let list = paths.get(key);
|
||||
if (list == null) {
|
||||
list = [];
|
||||
paths.set(key, list);
|
||||
}
|
||||
list.push(node);
|
||||
}
|
||||
},
|
||||
|
||||
exit: (node, parentNode) => {
|
||||
if (node.name === 'svg' && parentNode.type === 'root') {
|
||||
/**
|
||||
* @type {XastElement}
|
||||
*/
|
||||
const rawDefs = {
|
||||
type: 'element',
|
||||
name: 'defs',
|
||||
attributes: {},
|
||||
children: [],
|
||||
};
|
||||
/**
|
||||
* @type {XastElement}
|
||||
*/
|
||||
const defsTag = new JSAPI(rawDefs, node);
|
||||
let index = 0;
|
||||
for (const list of paths.values()) {
|
||||
if (list.length > 1) {
|
||||
// add reusable path to defs
|
||||
/**
|
||||
* @type {XastElement}
|
||||
*/
|
||||
const rawPath = {
|
||||
type: 'element',
|
||||
name: 'path',
|
||||
attributes: { ...list[0].attributes },
|
||||
children: [],
|
||||
};
|
||||
delete rawPath.attributes.transform;
|
||||
let id;
|
||||
if (rawPath.attributes.id == null) {
|
||||
id = 'reuse-' + index;
|
||||
index += 1;
|
||||
rawPath.attributes.id = id;
|
||||
} else {
|
||||
id = rawPath.attributes.id;
|
||||
delete list[0].attributes.id;
|
||||
}
|
||||
/**
|
||||
* @type {XastElement}
|
||||
*/
|
||||
const reusablePath = new JSAPI(rawPath, defsTag);
|
||||
defsTag.children.push(reusablePath);
|
||||
// convert paths to <use>
|
||||
for (const pathNode of list) {
|
||||
pathNode.name = 'use';
|
||||
pathNode.attributes['xlink:href'] = '#' + id;
|
||||
delete pathNode.attributes.d;
|
||||
delete pathNode.attributes.stroke;
|
||||
delete pathNode.attributes.fill;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (defsTag.children.length !== 0) {
|
||||
if (node.attributes['xmlns:xlink'] == null) {
|
||||
node.attributes['xmlns:xlink'] = 'http://www.w3.org/1999/xlink';
|
||||
}
|
||||
node.children.unshift(defsTag);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
113
node_modules/svgo/plugins/sortAttrs.js
generated
vendored
Normal file
113
node_modules/svgo/plugins/sortAttrs.js
generated
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
'use strict';
|
||||
|
||||
exports.type = 'visitor';
|
||||
exports.name = 'sortAttrs';
|
||||
exports.active = false;
|
||||
exports.description = 'Sort element attributes for better compression';
|
||||
|
||||
/**
|
||||
* Sort element attributes for better compression
|
||||
*
|
||||
* @author Nikolay Frantsev
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<{
|
||||
* order?: Array<string>
|
||||
* xmlnsOrder?: 'front' | 'alphabetical'
|
||||
* }>}
|
||||
*/
|
||||
exports.fn = (_root, params) => {
|
||||
const {
|
||||
order = [
|
||||
'id',
|
||||
'width',
|
||||
'height',
|
||||
'x',
|
||||
'x1',
|
||||
'x2',
|
||||
'y',
|
||||
'y1',
|
||||
'y2',
|
||||
'cx',
|
||||
'cy',
|
||||
'r',
|
||||
'fill',
|
||||
'stroke',
|
||||
'marker',
|
||||
'd',
|
||||
'points',
|
||||
],
|
||||
xmlnsOrder = 'front',
|
||||
} = params;
|
||||
|
||||
/**
|
||||
* @type {(name: string) => number}
|
||||
*/
|
||||
const getNsPriority = (name) => {
|
||||
if (xmlnsOrder === 'front') {
|
||||
// put xmlns first
|
||||
if (name === 'xmlns') {
|
||||
return 3;
|
||||
}
|
||||
// xmlns:* attributes second
|
||||
if (name.startsWith('xmlns:')) {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
// other namespaces after and sort them alphabetically
|
||||
if (name.includes(':')) {
|
||||
return 1;
|
||||
}
|
||||
// other attributes
|
||||
return 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {(a: [string, string], b: [string, string]) => number}
|
||||
*/
|
||||
const compareAttrs = ([aName], [bName]) => {
|
||||
// sort namespaces
|
||||
const aPriority = getNsPriority(aName);
|
||||
const bPriority = getNsPriority(bName);
|
||||
const priorityNs = bPriority - aPriority;
|
||||
if (priorityNs !== 0) {
|
||||
return priorityNs;
|
||||
}
|
||||
// extract the first part from attributes
|
||||
// for example "fill" from "fill" and "fill-opacity"
|
||||
const [aPart] = aName.split('-');
|
||||
const [bPart] = bName.split('-');
|
||||
// rely on alphabetical sort when the first part is the same
|
||||
if (aPart !== bPart) {
|
||||
const aInOrderFlag = order.includes(aPart) ? 1 : 0;
|
||||
const bInOrderFlag = order.includes(bPart) ? 1 : 0;
|
||||
// sort by position in order param
|
||||
if (aInOrderFlag === 1 && bInOrderFlag === 1) {
|
||||
return order.indexOf(aPart) - order.indexOf(bPart);
|
||||
}
|
||||
// put attributes from order param before others
|
||||
const priorityOrder = bInOrderFlag - aInOrderFlag;
|
||||
if (priorityOrder !== 0) {
|
||||
return priorityOrder;
|
||||
}
|
||||
}
|
||||
// sort alphabetically
|
||||
return aName < bName ? -1 : 1;
|
||||
};
|
||||
|
||||
return {
|
||||
element: {
|
||||
enter: (node) => {
|
||||
const attrs = Object.entries(node.attributes);
|
||||
attrs.sort(compareAttrs);
|
||||
/**
|
||||
* @type {Record<string, string>}
|
||||
*/
|
||||
const sortedAttributes = {};
|
||||
for (const [name, value] of attrs) {
|
||||
sortedAttributes[name] = value;
|
||||
}
|
||||
node.attributes = sortedAttributes;
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
60
node_modules/svgo/plugins/sortDefsChildren.js
generated
vendored
Normal file
60
node_modules/svgo/plugins/sortDefsChildren.js
generated
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
'use strict';
|
||||
|
||||
exports.type = 'visitor';
|
||||
exports.name = 'sortDefsChildren';
|
||||
exports.active = true;
|
||||
exports.description = 'Sorts children of <defs> to improve compression';
|
||||
|
||||
/**
|
||||
* Sorts children of defs in order to improve compression.
|
||||
* Sorted first by frequency then by element name length then by element name (to ensure grouping).
|
||||
*
|
||||
* @author David Leston
|
||||
*
|
||||
* @type {import('../lib/types').Plugin<void>}
|
||||
*/
|
||||
exports.fn = () => {
|
||||
return {
|
||||
element: {
|
||||
enter: (node) => {
|
||||
if (node.name === 'defs') {
|
||||
/**
|
||||
* @type {Map<string, number>}
|
||||
*/
|
||||
const frequencies = new Map();
|
||||
for (const child of node.children) {
|
||||
if (child.type === 'element') {
|
||||
const frequency = frequencies.get(child.name);
|
||||
if (frequency == null) {
|
||||
frequencies.set(child.name, 1);
|
||||
} else {
|
||||
frequencies.set(child.name, frequency + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
node.children.sort((a, b) => {
|
||||
if (a.type !== 'element' || b.type !== 'element') {
|
||||
return 0;
|
||||
}
|
||||
const aFrequency = frequencies.get(a.name);
|
||||
const bFrequency = frequencies.get(b.name);
|
||||
if (aFrequency != null && bFrequency != null) {
|
||||
const frequencyComparison = bFrequency - aFrequency;
|
||||
if (frequencyComparison !== 0) {
|
||||
return frequencyComparison;
|
||||
}
|
||||
}
|
||||
const lengthComparison = b.name.length - a.name.length;
|
||||
if (lengthComparison !== 0) {
|
||||
return lengthComparison;
|
||||
}
|
||||
if (a.name !== b.name) {
|
||||
return a.name > b.name ? -1 : 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
Reference in New Issue
Block a user