Socket
Socket
Sign inDemoInstall

svgo

Package Overview
Dependencies
Maintainers
3
Versions
104
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

svgo - npm Package Compare versions

Comparing version 2.4.0 to 2.5.0

lib/svgo/css-select-adapter.d.ts

76

lib/path.js
'use strict';
/**
* @typedef {import('./types').PathDataItem} PathDataItem
* @typedef {import('./types').PathDataCommand} PathDataCommand
*/
// Based on https://www.w3.org/TR/SVG11/paths.html#PathDataBNF

@@ -29,3 +34,3 @@

/**
* @param {string} c
* @type {(c: string) => c is PathDataCommand}
*/

@@ -37,3 +42,3 @@ const isCommand = (c) => {

/**
* @param {string} c
* @type {(c: string) => boolean}
*/

@@ -51,3 +56,3 @@ const isWsp = (c) => {

/**
* @param {string} c
* @type {(c: string) => boolean}
*/

@@ -67,5 +72,3 @@ const isDigit = (c) => {

/**
* @param {string} string
* @param {number} cursor
* @return {[number, number | null]}
* @type {(string: string, cursor: number) => [number, number | null]}
*/

@@ -137,6 +140,12 @@ const readNumber = (string, cursor) => {

/**
* @param {string} string
* @type {(string: string) => Array<PathDataItem>}
*/
const parsePathData = (string) => {
/**
* @type {Array<PathDataItem>}
*/
const pathData = [];
/**
* @type {null | PathDataCommand}
*/
let command = null;

@@ -240,11 +249,5 @@ let args = /** @type {number[]} */ ([]);

/**
* @typedef {{
* number: number;
* precision?: number;
* }} StringifyNumberOptions
* @type {(number: number, precision?: number) => string}
*/
/**
* @param {StringifyNumberOptions} param
*/
const stringifyNumber = ({ number, precision }) => {
const stringifyNumber = (number, precision) => {
if (precision != null) {

@@ -259,22 +262,13 @@ const ratio = 10 ** precision;

/**
* @typedef {{
* command: string;
* args: number[];
* precision?: number;
* disableSpaceAfterFlags?: boolean;
* }} StringifyArgsOptions
*/
/**
*
* Elliptical arc large-arc and sweep flags are rendered with spaces
* because many non-browser environments are not able to parse such paths
*
* @param {StringifyArgsOptions} param
* @type {(
* command: string,
* args: number[],
* precision?: number,
* disableSpaceAfterFlags?: boolean
* ) => string}
*/
const stringifyArgs = ({
command,
args,
precision,
disableSpaceAfterFlags,
}) => {
const stringifyArgs = (command, args, precision, disableSpaceAfterFlags) => {
let result = '';

@@ -284,3 +278,3 @@ let prev = '';

const number = args[i];
const numberString = stringifyNumber({ number, precision });
const numberString = stringifyNumber(number, precision);
if (

@@ -309,11 +303,4 @@ disableSpaceAfterFlags &&

/**
*
* @typedef {{
* command: string;
* args: number[];
* }} Command
*/
/**
* @typedef {{
* pathData: Command[];
* pathData: Array<PathDataItem>;
* precision?: number;

@@ -323,4 +310,5 @@ * disableSpaceAfterFlags?: boolean;

*/
/**
* @param {StringifyPathDataOptions} param
* @type {(options: StringifyPathDataOptions) => string}
*/

@@ -335,2 +323,5 @@ const stringifyPathData = ({ pathData, precision, disableSpaceAfterFlags }) => {

} else {
/**
* @type {PathDataItem}
*/
const last = combined[combined.length - 1];

@@ -363,4 +354,3 @@ // match leading moveto with following lineto

result +=
command +
stringifyArgs({ command, args, precision, disableSpaceAfterFlags });
command + stringifyArgs(command, args, precision, disableSpaceAfterFlags);
}

@@ -367,0 +357,0 @@ return result;

'use strict';
/**
* @typedef {import('css-tree').Rule} CsstreeRule
* @typedef {import('./types').Specificity} Specificity
* @typedef {import('./types').StylesheetRule} StylesheetRule
* @typedef {import('./types').StylesheetDeclaration} StylesheetDeclaration
* @typedef {import('./types').ComputedStyles} ComputedStyles
* @typedef {import('./types').XastRoot} XastRoot
* @typedef {import('./types').XastElement} XastElement
* @typedef {import('./types').XastParent} XastParent
* @typedef {import('./types').XastChild} XastChild
*/
const stable = require('stable');
const csstree = require('css-tree');
// @ts-ignore not defined in @types/csso
const specificity = require('csso/lib/restructure/prepare/specificity');
const { visit, matches } = require('./xast.js');
const { compareSpecificity } = require('./css-tools.js');
const {

@@ -14,5 +26,14 @@ attrsGroups,

// @ts-ignore not defined in @types/csstree
const csstreeWalkSkip = csstree.walk.skip;
/**
* @type {(ruleNode: CsstreeRule, dynamic: boolean) => StylesheetRule}
*/
const parseRule = (ruleNode, dynamic) => {
let selectors;
let selectorsSpecificity;
/**
* @type {Array<StylesheetDeclaration>}
*/
const declarations = [];

@@ -31,3 +52,3 @@ csstree.walk(ruleNode, (cssNode) => {

selectors = csstree.generate(newSelectorsNode);
return csstree.walk.skip;
return csstreeWalkSkip;
}

@@ -38,7 +59,10 @@ if (cssNode.type === 'Declaration') {

value: csstree.generate(cssNode.value),
important: cssNode.important,
important: cssNode.important === true,
});
return csstree.walk.skip;
return csstreeWalkSkip;
}
});
if (selectors == null || selectorsSpecificity == null) {
throw Error('assert');
}
return {

@@ -52,13 +76,22 @@ dynamic,

/**
* @type {(css: string, dynamic: boolean) => Array<StylesheetRule>}
*/
const parseStylesheet = (css, dynamic) => {
/**
* @type {Array<StylesheetRule>}
*/
const rules = [];
const ast = csstree.parse(css);
const ast = csstree.parse(css, {
parseValue: false,
parseAtrulePrelude: false,
});
csstree.walk(ast, (cssNode) => {
if (cssNode.type === 'Rule') {
rules.push(parseRule(cssNode, dynamic || false));
return csstree.walk.skip;
return csstreeWalkSkip;
}
if (cssNode.type === 'Atrule') {
if (cssNode.name === 'keyframes') {
return csstree.walk.skip;
return csstreeWalkSkip;
}

@@ -68,6 +101,6 @@ csstree.walk(cssNode, (ruleNode) => {

rules.push(parseRule(ruleNode, dynamic || true));
return csstree.walk.skip;
return csstreeWalkSkip;
}
});
return csstree.walk.skip;
return csstreeWalkSkip;
}

@@ -78,3 +111,33 @@ });

/**
* @type {(css: string) => Array<StylesheetDeclaration>}
*/
const parseStyleDeclarations = (css) => {
/**
* @type {Array<StylesheetDeclaration>}
*/
const declarations = [];
const ast = csstree.parse(css, {
context: 'declarationList',
parseValue: false,
});
csstree.walk(ast, (cssNode) => {
if (cssNode.type === 'Declaration') {
declarations.push({
name: cssNode.property,
value: csstree.generate(cssNode.value),
important: cssNode.important === true,
});
}
});
return declarations;
};
/**
* @type {(stylesheet: Array<StylesheetRule>, node: XastElement) => ComputedStyles}
*/
const computeOwnStyle = (stylesheet, node) => {
/**
* @type {ComputedStyles}
*/
const computedStyle = {};

@@ -116,5 +179,8 @@ const importantStyles = new Map();

// collect inline styles
for (const [name, { value, priority }] of node.style.properties) {
const styleDeclarations =
node.attributes.style == null
? []
: parseStyleDeclarations(node.attributes.style);
for (const { name, value, important } of styleDeclarations) {
const computed = computedStyle[name];
const important = priority === 'important';
if (computed && computed.type === 'dynamic') {

@@ -136,3 +202,27 @@ continue;

/**
* 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;
};
/**
* @type {(root: XastRoot) => Array<StylesheetRule>}
*/
const collectStylesheet = (root) => {
/**
* @type {Array<StylesheetRule>}
*/
const stylesheet = [];

@@ -170,2 +260,5 @@ // find and parse all styles

/**
* @type {(stylesheet: Array<StylesheetRule>, node: XastElement) => ComputedStyles}
*/
const computeStyle = (stylesheet, node) => {

@@ -175,3 +268,5 @@ // collect inherited styles

let parent = node;
// @ts-ignore parentNode is forbidden in public usage
while (parent.parentNode && parent.parentNode.type !== 'root') {
// @ts-ignore parentNode is forbidden in public usage
const inheritedStyles = computeOwnStyle(stylesheet, parent.parentNode);

@@ -188,7 +283,7 @@ for (const [name, computed] of Object.entries(inheritedStyles)) {

}
// @ts-ignore parentNode is forbidden in public usage
parent = parent.parentNode;
}
return computedStyles;
};
exports.computeStyle = computeStyle;
'use strict';
/**
* @param {any} node
* @return {node is any}
*/
const isTag = (node) => {

@@ -8,0 +4,0 @@ return node.type === 'element';

'use strict';
/**
* @typedef {import('../types').PathDataCommand} PathDataCommand
*/
/**
* Encode plain SVG data string into Data URI string.
*
* @param {string} str input string
* @param {string} type Data URI type
* @return {string} output string
* @type {(str: string, type?: 'base64' | 'enc' | 'unenc') => string}
*/
exports.encodeSVGDatauri = function (str, type) {
exports.encodeSVGDatauri = (str, type) => {
var prefix = 'data:image/svg+xml';

@@ -29,6 +31,5 @@ if (!type || type === 'base64') {

*
* @param {string} str input string
* @return {string} output string
* @type {(str: string) => string}
*/
exports.decodeSVGDatauri = function (str) {
exports.decodeSVGDatauri = (str) => {
var regexp = /data:image\/svg\+xml(;charset=[^;,]*)?(;base64)?,(.*)/;

@@ -56,2 +57,10 @@ var match = regexp.exec(str);

/**
* @typedef {{
* noSpaceAfterFlags?: boolean,
* leadingZero?: boolean,
* negativeExtraSpace?: boolean
* }} CleanupOutDataParams
*/
/**
* Convert a row of numbers to an optimized string view.

@@ -62,13 +71,13 @@ *

*
* @param {number[]} data
* @param {Object} params
* @param {string} [command] path data instruction
* @return {string}
* @type {(data: Array<number>, params: CleanupOutDataParams, command?: PathDataCommand) => string}
*/
exports.cleanupOutData = function (data, params, command) {
var str = '',
delimiter,
prev;
exports.cleanupOutData = (data, params, command) => {
let str = '';
let delimiter;
/**
* @type {number}
*/
let prev;
data.forEach(function (item, i) {
data.forEach((item, i) => {
// space delimiter by default

@@ -119,7 +128,5 @@ delimiter = ' ';

*
* @param {number} num input number
*
* @return {string} output number as string
* @type {(num: number) => string}
*/
var removeLeadingZero = function (num) {
const removeLeadingZero = (num) => {
var strNum = num.toString();

@@ -136,2 +143,5 @@

/**
* @type {(name: string) => { prefix: string, local: string }}
*/
const parseName = (name) => {

@@ -138,0 +148,0 @@ if (name == null) {

'use strict';
/**
* @typedef {import('./types').XastNode} XastNode
* @typedef {import('./types').XastChild} XastChild
* @typedef {import('./types').XastParent} XastParent
* @typedef {import('./types').Visitor} Visitor
*/
const { selectAll, selectOne, is } = require('css-select');

@@ -11,2 +18,5 @@ const xastAdaptor = require('./svgo/css-select-adapter.js');

/**
* @type {(node: XastNode, selector: string) => Array<XastChild>}
*/
const querySelectorAll = (node, selector) => {

@@ -17,2 +27,5 @@ return selectAll(selector, node, cssSelectOptions);

/**
* @type {(node: XastNode, selector: string) => null | XastChild}
*/
const querySelector = (node, selector) => {

@@ -23,2 +36,5 @@ return selectOne(selector, node, cssSelectOptions);

/**
* @type {(node: XastChild, selector: string) => boolean}
*/
const matches = (node, selector) => {

@@ -29,2 +45,5 @@ return is(node, selector, cssSelectOptions);

/**
* @type {(node: XastChild, name: string) => null | XastChild}
*/
const closestByName = (node, name) => {

@@ -36,2 +55,3 @@ let currentNode = node;

}
// @ts-ignore parentNode is hidden from public usage
currentNode = currentNode.parentNode;

@@ -46,2 +66,5 @@ }

/**
* @type {(node: any, fn: any) => any}
*/
const traverse = (node, fn) => {

@@ -61,6 +84,16 @@ if (fn(node) === traverseBreak) {

const visit = (node, visitor, parentNode = null) => {
const visitSkip = Symbol();
exports.visitSkip = visitSkip;
/**
* @type {(node: XastNode, visitor: Visitor, parentNode?: any) => void}
*/
const visit = (node, visitor, parentNode) => {
const callbacks = visitor[node.type];
if (callbacks && callbacks.enter) {
callbacks.enter(node, parentNode);
// @ts-ignore hard to infer
const symbol = callbacks.enter(node, parentNode);
if (symbol === visitSkip) {
return;
}
}

@@ -83,2 +116,3 @@ // visit root children

if (callbacks && callbacks.exit) {
// @ts-ignore hard to infer
callbacks.exit(node, parentNode);

@@ -89,2 +123,5 @@ }

/**
* @type {(node: XastChild, parentNode: XastParent) => void}
*/
const detachNodeFromParent = (node, parentNode) => {

@@ -91,0 +128,0 @@ // avoid splice to not break for loops

{
"name": "svgo",
"version": "2.4.0",
"version": "2.5.0",
"description": "Nodejs-based tool for optimizing SVG vector graphics files",

@@ -96,6 +96,6 @@ "keywords": [

"@trysound/sax": "0.1.1",
"colorette": "^1.2.2",
"commander": "^7.1.0",
"colorette": "^1.3.0",
"commander": "^7.2.0",
"css-select": "^4.1.3",
"css-tree": "^1.1.2",
"css-tree": "^1.1.3",
"csso": "^4.2.0",

@@ -107,6 +107,8 @@ "stable": "^0.1.8"

"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^11.2.0",
"@types/jest": "^27.0.0",
"@rollup/plugin-node-resolve": "^11.2.1",
"@types/css-tree": "^1.0.6",
"@types/csso": "^4.2.0",
"@types/jest": "^27.0.1",
"del": "^6.0.0",
"eslint": "^7.22.0",
"eslint": "^7.32.0",
"jest": "^27.0.6",

@@ -116,9 +118,9 @@ "mock-stdin": "^1.0.0",

"pixelmatch": "^5.2.1",
"playwright": "^1.9.2",
"playwright": "^1.14.0",
"pngjs": "^6.0.0",
"prettier": "^2.2.1",
"rollup": "^2.42.1",
"prettier": "^2.3.2",
"rollup": "^2.56.2",
"rollup-plugin-terser": "^7.0.2",
"tar-stream": "^2.2.0",
"typescript": "^4.2.3"
"typescript": "^4.3.5"
},

@@ -125,0 +127,0 @@ "engines": {

@@ -90,6 +90,5 @@ 'use strict';

} else {
elem.attributes[
'stroke-width'
] = strokeWidth.replace(regNumericValues, (num) =>
removeLeadingZero(num * scale)
elem.attributes['stroke-width'] = strokeWidth.replace(
regNumericValues,
(num) => removeLeadingZero(num * scale)
);

@@ -142,7 +141,8 @@ }

const applyMatrixToPathData = (pathData, matrix) => {
let start = [0, 0];
let cursor = [0, 0];
const start = [0, 0];
const cursor = [0, 0];
for (const pathItem of pathData) {
let { instruction: command, data: args } = pathItem;
let { command, args } = pathItem;
// moveto (x y)

@@ -328,2 +328,3 @@ if (command === 'M') {

// closepath
if (command === 'z' || command === 'Z') {

@@ -334,5 +335,5 @@ cursor[0] = start[0];

pathItem.instruction = command;
pathItem.data = args;
pathItem.command = command;
pathItem.args = args;
}
};
'use strict';
// https://www.w3.org/TR/SVG11/intro.html#Definitions
/**
* @type {Record<string, Array<string>>}
*/
exports.elemsGroups = {

@@ -91,2 +95,5 @@ animation: [

// https://www.w3.org/TR/SVG11/intro.html#Definitions
/**
* @type {Record<string, Array<string>>}
*/
exports.attrsGroups = {

@@ -192,2 +199,3 @@ animationAddition: ['additive', 'accumulate'],

'transform',
'transform-origin',
'unicode-bidi',

@@ -228,2 +236,5 @@ 'vector-effect',

/**
* @type {Record<string, Record<string, string>>}
*/
exports.attrsGroupsDefaults = {

@@ -294,2 +305,11 @@ core: { 'xml:space': 'default' },

// https://www.w3.org/TR/SVG11/eltindex.html
/**
* @type {Record<string, {
* attrsGroups: Array<string>,
* attrs?: Array<string>,
* defaults?: Record<string, string>,
* contentGroups?: Array<string>,
* content?: Array<string>,
* }>}
*/
exports.elems = {

@@ -965,4 +985,4 @@ a: {

defaults: {
x: 0,
y: 0,
x: '0',
y: '0',
},

@@ -1656,4 +1676,4 @@ },

defaults: {
refX: 0,
refY: 0,
refX: '0',
refY: '0',
},

@@ -1944,6 +1964,9 @@ contentGroups: [

'unicode-bidi',
'visibility',
];
// https://www.w3.org/TR/SVG11/single-page.html#types-ColorKeywords
/**
* https://www.w3.org/TR/SVG11/single-page.html#types-ColorKeywords
*
* @type {Record<string, string>}
*/
exports.colorsNames = {

@@ -2100,2 +2123,5 @@ aliceblue: '#f0f8ff',

/**
* @type {Record<string, string>}
*/
exports.colorsShortNames = {

@@ -2102,0 +2128,0 @@ '#f0ffff': 'azure',

'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;

@@ -10,24 +18,24 @@

*
* @param {String} pathString input string
* @param {Object} params plugin params
* @return {Array} output array
* @type {(path: XastElement) => Array<PathDataItem>}
*/
exports.path2js = function (path) {
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) {
if (command === 'Z' || command === 'z') {
pathData.push({ instruction: 'z' });
} else {
pathData.push({ instruction: command, data: args });
}
pathData.push({ command, args });
}
// First moveto is actually absolute. Subsequent coordinates were separated above.
if (pathData.length && pathData[0].instruction == 'm') {
pathData[0].instruction = 'M';
if (pathData.length && pathData[0].command == 'm') {
pathData[0].command = 'M';
}
// @ts-ignore legacy
path.pathJS = pathData;
return pathData;
};
exports.path2js = path2js;

@@ -37,283 +45,136 @@ /**

*
* @param {Array} data input data
* @return {Array} output data
* @type {(data: Array<PathDataItem>) => Array<PathDataItem>}
*
*/
var relative2absolute = (exports.relative2absolute = function (data) {
var currentPoint = [0, 0],
subpathPoint = [0, 0],
i;
const convertRelativeToAbsolute = (data) => {
/**
* @type {Array<PathDataItem>}
*/
const newData = [];
let start = [0, 0];
let cursor = [0, 0];
return data.map(function (item) {
var instruction = item.instruction,
itemData = item.data && item.data.slice();
for (let { command, args } of data) {
args = args.slice();
if (instruction == 'M') {
set(currentPoint, itemData);
set(subpathPoint, itemData);
} else if ('mlcsqt'.indexOf(instruction) > -1) {
for (i = 0; i < itemData.length; i++) {
itemData[i] += currentPoint[i % 2];
}
set(currentPoint, itemData);
// 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];
}
if (instruction == 'm') {
set(subpathPoint, itemData);
}
} else if (instruction == 'a') {
itemData[5] += currentPoint[0];
itemData[6] += currentPoint[1];
set(currentPoint, itemData);
} else if (instruction == 'h') {
itemData[0] += currentPoint[0];
currentPoint[0] = itemData[0];
} else if (instruction == 'v') {
itemData[0] += currentPoint[1];
currentPoint[1] = itemData[0];
} else if ('MZLCSQTA'.indexOf(instruction) > -1) {
set(currentPoint, itemData);
} else if (instruction == 'H') {
currentPoint[0] = itemData[0];
} else if (instruction == 'V') {
currentPoint[1] = itemData[0];
} else if (instruction == 'z') {
set(currentPoint, subpathPoint);
// horizontal lineto (x)
if (command === 'h') {
args[0] += cursor[0];
command = 'H';
}
if (command === 'H') {
cursor[0] = args[0];
}
return instruction == 'z'
? { instruction: 'z' }
: {
instruction: instruction.toUpperCase(),
data: itemData,
};
});
});
// vertical lineto (y)
if (command === 'v') {
args[0] += cursor[1];
command = 'V';
}
if (command === 'V') {
cursor[1] = args[0];
}
/**
* Compute Cubic Bézie bounding box.
*
* @see https://pomax.github.io/bezierinfo/
*
* @param {Float} xa
* @param {Float} ya
* @param {Float} xb
* @param {Float} yb
* @param {Float} xc
* @param {Float} yc
* @param {Float} xd
* @param {Float} yd
*
* @return {Object}
*/
exports.computeCubicBoundingBox = function (xa, ya, xb, yb, xc, yc, xd, yd) {
var minx = Number.POSITIVE_INFINITY,
miny = Number.POSITIVE_INFINITY,
maxx = Number.NEGATIVE_INFINITY,
maxy = Number.NEGATIVE_INFINITY,
ts,
t,
x,
y,
i;
// 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];
}
// X
if (xa < minx) {
minx = xa;
}
if (xa > maxx) {
maxx = xa;
}
if (xd < minx) {
minx = xd;
}
if (xd > maxx) {
maxx = xd;
}
// 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];
}
ts = computeCubicFirstDerivativeRoots(xa, xb, xc, xd);
for (i = 0; i < ts.length; i++) {
t = ts[i];
if (t >= 0 && t <= 1) {
x = computeCubicBaseValue(t, xa, xb, xc, xd);
// y = computeCubicBaseValue(t, ya, yb, yc, yd);
if (x < minx) {
minx = x;
}
if (x > maxx) {
maxx = x;
}
// 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];
}
// Y
if (ya < miny) {
miny = ya;
}
if (ya > maxy) {
maxy = ya;
}
if (yd < miny) {
miny = yd;
}
if (yd > maxy) {
maxy = yd;
}
ts = computeCubicFirstDerivativeRoots(ya, yb, yc, yd);
for (i = 0; i < ts.length; i++) {
t = ts[i];
if (t >= 0 && t <= 1) {
// x = computeCubicBaseValue(t, xa, xb, xc, xd);
y = computeCubicBaseValue(t, ya, yb, yc, yd);
if (y < miny) {
miny = y;
}
if (y > maxy) {
maxy = y;
}
// 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];
}
return {
minx: minx,
miny: miny,
maxx: maxx,
maxy: maxy,
};
};
// compute the value for the cubic bezier function at time=t
function computeCubicBaseValue(t, a, b, c, d) {
var mt = 1 - t;
return (
mt * mt * mt * a + 3 * mt * mt * t * b + 3 * mt * t * t * c + t * t * t * d
);
}
// compute the value for the first derivative of the cubic bezier function at time=t
function computeCubicFirstDerivativeRoots(a, b, c, d) {
var result = [-1, -1],
tl = -a + 2 * b - c,
tr = -Math.sqrt(-a * (c - d) + b * b - b * (c + d) + c * c),
dn = -a + 3 * b - 3 * c + d;
if (dn !== 0) {
result[0] = (tl + tr) / dn;
result[1] = (tl - tr) / dn;
}
return result;
}
/**
* Compute Quadratic Bézier bounding box.
*
* @see https://pomax.github.io/bezierinfo/
*
* @param {Float} xa
* @param {Float} ya
* @param {Float} xb
* @param {Float} yb
* @param {Float} xc
* @param {Float} yc
*
* @return {Object}
*/
exports.computeQuadraticBoundingBox = function (xa, ya, xb, yb, xc, yc) {
var minx = Number.POSITIVE_INFINITY,
miny = Number.POSITIVE_INFINITY,
maxx = Number.NEGATIVE_INFINITY,
maxy = Number.NEGATIVE_INFINITY,
t,
x,
y;
// X
if (xa < minx) {
minx = xa;
}
if (xa > maxx) {
maxx = xa;
}
if (xc < minx) {
minx = xc;
}
if (xc > maxx) {
maxx = xc;
}
t = computeQuadraticFirstDerivativeRoot(xa, xb, xc);
if (t >= 0 && t <= 1) {
x = computeQuadraticBaseValue(t, xa, xb, xc);
// y = computeQuadraticBaseValue(t, ya, yb, yc);
if (x < minx) {
minx = x;
// smooth quadratic Bézier curveto (x y)
if (command === 't') {
args[0] += cursor[0];
args[1] += cursor[1];
command = 'T';
}
if (x > maxx) {
maxx = x;
if (command === 'T') {
cursor[0] = args[0];
cursor[1] = args[1];
}
}
// Y
if (ya < miny) {
miny = ya;
}
if (ya > maxy) {
maxy = ya;
}
if (yc < miny) {
miny = yc;
}
if (yc > maxy) {
maxy = yc;
}
t = computeQuadraticFirstDerivativeRoot(ya, yb, yc);
if (t >= 0 && t <= 1) {
// x = computeQuadraticBaseValue(t, xa, xb, xc);
y = computeQuadraticBaseValue(t, ya, yb, yc);
if (y < miny) {
miny = y;
// 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 (y > maxy) {
maxy = y;
if (command === 'A') {
cursor[0] = args[5];
cursor[1] = args[6];
}
}
return {
minx: minx,
miny: miny,
maxx: maxx,
maxy: maxy,
};
};
// closepath
if (command === 'z' || command === 'Z') {
cursor[0] = start[0];
cursor[1] = start[1];
command = 'z';
}
// compute the value for the quadratic bezier function at time=t
function computeQuadraticBaseValue(t, a, b, c) {
var mt = 1 - t;
return mt * mt * a + 2 * mt * t * b + t * t * c;
}
// compute the value for the first derivative of the quadratic bezier function at time=t
function computeQuadraticFirstDerivativeRoot(a, b, c) {
var t = -1,
denominator = a - 2 * b + c;
if (denominator !== 0) {
t = (a - b) / denominator;
newData.push({ command, args });
}
return newData;
};
return t;
}
/**
* @typedef {{ floatPrecision?: number, noSpaceAfterFlags?: boolean }} Js2PathParams
*/

@@ -323,7 +184,6 @@ /**

*
* @param {Array} path input path data
* @param {Object} params plugin params
* @return {String} output path string
* @type {(path: XastElement, data: Array<PathDataItem>, params: Js2PathParams) => void}
*/
exports.js2path = function (path, data, params) {
// @ts-ignore legacy
path.pathJS = data;

@@ -336,3 +196,3 @@

pathData.length !== 0 &&
(item.instruction === 'M' || item.instruction === 'm')
(item.command === 'M' || item.command === 'm')
) {

@@ -345,4 +205,4 @@ const last = pathData[pathData.length - 1];

pathData.push({
command: item.instruction,
args: item.data || [],
command: item.command,
args: item.args,
});

@@ -358,2 +218,5 @@ }

/**
* @type {(dest: Array<number>, source: Array<number>) => Array<number>}
*/
function set(dest, source) {

@@ -370,10 +233,8 @@ dest[0] = source[source.length - 2];

*
* @param {Array} path1 JS path representation
* @param {Array} path2 JS path representation
* @return {Boolean}
* @type {(path1: Array<PathDataItem>, path2: Array<PathDataItem>) => boolean}
*/
exports.intersects = function (path1, path2) {
// Collect points of every subpath.
var points1 = relative2absolute(path1).reduce(gatherPoints, []),
points2 = relative2absolute(path2).reduce(gatherPoints, []);
const points1 = gatherPoints(convertRelativeToAbsolute(path1));
const points2 = gatherPoints(convertRelativeToAbsolute(path2));

@@ -386,9 +247,9 @@ // Axis-aligned bounding box check.

points2.maxY <= points1.minY ||
points1.every(function (set1) {
return points2.every(function (set2) {
points1.list.every((set1) => {
return points2.list.every((set2) => {
return (
set1[set1.maxX][0] <= set2[set2.minX][0] ||
set2[set2.maxX][0] <= set1[set1.minX][0] ||
set1[set1.maxY][1] <= set2[set2.minY][1] ||
set2[set2.maxY][1] <= set1[set1.minY][1]
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]
);

@@ -401,11 +262,11 @@ });

// Get a convex hull from points of each subpath. Has the most complexity O(n·log n).
var hullNest1 = points1.map(convexHull),
hullNest2 = points2.map(convexHull);
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.length < 3) return false;
if (hull1.list.length < 3) return false;
return hullNest2.some(function (hull2) {
if (hull2.length < 3) return false;
if (hull2.list.length < 3) return false;

@@ -435,2 +296,5 @@ var simplex = [getSupport(hull1, hull2, [1, 0])], // create the initial simplex

/**
* @type {(a: Point, b: Point, direction: Array<number>) => Array<number>}
*/
function getSupport(a, b, direction) {

@@ -443,2 +307,5 @@ return sub(supportPoint(a, direction), supportPoint(b, minus(direction)));

// 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) {

@@ -455,10 +322,13 @@ var index =

value;
while ((value = dot(polygon[index], direction)) > max) {
while ((value = dot(polygon.list[index], direction)) > max) {
max = value;
index = ++index % polygon.length;
index = ++index % polygon.list.length;
}
return polygon[(index || polygon.length) - 1];
return polygon.list[(index || polygon.list.length) - 1];
}
};
/**
* @type {(simplex: Array<Array<number>>, direction: Array<number>) => boolean}
*/
function processSimplex(simplex, direction) {

@@ -518,2 +388,5 @@ // we only need to handle to 1-simplex and 2-simplex

/**
* @type {(v: Array<number>) => Array<number>}
*/
function minus(v) {

@@ -523,2 +396,5 @@ return [-v[0], -v[1]];

/**
* @type {(v1: Array<number>, v2: Array<number>) => Array<number>}
*/
function sub(v1, v2) {

@@ -528,2 +404,5 @@ return [v1[0] - v2[0], v1[1] - v2[1]];

/**
* @type {(v1: Array<number>, v2: Array<number>) => number}
*/
function dot(v1, v2) {

@@ -533,2 +412,5 @@ return v1[0] * v2[0] + v1[1] * v2[1];

/**
* @type {(v1: Array<number>, v2: Array<number>) => Array<number>}
*/
function orth(v, from) {

@@ -539,109 +421,201 @@ var o = [-v[1], v[0]];

function gatherPoints(points, item, index, path) {
var subPath = points.length && points[points.length - 1],
prev = index && path[index - 1],
basePoint = subPath.length && subPath[subPath.length - 1],
data = item.data,
ctrlPoint = basePoint;
/**
* @typedef {{
* list: Array<Array<number>>,
* minX: number,
* minY: number,
* maxX: number,
* maxY: number
* }} Point
*/
switch (item.instruction) {
case 'M':
points.push((subPath = []));
break;
case 'H':
addPoint(subPath, [data[0], basePoint[1]]);
break;
case 'V':
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 (prev.instruction == 'Q' || prev.instruction == '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':
// 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 (prev.instruction == 'C' || prev.instruction == 'S') {
/**
* @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, [
basePoint[0] + 0.5 * prevCtrlPoint[0],
basePoint[1] + 0.5 * prevCtrlPoint[1],
0.5 * (data[0] + data[2]),
0.5 * (data[1] + data[3]),
]);
ctrlPoint = [
basePoint[0] + prevCtrlPoint[0],
basePoint[1] + prevCtrlPoint[1],
];
}
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':
// Convert the arc to bezier curves and use the same approximation
var curves = a2c.apply(0, basePoint.concat(data));
for (var cData; (cData = curves.splice(0, 6).map(toAbsolute)).length; ) {
addPoint(subPath, [
0.5 * (basePoint[0] + cData[0]),
0.5 * (basePoint[1] + cData[1]),
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 * (cData[0] + cData[2]),
0.5 * (cData[1] + cData[3]),
0.5 * (data[0] + data[2]),
0.5 * (data[1] + data[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 && data.length >= 2) addPoint(subPath, data.slice(-2));
return points;
prevCtrlPoint = [data[2] - data[0], data[3] - data[1]];
break;
function toAbsolute(n, i) {
return n + basePoint[i % 2];
}
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;
// Writes data about the extreme points on each axle
function addPoint(path, point) {
if (!path.length || point[1] > path[path.maxY][1]) {
path.maxY = path.length;
points.maxY = points.length ? Math.max(point[1], points.maxY) : point[1];
) {
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;
}
if (!path.length || point[0] > path[path.maxX][0]) {
path.maxX = path.length;
points.maxX = points.length ? Math.max(point[0], points.maxX) : point[0];
}
if (!path.length || point[1] < path[path.minY][1]) {
path.minY = path.length;
points.minY = points.length ? Math.min(point[1], points.minY) : point[1];
}
if (!path.length || point[0] < path[path.minX][0]) {
path.minX = path.length;
points.minX = points.length ? Math.min(point[0], points.minX) : point[0];
}
path.push(point);
// Save final command coordinates
if (data.length >= 2) addPoint(subPath, data.slice(-2));
}
return points;
}

@@ -653,6 +627,6 @@

*
* @param points An array of [X, Y] coordinates
* @type {(points: Point) => Point}
*/
function convexHull(points) {
points.sort(function (a, b) {
points.list.sort(function (a, b) {
return a[0] == b[0] ? a[1] - b[1] : a[0] - b[0];

@@ -664,31 +638,33 @@ });

bottom = 0;
for (let i = 0; i < points.length; i++) {
for (let i = 0; i < points.list.length; i++) {
while (
lower.length >= 2 &&
cross(lower[lower.length - 2], lower[lower.length - 1], points[i]) <= 0
cross(lower[lower.length - 2], lower[lower.length - 1], points.list[i]) <=
0
) {
lower.pop();
}
if (points[i][1] < points[minY][1]) {
if (points.list[i][1] < points.list[minY][1]) {
minY = i;
bottom = lower.length;
}
lower.push(points[i]);
lower.push(points.list[i]);
}
var upper = [],
maxY = points.length - 1,
maxY = points.list.length - 1,
top = 0;
for (let i = points.length; i--; ) {
for (let i = points.list.length; i--; ) {
while (
upper.length >= 2 &&
cross(upper[upper.length - 2], upper[upper.length - 1], points[i]) <= 0
cross(upper[upper.length - 2], upper[upper.length - 1], points.list[i]) <=
0
) {
upper.pop();
}
if (points[i][1] > points[maxY][1]) {
if (points.list[i][1] > points.list[maxY][1]) {
maxY = i;
top = upper.length;
}
upper.push(points[i]);
upper.push(points.list[i]);
}

@@ -700,8 +676,14 @@

var hull = lower.concat(upper);
const hullList = lower.concat(upper);
hull.minX = 0; // by sorting
hull.maxX = lower.length;
hull.minY = bottom;
hull.maxY = (lower.length + top) % hull.length;
/**
* @type {Point}
*/
const hull = {
list: hullList,
minX: 0, // by sorting
maxX: lower.length,
minY: bottom,
maxY: (lower.length + top) % hullList.length,
};

@@ -711,2 +693,5 @@ return hull;

/**
* @type {(o: Array<number>, a: Array<number>, b: Array<number>) => number}
*/
function cross(o, a, b) {

@@ -716,7 +701,20 @@ 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/
/**
* 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>}
*/
function a2c(
const a2c = (
x1,

@@ -732,14 +730,23 @@ y1,

recursive
) {
) => {
// for more information of where this Math came from visit:
// https://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
var _120 = (Math.PI * 120) / 180,
rad = (Math.PI / 180) * (+angle || 0),
res = [],
rotateX = function (x, y, rad) {
return x * Math.cos(rad) - y * Math.sin(rad);
},
rotateY = function (x, y, rad) {
return x * Math.sin(rad) + y * Math.cos(rad);
};
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) {

@@ -758,16 +765,15 @@ x1 = rotateX(x1, y1, -rad);

}
var rx2 = rx * rx,
ry2 = ry * ry,
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)
)
),
cx = (k * rx * y) / ry + (x1 + x2) / 2,
cy = (k * -ry * x) / rx + (y1 + y2) / 2,
f1 = Math.asin(((y1 - cy) / ry).toFixed(9)),
f2 = Math.asin(((y2 - cy) / ry).toFixed(9));
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)));

@@ -834,2 +840,2 @@ f1 = x1 < cx ? Math.PI - f1 : f1;

}
}
};
'use strict';
var regTransformTypes = /matrix|translate|scale|rotate|skewX|skewY/,
regTransformSplit = /\s*(matrix|translate|scale|rotate|skewX|skewY)\s*\(\s*(.+?)\s*\)[\s,]*/,
regNumericValues = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;
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.
*
* @param {String} transformString input string
* @param {Object} params plugin params
* @return {Array} output array
* @type {(transformString: string) => Array<TransformItem>}
*/
exports.transform2js = function (transformString) {
exports.transform2js = (transformString) => {
// JS representation of the transform data
var transforms = [],
// current transform context
current;
/**
* @type {Array<TransformItem>}
*/
const transforms = [];
// current transform context
/**
* @type {null | TransformItem}
*/
let current = null;
// split value into ['', 'translate', '10 50', '', 'scale', '2', '', 'rotate', '-45', '']
transformString.split(regTransformSplit).forEach(function (item) {
for (const item of transformString.split(regTransformSplit)) {
var num;
if (item) {

@@ -28,3 +35,4 @@ // if item is a translate function

// then collect it and change current context
transforms.push((current = { name: item }));
current = { name: item, data: [] };
transforms.push(current);
// else if item is data

@@ -36,11 +44,11 @@ } else {

num = Number(num);
if (current.data) current.data.push(num);
else current.data = [num];
if (current != null) {
current.data.push(num);
}
}
}
}
});
}
// return empty array if broken transform (no data)
return current && current.data ? transforms : [];
return current == null || current.data.length == 0 ? [] : transforms;
};

@@ -51,8 +59,7 @@

*
* @param {Array} input transforms array
* @return {Array} output matrix array
* @type {(transforms: Array<TransformItem>) => TransformItem}
*/
exports.transformsMultiply = function (transforms) {
exports.transformsMultiply = (transforms) => {
// convert transforms objects to the matrices
transforms = transforms.map(function (transform) {
const matrixData = transforms.map((transform) => {
if (transform.name === 'matrix') {

@@ -63,70 +70,109 @@ return transform.data;

});
// multiply all matrices into one
transforms = {
const matrixTransform = {
name: 'matrix',
data:
transforms.length > 0 ? transforms.reduce(multiplyTransformMatrices) : [],
matrixData.length > 0 ? matrixData.reduce(multiplyTransformMatrices) : [],
};
return transforms;
return matrixTransform;
};
/**
* Do math like a schoolgirl.
*
* @type {Object}
* math utilities in radians.
*/
var mth = (exports.mth = {
rad: function (deg) {
const mth = {
/**
* @type {(deg: number) => number}
*/
rad: (deg) => {
return (deg * Math.PI) / 180;
},
deg: function (rad) {
/**
* @type {(rad: number) => number}
*/
deg: (rad) => {
return (rad * 180) / Math.PI;
},
cos: function (deg) {
return Math.cos(this.rad(deg));
/**
* @type {(deg: number) => number}
*/
cos: (deg) => {
return Math.cos(mth.rad(deg));
},
acos: function (val, floatPrecision) {
return +this.deg(Math.acos(val)).toFixed(floatPrecision);
/**
* @type {(val: number, floatPrecision: number) => number}
*/
acos: (val, floatPrecision) => {
return Number(mth.deg(Math.acos(val)).toFixed(floatPrecision));
},
sin: function (deg) {
return Math.sin(this.rad(deg));
/**
* @type {(deg: number) => number}
*/
sin: (deg) => {
return Math.sin(mth.rad(deg));
},
asin: function (val, floatPrecision) {
return +this.deg(Math.asin(val)).toFixed(floatPrecision);
/**
* @type {(val: number, floatPrecision: number) => number}
*/
asin: (val, floatPrecision) => {
return Number(mth.deg(Math.asin(val)).toFixed(floatPrecision));
},
tan: function (deg) {
return Math.tan(this.rad(deg));
/**
* @type {(deg: number) => number}
*/
tan: (deg) => {
return Math.tan(mth.rad(deg));
},
atan: function (val, floatPrecision) {
return +this.deg(Math.atan(val)).toFixed(floatPrecision);
/**
* @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
*
* @param {Object} data matrix transform object
* @return {Object|Array} transforms array or original transform object
* @type {(transform: TransformItem, params: TransformParams) => Array<TransformItem>}
*/
exports.matrixToTransform = function (transform, params) {
var floatPrecision = params.floatPrecision,
data = transform.data,
transforms = [],
sx = +Math.hypot(data[0], data[1]).toFixed(params.transformPrecision),
sy = +((data[0] * data[3] - data[1] * data[2]) / sx).toFixed(
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
),
colsSum = data[0] * data[2] + data[1] * data[3],
rowsSum = data[0] * data[1] + data[2] * data[3],
scaleBefore = rowsSum != 0 || sx == sy;
)
);
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;

@@ -184,6 +230,7 @@ // [..., ..., ..., ..., tx, ty] → translate(tx, ty)

sin = data[1] / (scaleBefore ? sx : sy),
x = data[4] * (scaleBefore || sy),
y = data[5] * (scaleBefore || sx),
x = data[4] * (scaleBefore ? 1 : sy),
y = data[5] * (scaleBefore ? 1 : sx),
denom =
(Math.pow(1 - cos, 2) + Math.pow(sin, 2)) * (scaleBefore || sx * sy);
(Math.pow(1 - cos, 2) + Math.pow(sin, 2)) *
(scaleBefore ? 1 : sx * sy);
rotate.push(((1 - cos) * x - sin * y) / denom);

@@ -195,3 +242,3 @@ rotate.push(((1 - cos) * y + sin * x) / denom);

} else if (data[1] || data[2]) {
return transform;
return [transform];
}

@@ -211,18 +258,15 @@

*
* @param {Object} transform transform object
* @return {Array} matrix data
* @type {(transform: TransformItem) => Array<number> }
*/
function transformToMatrix(transform) {
if (transform.name === 'matrix') return transform.data;
var matrix;
const transformToMatrix = (transform) => {
if (transform.name === 'matrix') {
return transform.data;
}
switch (transform.name) {
case 'translate':
// [1, 0, 0, 1, tx, ty]
matrix = [1, 0, 0, 1, transform.data[0], transform.data[1] || 0];
break;
return [1, 0, 0, 1, transform.data[0], transform.data[1] || 0];
case 'scale':
// [sx, 0, 0, sy, 0, 0]
matrix = [
return [
transform.data[0],

@@ -235,3 +279,2 @@ 0,

];
break;
case 'rotate':

@@ -243,4 +286,3 @@ // [cos(a), sin(a), -sin(a), cos(a), x, y]

cy = transform.data[2] || 0;
matrix = [
return [
cos,

@@ -253,16 +295,13 @@ sin,

];
break;
case 'skewX':
// [1, 0, tan(a), 1, 0, 0]
matrix = [1, 0, mth.tan(transform.data[0]), 1, 0, 0];
break;
return [1, 0, mth.tan(transform.data[0]), 1, 0, 0];
case 'skewY':
// [1, tan(a), 0, 1, 0, 0]
matrix = [1, mth.tan(transform.data[0]), 0, 1, 0, 0];
break;
return [1, mth.tan(transform.data[0]), 0, 1, 0, 0];
default:
throw Error(`Unknown transform ${transform.name}`);
}
};
return matrix;
}
/**

@@ -274,31 +313,34 @@ * Applies transformation to an arc. To do so, we represent ellipse as a matrix, multiply it

*
* @param {Array} cursor [x, y]
* @param {Array} arc [a, b, rotation in deg]
* @param {Array} transform transformation matrix
* @return {Array} arc transformed input arc
* @type {(
* cursor: [x: number, y: number],
* arc: Array<number>,
* transform: Array<number>
* ) => Array<number>}
*/
exports.transformArc = function (cursor, arc, transform) {
exports.transformArc = (cursor, arc, transform) => {
const x = arc[5] - cursor[0];
const y = arc[6] - cursor[1];
var a = arc[0],
b = arc[1],
rot = (arc[2] * Math.PI) / 180,
cos = Math.cos(rot),
sin = Math.sin(rot),
h =
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;
if (h > 1) {
h = Math.sqrt(h);
a *= h;
b *= h;
}
}
var ellipse = [a * cos, a * sin, -b * sin, b * cos, 0, 0],
m = multiplyTransformMatrices(transform, ellipse),
// Decompose the new ellipse matrix
lastCol = m[2] * m[2] + m[3] * m[3],
squareSum = m[0] * m[0] + m[1] * m[1] + lastCol,
root =
Math.hypot(m[0] - m[3], m[1] + m[2]) *
Math.hypot(m[0] + m[3], m[1] - m[2]);
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]);

@@ -310,9 +352,9 @@ if (!root) {

} else {
var majorAxisSqr = (squareSum + root) / 2,
minorAxisSqr = (squareSum - root) / 2,
major = Math.abs(majorAxisSqr - lastCol) > 1e-6,
sub = (major ? majorAxisSqr : minorAxisSqr) - lastCol,
rowsSum = m[0] * m[2] + m[1] * m[3],
term1 = m[0] * sub + m[2] * rowsSum,
term2 = m[1] * sub + m[3] * rowsSum;
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);

@@ -338,7 +380,5 @@ arc[1] = Math.sqrt(minorAxisSqr);

*
* @param {Array} a matrix A data
* @param {Array} b matrix B data
* @return {Array} result
* @type {(a: Array<number>, b: Array<number>) => Array<number>}
*/
function multiplyTransformMatrices(a, b) {
const multiplyTransformMatrices = (a, b) => {
return [

@@ -352,2 +392,2 @@ a[0] * b[0] + a[2] * b[1],

];
}
};
'use strict';
const { closestByName } = require('../lib/xast.js');
exports.name = 'addAttributesToSVGElement';
exports.type = 'perItem';
exports.type = 'visitor';
exports.active = false;
exports.description = 'adds attributes to an outer <svg> element';

@@ -56,31 +51,38 @@

* @author April Arcus
*
* @type {import('../lib/types').Plugin<{
* attribute?: string | Record<string, null | string>,
* attributes?: Array<string | Record<string, null | string>>
* }>}
*/
exports.fn = (node, params) => {
if (
node.type === 'element' &&
node.name === 'svg' &&
closestByName(node.parentNode, 'svg') == null
) {
if (!params || !(Array.isArray(params.attributes) || params.attribute)) {
console.error(ENOCLS);
return;
}
const attributes = params.attributes || [params.attribute];
for (const attribute of attributes) {
if (typeof attribute === 'string') {
if (node.attributes[attribute] == null) {
node.attributes[attribute] = undefined;
}
}
if (typeof attribute === 'object') {
for (const key of Object.keys(attribute)) {
if (node.attributes[key] == null) {
node.attributes[key] = attribute[key];
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];
}
}
}
}
}
}
}
}
},
},
};
};
'use strict';
exports.name = 'addClassesToSVGElement';
exports.type = 'full';
exports.type = 'visitor';
exports.active = false;
exports.description = 'adds classnames to an outer <svg> element';

@@ -15,9 +12,19 @@

plugins:
- addClassesToSVGElement:
className: "mySvg"
plugins: [
{
name: "addClassesToSVGElement",
params: {
className: "mySvg"
}
}
]
plugins:
- addClassesToSVGElement:
classNames: ["mySvg", "size-big"]
plugins: [
{
name: "addClassesToSVGElement",
params: {
classNames: ["mySvg", "size-big"]
}
}
]
`;

@@ -28,32 +35,55 @@

*
* plugins:
* - addClassesToSVGElement:
* className: 'mySvg'
* plugins: [
* {
* name: "addClassesToSVGElement",
* params: {
* className: "mySvg"
* }
* }
* ]
*
* plugins:
* - addClassesToSVGElement:
* classNames: ['mySvg', 'size-big']
* plugins: [
* {
* name: "addClassesToSVGElement",
* params: {
* classNames: ["mySvg", "size-big"]
* }
* }
* ]
*
* @author April Arcus
*
* @type {import('../lib/types').Plugin<{
* className?: string,
* classNames?: Array<string>
* }>}
*/
exports.fn = function (data, params) {
exports.fn = (root, params) => {
if (
!params ||
!(
(Array.isArray(params.classNames) && params.classNames.some(String)) ||
params.className
)
!(Array.isArray(params.classNames) && params.classNames.some(String)) &&
!params.className
) {
console.error(ENOCLS);
return data;
return null;
}
var classNames = params.classNames || [params.className],
svg = data.children[0];
if (svg.isElem('svg')) {
svg.class.add.apply(svg.class, classNames);
}
return data;
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(' ');
}
},
},
};
};

@@ -17,2 +17,8 @@ 'use strict';

* @author Kir Belevich
*
* @type {import('../lib/types').Plugin<{
* newlines?: boolean,
* trim?: boolean,
* spaces?: boolean
* }>}
*/

@@ -19,0 +25,0 @@ exports.fn = (root, params) => {

'use strict';
const { traverse } = require('../lib/xast.js');
const { visit } = require('../lib/xast.js');
exports.type = 'visitor';
exports.name = 'cleanupEnableBackground';
exports.type = 'full';
exports.active = true;
exports.description =

@@ -24,53 +21,56 @@ 'remove or cleanup enable-background attribute when possible';

*
* @param {Object} root current iteration item
* @return {Boolean} if false, item will be filtered out
* @author Kir Belevich
*
* @author Kir Belevich
* @type {import('../lib/types').Plugin<void>}
*/
exports.fn = function (root) {
const regEnableBackground = /^new\s0\s0\s([-+]?\d*\.?\d+([eE][-+]?\d+)?)\s([-+]?\d*\.?\d+([eE][-+]?\d+)?)$/;
exports.fn = (root) => {
const regEnableBackground =
/^new\s0\s0\s([-+]?\d*\.?\d+([eE][-+]?\d+)?)\s([-+]?\d*\.?\d+([eE][-+]?\d+)?)$/;
let hasFilter = false;
const elems = ['svg', 'mask', 'pattern'];
visit(root, {
element: {
enter: (node) => {
if (node.name === 'filter') {
hasFilter = true;
}
},
},
});
traverse(root, (node) => {
if (node.type === 'element') {
if (
elems.includes(node.name) &&
node.attributes['enable-background'] != null &&
node.attributes.width != null &&
node.attributes.height != null
) {
const match = node.attributes['enable-background'].match(
regEnableBackground
);
if (match) {
return {
element: {
enter: (node) => {
if (node.attributes['enable-background'] == null) {
return;
}
if (hasFilter) {
if (
node.attributes.width === match[1] &&
node.attributes.height === match[3]
(node.name === 'svg' ||
node.name === 'mask' ||
node.name === 'pattern') &&
node.attributes.width != null &&
node.attributes.height != null
) {
if (node.name === 'svg') {
delete node.attributes['enable-background'];
} else {
node.attributes['enable-background'] = 'new';
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'];
}
}
if (node.name === 'filter') {
hasFilter = true;
}
}
});
if (hasFilter === false) {
traverse(root, (node) => {
if (node.type === 'element') {
//we don't need 'enable-background' if we have no filters
delete node.attributes['enable-background'];
}
});
}
return root;
},
},
};
};

@@ -6,17 +6,8 @@ 'use strict';

exports.name = 'cleanupListOfValues';
exports.type = 'perItem';
exports.type = 'visitor';
exports.active = false;
exports.description = 'rounds list of values to the fixed precision';
exports.params = {
floatPrecision: 3,
leadingZero: true,
defaultPx: true,
convertToPx: true,
};
const regNumericValues = /^([-+]?\d*\.?\d+([eE][-+]?\d+)?)(px|pt|pc|mm|cm|m|in|ft|em|ex|%)?$/;
const regNumericValues =
/^([-+]?\d*\.?\d+([eE][-+]?\d+)?)(px|pt|pc|mm|cm|m|in|ft|em|ex|%)?$/;
const regSeparator = /\s+,?\s*|,\s*/;

@@ -30,2 +21,3 @@ const absoluteLengths = {

pc: 16,
px: 1,
};

@@ -41,3 +33,2 @@

*
*
* <polygon points="208.250977 77.1308594 223.069336 ... "/>

@@ -47,89 +38,68 @@ * ⬇

*
* @author kiyopikko
*
* @param {Object} item current iteration item
* @param {Object} params plugin params
* @return {Boolean} if false, item will be filtered out
*
* @author kiyopikko
* @type {import('../lib/types').Plugin<{
* floatPrecision?: number,
* leadingZero?: boolean,
* defaultPx?: boolean,
* convertToPx?: boolean
* }>}
*/
exports.fn = function (item, params) {
if (item.type !== 'element') {
return;
}
exports.fn = (_root, params) => {
const {
floatPrecision = 3,
leadingZero = true,
defaultPx = true,
convertToPx = true,
} = params;
if (item.attributes.points != null) {
item.attributes.points = roundValues(item.attributes.points);
}
/**
* @type {(lists: string) => string}
*/
const roundValues = (lists) => {
const roundedList = [];
if (item.attributes['enable-background'] != null) {
item.attributes['enable-background'] = roundValues(
item.attributes['enable-background']
);
}
for (const elem of lists.split(regSeparator)) {
const match = elem.match(regNumericValues);
const matchNew = elem.match(/new/);
if (item.attributes.viewBox != null) {
item.attributes.viewBox = roundValues(item.attributes.viewBox);
}
if (item.attributes['stroke-dasharray'] != null) {
item.attributes['stroke-dasharray'] = roundValues(
item.attributes['stroke-dasharray']
);
}
if (item.attributes.dx != null) {
item.attributes.dx = roundValues(item.attributes.dx);
}
if (item.attributes.dy != null) {
item.attributes.dy = roundValues(item.attributes.dy);
}
if (item.attributes.x != null) {
item.attributes.x = roundValues(item.attributes.x);
}
if (item.attributes.y != null) {
item.attributes.y = roundValues(item.attributes.y);
}
function roundValues(lists) {
var num,
units,
match,
matchNew,
listsArr = lists.split(regSeparator),
roundedList = [];
for (const elem of listsArr) {
match = elem.match(regNumericValues);
matchNew = elem.match(/new/);
// if attribute value matches regNumericValues
if (match) {
// round it to the fixed precision
(num = +(+match[1]).toFixed(params.floatPrecision)),
(units = match[3] || '');
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 (params.convertToPx && units && units in absoluteLengths) {
var pxNum = +(absoluteLengths[units] * match[1]).toFixed(
params.floatPrecision
if (convertToPx && units && units in absoluteLengths) {
const pxNum = Number(
(absoluteLengths[units] * Number(match[1])).toFixed(floatPrecision)
);
if (String(pxNum).length < match[0].length)
(num = pxNum), (units = 'px');
if (pxNum.toString().length < match[0].length) {
num = pxNum;
units = 'px';
}
}
// and remove leading zero
if (params.leadingZero) {
num = removeLeadingZero(num);
let str;
if (leadingZero) {
str = removeLeadingZero(num);
} else {
str = num.toString();
}
// remove default 'px' units
if (params.defaultPx && units === 'px') {
if (defaultPx && units === 'px') {
units = '';
}
roundedList.push(num + units);
roundedList.push(str + units);
}

@@ -145,3 +115,45 @@ // if attribute value is "new"(only enable-background).

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);
}
},
},
};
};
'use strict';
const { removeLeadingZero } = require('../lib/svgo/tools');
exports.name = 'cleanupNumericValues';
exports.type = 'perItem';
exports.type = 'visitor';
exports.active = true;
exports.description =
'rounds numeric values to the fixed precision, removes default ‘px’ units';
exports.params = {
floatPrecision: 3,
leadingZero: true,
defaultPx: true,
convertToPx: true,
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,
};
var regNumericValues = /^([-+]?\d*\.?\d+([eE][-+]?\d+)?)(px|pt|pc|mm|cm|m|in|ft|em|ex|%)?$/,
removeLeadingZero = require('../lib/svgo/tools').removeLeadingZero,
absoluteLengths = {
// relative to px
cm: 96 / 2.54,
mm: 96 / 25.4,
in: 96,
pt: 4 / 3,
pc: 16,
};
/**

@@ -34,62 +28,87 @@ * Round numeric values to the fixed precision,

*
* @param {Object} item current iteration item
* @param {Object} params plugin params
* @return {Boolean} if false, item will be filtered out
* @author Kir Belevich
*
* @author Kir Belevich
* @type {import('../lib/types').Plugin<{
* floatPrecision?: number,
* leadingZero?: boolean,
* defaultPx?: boolean,
* convertToPx?: boolean
* }>}
*/
exports.fn = function (item, params) {
if (item.type === 'element') {
var floatPrecision = params.floatPrecision;
exports.fn = (_root, params) => {
const {
floatPrecision = 3,
leadingZero = true,
defaultPx = true,
convertToPx = true,
} = params;
if (item.attributes.viewBox != null) {
var nums = item.attributes.viewBox.split(/\s,?\s*|,\s*/g);
item.attributes.viewBox = nums
.map(function (value) {
var num = +value;
return isNaN(num) ? value : +num.toFixed(floatPrecision);
})
.join(' ');
}
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(item.attributes)) {
// The `version` attribute is a text string and cannot be rounded
if (name === 'version') {
continue;
}
for (const [name, value] of Object.entries(node.attributes)) {
// The `version` attribute is a text string and cannot be rounded
if (name === 'version') {
continue;
}
var match = value.match(regNumericValues);
const match = value.match(regNumericValues);
// if attribute value matches regNumericValues
if (match) {
// round it to the fixed precision
var num = +(+match[1]).toFixed(floatPrecision),
units = match[3] || '';
// 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 (params.convertToPx && units && units in absoluteLengths) {
var pxNum = +(absoluteLengths[units] * match[1]).toFixed(
floatPrecision
);
// 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';
}
}
if (String(pxNum).length < match[0].length) {
num = pxNum;
units = 'px';
}
}
// and remove leading zero
let str;
if (leadingZero) {
str = removeLeadingZero(num);
} else {
str = num.toString();
}
// and remove leading zero
if (params.leadingZero) {
num = removeLeadingZero(num);
}
// remove default 'px' units
if (defaultPx && units === 'px') {
units = '';
}
// remove default 'px' units
if (params.defaultPx && units === 'px') {
units = '';
node.attributes[name] = str + units;
}
}
item.attributes[name] = num + units;
}
}
}
},
},
};
};
'use strict';
const collections = require('./_collections.js');
exports.type = 'visitor';
exports.name = 'convertColors';
exports.type = 'perItem';
exports.active = true;
exports.description = 'converts colors: rgb() to #rrggbb and #rrggbb to #rgb';
exports.params = {
currentColor: false,
names2hex: true,
rgb2hex: true,
shorthex: true,
shortname: true,
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();
};
var collections = require('./_collections'),
rNumber = '([+-]?(?:\\d*\\.\\d+|\\d+\\.?)%?)',
rComma = '\\s*,\\s*',
regRGB = new RegExp(
'^rgb\\(\\s*' + rNumber + rComma + rNumber + rComma + rNumber + '\\s*\\)$'
),
regHEX = /^#(([a-fA-F0-9])\2){3}$/,
none = /\bnone\b/i;
/**

@@ -48,84 +64,90 @@ * Convert different colors formats in element attributes to hex.

*
* @param {Object} item current iteration item
* @param {Object} params plugin params
* @return {Boolean} if false, item will be filtered out
* @author Kir Belevich
*
* @author Kir Belevich
* @type {import('../lib/types').Plugin<{
* currentColor?: boolean | string | RegExp,
* names2hex?: boolean,
* rgb2hex?: boolean,
* shorthex?: boolean,
* shortname?: boolean,
* }>}
*/
exports.fn = function (item, params) {
if (item.type === 'element') {
for (const [name, value] of Object.entries(item.attributes)) {
if (collections.colorsProps.includes(name)) {
let val = value;
let match;
exports.fn = (_root, params) => {
const {
currentColor = false,
names2hex = true,
rgb2hex = true,
shorthex = true,
shortname = true,
} = params;
// Convert colors to currentColor
if (params.currentColor) {
if (typeof params.currentColor === 'string') {
match = val === params.currentColor;
} else if (params.currentColor.exec) {
match = params.currentColor.exec(val);
} else {
match = !val.match(none);
}
if (match) {
val = 'currentColor';
}
}
return {
element: {
enter: (node) => {
for (const [name, value] of Object.entries(node.attributes)) {
if (collections.colorsProps.includes(name)) {
let val = value;
// Convert color name keyword to long hex
if (params.names2hex && val.toLowerCase() in collections.colorsNames) {
val = collections.colorsNames[val.toLowerCase()];
}
// 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 rgb() to long hex
if (params.rgb2hex && (match = val.match(regRGB))) {
match = match.slice(1, 4).map(function (m) {
if (m.indexOf('%') > -1) m = Math.round(parseFloat(m) * 2.55);
// convert color name keyword to long hex
if (names2hex) {
const colorName = val.toLowerCase();
if (collections.colorsNames[colorName] != null) {
val = collections.colorsNames[colorName];
}
}
return Math.max(0, Math.min(m, 255));
});
// 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);
}
}
val = rgb2hex(match);
}
// 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 long hex to short hex
if (params.shorthex && (match = val.match(regHEX))) {
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];
}
}
// Convert hex to short name
if (params.shortname) {
var lowerVal = val.toLowerCase();
if (lowerVal in collections.colorsShortNames) {
val = collections.colorsShortNames[lowerVal];
node.attributes[name] = val;
}
}
item.attributes[name] = val;
}
}
}
},
},
};
};
/**
* Convert [r, g, b] to #rrggbb.
*
* @see https://gist.github.com/983535
*
* @example
* rgb2hex([255, 255, 255]) // '#ffffff'
*
* @param {Array} rgb [r, g, b]
* @return {String} #rrggbb
*
* @author Jed Schmidt
*/
function rgb2hex(rgb) {
return (
'#' +
('00000' + ((rgb[0] << 16) | (rgb[1] << 8) | rgb[2]).toString(16))
.slice(-6)
.toUpperCase()
);
}

@@ -14,2 +14,4 @@ 'use strict';

* @author Taylor Hunt
*
* @type {import('../lib/types').Plugin<void>}
*/

@@ -21,4 +23,4 @@ exports.fn = () => {

if (node.name === 'ellipse') {
const rx = node.attributes.rx || 0;
const ry = node.attributes.ry || 0;
const rx = node.attributes.rx || '0';
const ry = node.attributes.ry || '0';
if (

@@ -25,0 +27,0 @@ rx === ry ||

@@ -128,3 +128,3 @@ 'use strict';

const pathItem = pathData[i];
let { instruction: command, data: args } = pathItem;
let { command, args } = pathItem;

@@ -275,4 +275,4 @@ // moveto (x y)

pathItem.instruction = command;
pathItem.data = args;
pathItem.command = command;
pathItem.args = args;
// store absolute coordinates for later use

@@ -302,15 +302,15 @@ // base should preserve reference from other element

path = path.filter(function (item, index, path) {
var instruction = item.instruction,
data = item.data,
next = path[index + 1];
let command = item.command;
let data = item.args;
let next = path[index + 1];
if (data) {
if (command !== 'Z' && command !== 'z') {
var sdata = data,
circle;
if (instruction === 's') {
if (command === 's') {
sdata = [0, 0].concat(data);
if ('cs'.indexOf(prev.instruction) > -1) {
var pdata = prev.data,
if (command === 'c' || command === 's') {
var pdata = prev.args,
n = pdata.length;

@@ -327,3 +327,3 @@

params.makeArcs &&
(instruction == 'c' || instruction == 's') &&
(command == 'c' || command == 's') &&
isConvex(sdata) &&

@@ -336,4 +336,4 @@ (circle = findCircle(sdata))

arc = {
instruction: 'a',
data: [r, r, 0, 0, sweep, sdata[4], sdata[5]],
command: 'a',
args: [r, r, 0, 0, sweep, sdata[4], sdata[5]],
coords: item.coords.slice(),

@@ -355,14 +355,12 @@ base: item.base,

if (
(prev.instruction == 'c' &&
isConvex(prev.data) &&
isArcPrev(prev.data, circle)) ||
(prev.instruction == 'a' &&
prev.sdata &&
isArcPrev(prev.sdata, circle))
(prev.command == 'c' &&
isConvex(prev.args) &&
isArcPrev(prev.args, circle)) ||
(prev.command == 'a' && prev.sdata && isArcPrev(prev.sdata, circle))
) {
arcCurves.unshift(prev);
arc.base = prev.base;
arc.data[5] = arc.coords[0] - arc.base[0];
arc.data[6] = arc.coords[1] - arc.base[1];
var prevData = prev.instruction == 'a' ? prev.sdata : prev.data;
arc.args[5] = arc.coords[0] - arc.base[0];
arc.args[6] = arc.coords[1] - arc.base[1];
var prevData = prev.command == 'a' ? prev.sdata : prev.args;
var prevAngle = findArcAngle(prevData, {

@@ -376,3 +374,3 @@ center: [

angle += prevAngle;
if (angle > Math.PI) arc.data[3] = 1;
if (angle > Math.PI) arc.args[3] = 1;
hasPrev = 1;

@@ -384,13 +382,13 @@ }

var j = index;
(next = path[++j]) && ~'cs'.indexOf(next.instruction);
(next = path[++j]) && ~'cs'.indexOf(next.command);
) {
var nextData = next.data;
if (next.instruction == 's') {
var nextData = next.args;
if (next.command == 's') {
nextLonghand = makeLonghand(
{ instruction: 's', data: next.data.slice() },
path[j - 1].data
{ command: 's', args: next.args.slice() },
path[j - 1].args
);
nextData = nextLonghand.data;
nextLonghand.data = nextData.slice(0, 2);
nextData = nextLonghand.args;
nextLonghand.args = nextData.slice(0, 2);
suffix = stringify([nextLonghand]);

@@ -401,3 +399,3 @@ }

if (angle - 2 * Math.PI > 1e-3) break; // more than 360°
if (angle > Math.PI) arc.data[3] = 1;
if (angle > Math.PI) arc.args[3] = 1;
arcCurves.push(next);

@@ -407,15 +405,15 @@ if (2 * Math.PI - angle > 1e-3) {

arc.coords = next.coords;
arc.data[5] = arc.coords[0] - arc.base[0];
arc.data[6] = arc.coords[1] - arc.base[1];
arc.args[5] = arc.coords[0] - arc.base[0];
arc.args[6] = arc.coords[1] - arc.base[1];
} else {
// full circle, make a half-circle arc and add a second one
arc.data[5] = 2 * (relCircle.center[0] - nextData[4]);
arc.data[6] = 2 * (relCircle.center[1] - nextData[5]);
arc.args[5] = 2 * (relCircle.center[0] - nextData[4]);
arc.args[6] = 2 * (relCircle.center[1] - nextData[5]);
arc.coords = [
arc.base[0] + arc.data[5],
arc.base[1] + arc.data[6],
arc.base[0] + arc.args[5],
arc.base[1] + arc.args[6],
];
arc = {
instruction: 'a',
data: [
command: 'a',
args: [
r,

@@ -442,12 +440,12 @@ r,

if ((stringify(output) + suffix).length < stringify(arcCurves).length) {
if (path[j] && path[j].instruction == 's') {
makeLonghand(path[j], path[j - 1].data);
if (path[j] && path[j].command == 's') {
makeLonghand(path[j], path[j - 1].args);
}
if (hasPrev) {
var prevArc = output.shift();
roundData(prevArc.data);
relSubpoint[0] += prevArc.data[5] - prev.data[prev.data.length - 2];
relSubpoint[1] += prevArc.data[6] - prev.data[prev.data.length - 1];
prev.instruction = 'a';
prev.data = prevArc.data;
roundData(prevArc.args);
relSubpoint[0] += prevArc.args[5] - prev.args[prev.args.length - 2];
relSubpoint[1] += prevArc.args[6] - prev.args[prev.args.length - 1];
prev.command = 'a';
prev.args = prevArc.args;
item.base = prev.coords = prevArc.coords;

@@ -466,4 +464,4 @@ }

if (!arc) return false;
instruction = 'a';
data = arc.data;
command = 'a';
data = arc.args;
item.coords = arc.coords;

@@ -477,11 +475,18 @@ }

if (precision !== false) {
if ('mltqsc'.indexOf(instruction) > -1) {
if (
command === 'm' ||
command === 'l' ||
command === 't' ||
command === 'q' ||
command === 's' ||
command === 'c'
) {
for (var i = data.length; i--; ) {
data[i] += item.base[i % 2] - relSubpoint[i % 2];
}
} else if (instruction == 'h') {
} else if (command == 'h') {
data[0] += item.base[0] - relSubpoint[0];
} else if (instruction == 'v') {
} else if (command == 'v') {
data[0] += item.base[1] - relSubpoint[1];
} else if (instruction == 'a') {
} else if (command == 'a') {
data[5] += item.base[0] - relSubpoint[0];

@@ -492,4 +497,4 @@ data[6] += item.base[1] - relSubpoint[1];

if (instruction == 'h') relSubpoint[0] += data[0];
else if (instruction == 'v') relSubpoint[1] += data[0];
if (command == 'h') relSubpoint[0] += data[0];
else if (command == 'v') relSubpoint[1] += data[0];
else {

@@ -501,3 +506,3 @@ relSubpoint[0] += data[data.length - 2];

if (instruction.toLowerCase() == 'm') {
if (command === 'M' || command === 'm') {
pathBase[0] = relSubpoint[0];

@@ -511,21 +516,21 @@ pathBase[1] = relSubpoint[1];

if (
(instruction === 'c' && isCurveStraightLine(data)) ||
(instruction === 's' && isCurveStraightLine(sdata))
(command === 'c' && isCurveStraightLine(data)) ||
(command === 's' && isCurveStraightLine(sdata))
) {
if (next && next.instruction == 's') makeLonghand(next, data); // fix up next curve
instruction = 'l';
if (next && next.command == 's') makeLonghand(next, data); // fix up next curve
command = 'l';
data = data.slice(-2);
} else if (instruction === 'q' && isCurveStraightLine(data)) {
if (next && next.instruction == 't') makeLonghand(next, data); // fix up next curve
instruction = 'l';
} else if (command === 'q' && isCurveStraightLine(data)) {
if (next && next.command == 't') makeLonghand(next, data); // fix up next curve
command = 'l';
data = data.slice(-2);
} else if (
instruction === 't' &&
prev.instruction !== 'q' &&
prev.instruction !== 't'
command === 't' &&
prev.command !== 'q' &&
prev.command !== 't'
) {
instruction = 'l';
command = 'l';
data = data.slice(-2);
} else if (instruction === 'a' && (data[0] === 0 || data[1] === 0)) {
instruction = 'l';
} else if (command === 'a' && (data[0] === 0 || data[1] === 0)) {
command = 'l';
data = data.slice(-2);

@@ -538,8 +543,8 @@ }

// l 0 50 → v 50
if (params.lineShorthands && instruction === 'l') {
if (params.lineShorthands && command === 'l') {
if (data[1] === 0) {
instruction = 'h';
command = 'h';
data.pop();
} else if (data[0] === 0) {
instruction = 'v';
command = 'v';
data.shift();

@@ -554,11 +559,11 @@ }

hasMarkerMid === false &&
'mhv'.indexOf(instruction) > -1 &&
prev.instruction &&
instruction == prev.instruction.toLowerCase() &&
((instruction != 'h' && instruction != 'v') ||
prev.data[0] >= 0 == data[0] >= 0)
(command === 'm' || command === 'h' || command === 'v') &&
prev.command &&
command == prev.command.toLowerCase() &&
((command != 'h' && command != 'v') ||
prev.args[0] >= 0 == data[0] >= 0)
) {
prev.data[0] += data[0];
if (instruction != 'h' && instruction != 'v') {
prev.data[1] += data[1];
prev.args[0] += data[0];
if (command != 'h' && command != 'v') {
prev.args[1] += data[1];
}

@@ -571,12 +576,12 @@ prev.coords = item.coords;

// convert curves into smooth shorthands
if (params.curveSmoothShorthands && prev.instruction) {
if (params.curveSmoothShorthands && prev.command) {
// curveto
if (instruction === 'c') {
if (command === 'c') {
// c + c → c + s
if (
prev.instruction === 'c' &&
data[0] === -(prev.data[2] - prev.data[4]) &&
data[1] === -(prev.data[3] - prev.data[5])
prev.command === 'c' &&
data[0] === -(prev.args[2] - prev.args[4]) &&
data[1] === -(prev.args[3] - prev.args[5])
) {
instruction = 's';
command = 's';
data = data.slice(2);

@@ -587,7 +592,7 @@ }

else if (
prev.instruction === 's' &&
data[0] === -(prev.data[0] - prev.data[2]) &&
data[1] === -(prev.data[1] - prev.data[3])
prev.command === 's' &&
data[0] === -(prev.args[0] - prev.args[2]) &&
data[1] === -(prev.args[1] - prev.args[3])
) {
instruction = 's';
command = 's';
data = data.slice(2);

@@ -598,7 +603,8 @@ }

else if (
'cs'.indexOf(prev.instruction) === -1 &&
prev.command !== 'c' &&
prev.command !== 's' &&
data[0] === 0 &&
data[1] === 0
) {
instruction = 's';
command = 's';
data = data.slice(2);

@@ -609,10 +615,10 @@ }

// quadratic Bézier curveto
else if (instruction === 'q') {
else if (command === 'q') {
// q + q → q + t
if (
prev.instruction === 'q' &&
data[0] === prev.data[2] - prev.data[0] &&
data[1] === prev.data[3] - prev.data[1]
prev.command === 'q' &&
data[0] === prev.args[2] - prev.args[0] &&
data[1] === prev.args[3] - prev.args[1]
) {
instruction = 't';
command = 't';
data = data.slice(2);

@@ -623,7 +629,7 @@ }

else if (
prev.instruction === 't' &&
data[2] === prev.data[0] &&
data[3] === prev.data[1]
prev.command === 't' &&
data[2] === prev.args[0] &&
data[3] === prev.args[1]
) {
instruction = 't';
command = 't';
data = data.slice(2);

@@ -638,3 +644,9 @@ }

if (
'lhvqtcs'.indexOf(instruction) > -1 &&
(command === 'l' ||
command === 'h' ||
command === 'v' ||
command === 'q' ||
command === 't' ||
command === 'c' ||
command === 's') &&
data.every(function (i) {

@@ -649,3 +661,3 @@ return i === 0;

// a 25,25 -30 0,1 0,0
if (instruction === 'a' && data[5] === 0 && data[6] === 0) {
if (command === 'a' && data[5] === 0 && data[6] === 0) {
path[index] = prev;

@@ -656,4 +668,4 @@ return false;

item.instruction = instruction;
item.data = data;
item.command = command;
item.args = data;

@@ -665,3 +677,3 @@ prev = item;

relSubpoint[1] = pathBase[1];
if (prev.instruction == 'z') return false;
if (prev.command === 'Z' || prev.command === 'z') return false;
prev = item;

@@ -687,3 +699,3 @@ }

if (index == 0) return true;
if (!item.data) {
if (item.command === 'Z' || item.command === 'z') {
prev = item;

@@ -693,15 +705,22 @@ return true;

var instruction = item.instruction,
data = item.data,
adata = data && data.slice(0);
var command = item.command,
data = item.args,
adata = data.slice();
if ('mltqsc'.indexOf(instruction) > -1) {
if (
command === 'm' ||
command === 'l' ||
command === 't' ||
command === 'q' ||
command === 's' ||
command === 'c'
) {
for (var i = adata.length; i--; ) {
adata[i] += item.base[i % 2];
}
} else if (instruction == 'h') {
} else if (command == 'h') {
adata[0] += item.base[0];
} else if (instruction == 'v') {
} else if (command == 'v') {
adata[0] += item.base[1];
} else if (instruction == 'a') {
} else if (command == 'a') {
adata[5] += item.base[0];

@@ -718,3 +737,3 @@ adata[6] += item.base[1];

// v-20 -> V0
// Don't convert if it fits following previous instruction.
// Don't convert if it fits following previous command.
// l20 30-10-50 instead of l20 30L20 30

@@ -726,11 +745,11 @@ if (

params.negativeExtraSpace &&
instruction == prev.instruction &&
prev.instruction.charCodeAt(0) > 96 &&
command == prev.command &&
prev.command.charCodeAt(0) > 96 &&
absoluteDataStr.length == relativeDataStr.length - 1 &&
(data[0] < 0 ||
(/^0\./.test(data[0]) && prev.data[prev.data.length - 1] % 1))
(/^0\./.test(data[0]) && prev.args[prev.args.length - 1] % 1))
))
) {
item.instruction = instruction.toUpperCase();
item.data = adata;
item.command = command.toUpperCase();
item.args = adata;
}

@@ -874,11 +893,11 @@

function makeLonghand(item, data) {
switch (item.instruction) {
switch (item.command) {
case 's':
item.instruction = 'c';
item.command = 'c';
break;
case 't':
item.instruction = 'q';
item.command = 'q';
break;
}
item.data.unshift(
item.args.unshift(
data[data.length - 2] - data[data.length - 4],

@@ -1031,7 +1050,7 @@ data[data.length - 1] - data[data.length - 3]

var strData = '';
if (item.data) {
strData = cleanupOutData(roundData(item.data.slice()), params);
if (item.args) {
strData = cleanupOutData(roundData(item.args.slice()), params);
}
return pathString + item.instruction + strData;
return pathString + item.command + strData;
}, '');
}
'use strict';
/**
* @typedef {import('../lib/types').PathDataItem} PathDataItem
*/
const { stringifyPathData } = require('../lib/path.js');

@@ -21,5 +25,10 @@ const { detachNodeFromParent } = require('../lib/xast.js');

* @author Lev Solntsev
*
* @type {import('../lib/types').Plugin<{
* convertArcs?: boolean,
* floatPrecision?: number
* }>}
*/
exports.fn = (root, params) => {
const { convertArcs = false, floatPrecision: precision = null } = params;
const { convertArcs = false, floatPrecision: precision } = params;

@@ -45,2 +54,5 @@ return {

if (Number.isNaN(x - y + width - height)) return;
/**
* @type {Array<PathDataItem>}
*/
const pathData = [

@@ -68,2 +80,5 @@ { command: 'M', args: [x, y] },

if (Number.isNaN(x1 - y1 + x2 - y2)) return;
/**
* @type {Array<PathDataItem>}
*/
const pathData = [

@@ -93,2 +108,5 @@ { command: 'M', args: [x1, y1] },

}
/**
* @type {Array<PathDataItem>}
*/
const pathData = [];

@@ -117,2 +135,5 @@ for (let i = 0; i < coords.length; i += 2) {

}
/**
* @type {Array<PathDataItem>}
*/
const pathData = [

@@ -140,2 +161,5 @@ { command: 'M', args: [cx, cy - r] },

}
/**
* @type {Array<PathDataItem>}
*/
const pathData = [

@@ -142,0 +166,0 @@ { command: 'M', args: [ecx, ecy - ry] },

'use strict';
exports.name = 'convertTransform';
/**
* @typedef {import('../lib/types').XastElement} XastElement
*/
exports.type = 'perItem';
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';
exports.params = {
convertToShorts: true,
// degPrecision: 3, // transformPrecision (or matrix precision) - 2 by default
floatPrecision: 3,
transformPrecision: 5,
matrixToTransform: true,
shortTranslate: true,
shortScale: true,
shortRotate: true,
removeUseless: true,
collapseIntoOne: true,
leadingZero: true,
negativeExtraSpace: false,
};
var cleanupOutData = require('../lib/svgo/tools').cleanupOutData,
transform2js = require('./_transforms.js').transform2js,
transformsMultiply = require('./_transforms.js').transformsMultiply,
matrixToTransform = require('./_transforms.js').matrixToTransform,
degRound,
floatRound,
transformRound;
/**

@@ -42,26 +27,89 @@ * Convert matrices to the short aliases,

*
* @param {Object} item current iteration item
* @param {Object} params plugin params
* @return {Boolean} if false, item will be filtered out
* @author Kir Belevich
*
* @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 = function (item, params) {
if (item.type === 'element') {
// transform
if (item.attributes.transform != null) {
convertTransform(item, 'transform', params);
}
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);
}
},
},
};
};
// gradientTransform
if (item.attributes.gradientTransform != null) {
convertTransform(item, 'gradientTransform', params);
}
/**
* @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
*/
// patternTransform
if (item.attributes.patternTransform != null) {
convertTransform(item, 'patternTransform', params);
}
}
};
/**
* @typedef {{ name: string, data: Array<number> }} TransformItem
*/

@@ -71,7 +119,5 @@ /**

*
* @param {Object} item input item
* @param {String} attrName attribute name
* @param {Object} params plugin params
* @type {(item: XastElement, attrName: string, params: TransformParams) => void}
*/
function convertTransform(item, attrName, params) {
const convertTransform = (item, attrName, params) => {
let data = transform2js(item.attributes[attrName]);

@@ -87,3 +133,3 @@ params = definePrecision(data, params);

} else {
data.forEach(roundTransform);
data.forEach((item) => roundTransform(item, params));
}

@@ -100,3 +146,3 @@

}
}
};

@@ -110,69 +156,83 @@ /**

*
* @param {Array} transforms input array
* @param {Object} params plugin params
* @return {Array} output array
* @type {(data: Array<TransformItem>, params: TransformParams) => TransformParams}
*
* clone params so it don't affect other elements transformations.
*/
function definePrecision(data, params) {
var matrixData = data.reduce(getMatrixData, []),
significantDigits = params.transformPrecision;
// Clone params so it don't affect other elements transformations.
params = Object.assign({}, params);
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) {
params.transformPrecision = Math.min(
params.transformPrecision,
newParams.transformPrecision = Math.min(
newParams.transformPrecision,
Math.max.apply(Math, matrixData.map(floatDigits)) ||
params.transformPrecision
newParams.transformPrecision
);
significantDigits = Math.max.apply(
Math,
matrixData.map(function (n) {
return String(n).replace(/\D+/g, '').length; // Number of digits in a number. 123.45 → 5
})
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 (!('degPrecision' in params)) {
params.degPrecision = Math.max(
if (newParams.degPrecision == null) {
newParams.degPrecision = Math.max(
0,
Math.min(params.floatPrecision, significantDigits - 2)
Math.min(newParams.floatPrecision, significantDigits - 2)
);
}
return newParams;
};
floatRound =
params.floatPrecision >= 1 && params.floatPrecision < 20
? smartRound.bind(this, params.floatPrecision)
: round;
degRound =
params.degPrecision >= 1 && params.floatPrecision < 20
? smartRound.bind(this, params.degPrecision)
: round;
transformRound =
params.transformPrecision >= 1 && params.floatPrecision < 20
? smartRound.bind(this, params.transformPrecision)
: round;
/**
* @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);
}
};
return params;
}
/**
* Gathers four first matrix parameters.
*
* @param {Array} a array of data
* @param {Object} transform
* @return {Array} output array
* @type {(data: Array<number>, params: TransformParams) => Array<number>}
*/
function getMatrixData(a, b) {
return b.name == 'matrix' ? a.concat(b.data.slice(0, 4)) : a;
}
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}
*/
function floatDigits(n) {
return (n = String(n)).slice(n.indexOf('.')).length - 1;
}
const floatDigits = (n) => {
const str = n.toString();
return str.slice(str.indexOf('.')).length - 1;
};

@@ -182,7 +242,5 @@ /**

*
* @param {Array} transforms input array
* @param {Object} params plugin params
* @return {Array} output array
* @type {(transforms: Array<TransformItem>, params: TransformParams) => Array<TransformItem>}
*/
function convertToShorts(transforms, params) {
const convertToShorts = (transforms, params) => {
for (var i = 0; i < transforms.length; i++) {

@@ -195,7 +253,6 @@ var transform = transforms[i];

if (
decomposed != transform &&
js2transform(decomposed, params).length <=
js2transform([transform], params).length
js2transform([transform], params).length
) {
transforms.splice.apply(transforms, [i, 1].concat(decomposed));
transforms.splice(i, 1, ...decomposed);
}

@@ -207,3 +264,3 @@ transform = transforms[i];

// 12.754997 → 12.755
roundTransform(transform);
roundTransform(transform, params);

@@ -258,3 +315,3 @@ // convert long translate transform notation to the shorts one

return transforms;
}
};

@@ -264,7 +321,6 @@ /**

*
* @param {Array} transforms input array
* @return {Array} output array
* @type {(trasforms: Array<TransformItem>) => Array<TransformItem>}
*/
function removeUseless(transforms) {
return transforms.filter(function (transform) {
const removeUseless = (transforms) => {
return transforms.filter((transform) => {
// translate(0), rotate(0[, cx, cy]), skewX(0), skewY(0)

@@ -299,3 +355,3 @@ if (

});
}
};

@@ -305,12 +361,10 @@ /**

*
* @param {Array} transformJS JS representation array
* @param {Object} params plugin params
* @return {String} output string
* @type {(transformJS: Array<TransformItem>, params: TransformParams) => string}
*/
function js2transform(transformJS, params) {
const js2transform = (transformJS, params) => {
var transformString = '';
// collect output value string
transformJS.forEach(function (transform) {
roundTransform(transform);
transformJS.forEach((transform) => {
roundTransform(transform, params);
transformString +=

@@ -325,29 +379,34 @@ (transformString && ' ') +

return transformString;
}
};
function roundTransform(transform) {
/**
* @type {(transform: TransformItem, params: TransformParams) => TransformItem}
*/
const roundTransform = (transform, params) => {
switch (transform.name) {
case 'translate':
transform.data = floatRound(transform.data);
transform.data = floatRound(transform.data, params);
break;
case 'rotate':
transform.data = degRound(transform.data.slice(0, 1)).concat(
floatRound(transform.data.slice(1))
);
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);
transform.data = degRound(transform.data, params);
break;
case 'scale':
transform.data = transformRound(transform.data);
transform.data = transformRound(transform.data, params);
break;
case 'matrix':
transform.data = transformRound(transform.data.slice(0, 4)).concat(
floatRound(transform.data.slice(4))
);
transform.data = [
...transformRound(transform.data.slice(0, 4), params),
...floatRound(transform.data.slice(4), params),
];
break;
}
return transform;
}
};

@@ -357,8 +416,7 @@ /**

*
* @param {Array} data input data array
* @return {Array} output data array
* @type {(data: Array<number>) => Array<number>}
*/
function round(data) {
const round = (data) => {
return data.map(Math.round);
}
};

@@ -370,7 +428,5 @@ /**

*
* @param {Number} fixed number of decimals
* @param {Array} data input data array
* @return {Array} output data array
* @type {(precision: number, data: Array<number>) => Array<number>}
*/
function smartRound(precision, data) {
const smartRound = (precision, data) => {
for (

@@ -382,3 +438,3 @@ var i = data.length,

) {
if (data[i].toFixed(precision) != data[i]) {
if (Number(data[i].toFixed(precision)) !== data[i]) {
var rounded = +data[i].toFixed(precision - 1);

@@ -392,2 +448,2 @@ data[i] =

return data;
}
};

@@ -151,7 +151,20 @@ 'use strict';

}
const styleDeclarationList = csstree.parse(
selectedEl.attributes.style == null ? '' : selectedEl.attributes.style,
{
context: 'declarationList',
parseValue: false,
}
);
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: function (styleCsstreeDeclaration) {
enter(ruleDeclaration) {
// existing inline styles have higher priority

@@ -161,19 +174,25 @@ // no inline styles, external styles, external styles used

// inline styles, external styles higher priority than inline styles, external styles used
var styleDeclaration = cssTools.csstreeToStyleDeclaration(
styleCsstreeDeclaration
const matchedItem = styleDeclarationItems.get(
ruleDeclaration.property
);
if (
selectedEl.style.getPropertyValue(styleDeclaration.name) !== null &&
selectedEl.style.getPropertyPriority(styleDeclaration.name) >=
styleDeclaration.priority
const ruleDeclarationItem =
styleDeclarationList.children.createItem(ruleDeclaration);
if (matchedItem == null) {
styleDeclarationList.children.append(ruleDeclarationItem);
} else if (
matchedItem.data.important !== true &&
ruleDeclaration.important === true
) {
return;
styleDeclarationList.children.replace(
matchedItem,
ruleDeclarationItem
);
styleDeclarationItems.set(
ruleDeclaration.property,
ruleDeclarationItem
);
}
selectedEl.style.setProperty(
styleDeclaration.name,
styleDeclaration.value,
styleDeclaration.priority
);
},
});
selectedEl.attributes.style = csstree.generate(styleDeclarationList);
}

@@ -212,9 +231,15 @@

// class
var firstSubSelector = selector.item.data.children.first();
const classList = new Set(
selectedEl.attributes.class == null
? null
: selectedEl.attributes.class.split(' ')
);
const firstSubSelector = selector.item.data.children.first();
if (firstSubSelector.type === 'ClassSelector') {
selectedEl.class.remove(firstSubSelector.name);
classList.delete(firstSubSelector.name);
}
// clean up now empty class attributes
if (typeof selectedEl.class.item(0) === 'undefined') {
if (classList.size === 0) {
delete selectedEl.attributes.class;
} else {
selectedEl.attributes.class = Array.from(classList).join(' ');
}

@@ -221,0 +246,0 @@

@@ -7,4 +7,4 @@ 'use strict';

exports.type = 'visitor';
exports.name = 'mergePaths';
exports.type = 'visitor';
exports.active = true;

@@ -16,6 +16,9 @@ exports.description = 'merges multiple paths in one if possible';

*
* @param {Object} root
* @param {Object} params
* @author Kir Belevich, Lev Solntsev
*
* @author Kir Belevich, Lev Solntsev
* @type {import('../lib/types').Plugin<{
* force?: boolean,
* floatPrecision?: number,
* noSpaceAfterFlags?: boolean
* }>}
*/

@@ -22,0 +25,0 @@ exports.fn = (root, params) => {

@@ -67,2 +67,4 @@ 'use strict';

}
return false;
}),

@@ -69,0 +71,0 @@ allPath = item.children.every(function (inner) {

'use strict';
const { querySelectorAll } = require('../lib/xast.js');
exports.name = 'removeAttributesBySelector';
exports.type = 'perItem';
exports.type = 'visitor';
exports.active = false;
exports.description =

@@ -15,12 +14,13 @@ 'removes attributes of elements that match a css selector';

*
* @param {Object} item current iteration item
* @param {Object} params plugin params
* @return {Boolean} if false, item will be filtered out
*
* @example
* <caption>A selector removing a single attribute</caption>
* plugins:
* - removeAttributesBySelector:
* plugins: [
* {
* name: "removeAttributesBySelector",
* params: {
* selector: "[fill='#00ff00']"
* attributes: "fill"
* }
* }
* ]
*

@@ -32,8 +32,14 @@ * <rect x="0" y="0" width="100" height="100" fill="#00ff00" stroke="#00ff00"/>

* <caption>A selector removing multiple attributes</caption>
* plugins:
* - removeAttributesBySelector:
* selector: "[fill='#00ff00']"
* attributes:
* - fill
* - stroke
* plugins: [
* {
* name: "removeAttributesBySelector",
* params: {
* selector: "[fill='#00ff00']",
* attributes: [
* "fill",
* "stroke"
* ]
* }
* }
* ]
*

@@ -45,13 +51,23 @@ * <rect x="0" y="0" width="100" height="100" fill="#00ff00" stroke="#00ff00"/>

* <caption>Multiple selectors removing attributes</caption>
* plugins:
* - removeAttributesBySelector:
* selectors:
* - selector: "[fill='#00ff00']"
* plugins: [
* {
* name: "removeAttributesBySelector",
* params: {
* selectors: [
* {
* selector: "[fill='#00ff00']",
* attributes: "fill"
* },
* {
* selector: "#remove",
* attributes: [
* "stroke",
* "id"
* ]
* }
* ]
* }
* }
* ]
*
* - selector: "#remove"
* attributes:
* - stroke
* - id
*
* <rect x="0" y="0" width="100" height="100" fill="#00ff00" stroke="#00ff00"/>

@@ -61,20 +77,27 @@ * ↓

*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|MDN CSS Selectors}
* @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 = function (item, params) {
var selectors = Array.isArray(params.selectors) ? params.selectors : [params];
selectors.map(({ selector, attributes }) => {
if (item.matches(selector)) {
if (Array.isArray(attributes)) {
for (const name of attributes) {
delete item.attributes[name];
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];
}
} else {
delete item.attributes[attributes];
}
}
});
}
return {};
};
'use strict';
var DEFAULT_SEPARATOR = ':';
exports.name = 'removeAttrs';
exports.type = 'perItem';
exports.type = 'visitor';
exports.active = false;
exports.description = 'removes specified attributes';
exports.params = {
elemSeparator: DEFAULT_SEPARATOR,
preserveCurrentColor: false,
attrs: [],
};
const DEFAULT_SEPARATOR = ':';

@@ -22,9 +13,9 @@ /**

*
* @param elemSeparator
* @example elemSeparator
* format: string
*
* @param preserveCurrentColor
* @example preserveCurrentColor
* format: boolean
*
* @param attrs:
* @example attrs:
*

@@ -81,63 +72,66 @@ * format: [ element* : attribute* : value* ]

*
* @param {Object} item current iteration item
* @param {Object} params plugin params
* @return {Boolean} if false, item will be filtered out
* @author Benny Schudel
*
* @author Benny Schudel
* @type {import('../lib/types').Plugin<{
* elemSeparator?: string,
* preserveCurrentColor?: boolean,
* attrs: string | Array<string>
* }>}
*/
exports.fn = function (item, params) {
exports.fn = (root, params) => {
// wrap into an array if params is not
if (!Array.isArray(params.attrs)) {
params.attrs = [params.attrs];
}
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];
if (item.type === 'element') {
var elemSeparator =
typeof params.elemSeparator == 'string'
? params.elemSeparator
: DEFAULT_SEPARATOR;
var preserveCurrentColor =
typeof params.preserveCurrentColor == 'boolean'
? params.preserveCurrentColor
: false;
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('');
}
// prepare patterns
var patterns = params.attrs.map(function (pattern) {
// if no element separators (:), assume it's attribute name, and apply to all elements *regardless of value*
if (pattern.indexOf(elemSeparator) === -1) {
pattern = ['.*', elemSeparator, 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');
});
// 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
return pattern.split(elemSeparator).map(function (value) {
// adjust single * to match anything
if (value === '*') {
value = '.*';
}
return new RegExp(['^', value, '$'].join(''), 'i');
});
});
// loop patterns
patterns.forEach(function (pattern) {
// matches element
if (pattern[0].test(item.name)) {
// loop attributes
for (const [name, value] of Object.entries(item.attributes)) {
var isFillCurrentColor =
preserveCurrentColor && name == 'fill' && value == 'currentColor';
var isStrokeCurrentColor =
preserveCurrentColor && name == 'stroke' && value == 'currentColor';
if (!(isFillCurrentColor || isStrokeCurrentColor)) {
// matches attribute name
if (pattern[1].test(name)) {
// matches attribute value
if (pattern[2].test(value)) {
delete item.attributes[name];
// 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];
}

@@ -147,5 +141,5 @@ }

}
}
});
}
},
},
};
};

@@ -18,2 +18,4 @@ 'use strict';

* @author Kir Belevich
*
* @type {import('../lib/types').Plugin<void>}
*/

@@ -20,0 +22,0 @@ exports.fn = () => {

@@ -20,2 +20,4 @@ 'use strict';

* @author Daniel Wabyick
*
* @type {import('../lib/types').Plugin<{ removeAny?: boolean }>}
*/

@@ -22,0 +24,0 @@ exports.fn = (root, params) => {

@@ -31,2 +31,4 @@ 'use strict';

* @author Kir Belevich
*
* @type {import('../lib/types').Plugin<void>}
*/

@@ -33,0 +35,0 @@ exports.fn = () => {

'use strict';
const { detachNodeFromParent } = require('../lib/xast.js');
exports.name = 'removeElementsByAttr';
exports.type = 'perItem';
exports.type = 'visitor';
exports.active = false;
exports.description =
'removes arbitrary elements by ID or className (disabled by default)';
exports.params = {
id: [],
class: [],
};
/**
* Remove arbitrary SVG elements by ID or className.
*
* @param id
* examples:
*
* @example id
* > single: remove element with ID of `elementID`

@@ -35,5 +27,3 @@ * ---

*
* @param class
* examples:
*
* @example class
* > single: remove all elements with class of `elementClass`

@@ -51,31 +41,40 @@ * ---

*
* @param {Object} item current iteration item
* @param {Object} params plugin params
* @return {Boolean} if false, item will be filtered out
* @author Eli Dupuis (@elidupuis)
*
* @author Eli Dupuis (@elidupuis)
* @type {import('../lib/types').Plugin<{
* id?: string | Array<string>,
* class?: string | Array<string>
* }>}
*/
exports.fn = function (item, params) {
// wrap params in an array if not already
['id', 'class'].forEach(function (key) {
if (!Array.isArray(params[key])) {
params[key] = [params[key]];
}
});
// abort if current item is no an element
if (item.type !== 'element') {
return;
}
// remove element if it's `id` matches configured `id` params
if (item.attributes.id != null && params.id.length !== 0) {
return params.id.includes(item.attributes.id) === false;
}
// remove element if it's `class` contains any of the configured `class` params
if (item.attributes.class && params.class.length !== 0) {
const classList = item.attributes.class.split(' ');
return params.class.some((item) => classList.includes(item)) === false;
}
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;
}
}
}
},
},
};
};

@@ -26,2 +26,8 @@ 'use strict';

* @author Kir Belevich
*
* @type {import('../lib/types').Plugin<{
* text?: boolean,
* tspan?: boolean,
* tref?: boolean
* }>}
*/

@@ -28,0 +34,0 @@ exports.fn = (root, params) => {

@@ -30,6 +30,21 @@ 'use strict';

*
* @param {Object} root
* @param {Object} params
* @author Kir Belevich
*
* @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,
* }>}
*/

@@ -36,0 +51,0 @@ exports.fn = (root, params) => {

@@ -16,2 +16,4 @@ 'use strict';

* @author Kir Belevich
*
* @type {import('../lib/types').Plugin<void>}
*/

@@ -18,0 +20,0 @@ exports.fn = () => {

'use strict';
exports.name = 'removeOffCanvasPaths';
/**
* @typedef {import('../lib/types').PathDataItem} PathDataItem
*/
exports.type = 'perItem';
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)';
const JSAPI = require('../lib/svgo/jsAPI.js');
var _path = require('./_path.js'),
intersects = _path.intersects,
path2js = _path.path2js,
viewBox,
viewBoxJS;
/**
* Remove elements that are drawn outside of the viewbox.
*
* @param {Object} item current iteration item
* @return {Boolean} if false, item will be filtered out
* @author JoshyPHP
*
* @author JoshyPHP
* @type {import('../lib/types').Plugin<void>}
*/
exports.fn = function (item) {
if (
item.type === 'element' &&
item.name === 'path' &&
item.attributes.d != null &&
typeof viewBox !== 'undefined'
) {
// Consider that any item with a transform attribute or a M instruction
// within the viewBox is visible
if (hasTransform(item) || pathMovesWithinViewBox(item.attributes.d)) {
return true;
}
exports.fn = () => {
/**
* @type {null | {
* top: number,
* right: number,
* bottom: number,
* left: number,
* width: number,
* height: number
* }}
*/
let viewBoxData = null;
var pathJS = path2js(item);
if (pathJS.length === 2) {
// Use a closed clone of the path if it's too short for intersects()
pathJS = JSON.parse(JSON.stringify(pathJS));
pathJS.push({ instruction: 'z' });
}
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}`;
}
return intersects(viewBoxJS, pathJS);
}
if (item.type === 'element' && item.name === 'svg') {
parseViewBox(item);
}
// 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]);
return true;
};
// store the viewBox boundaries
viewBoxData = {
left,
top,
right: left + width,
bottom: top + height,
width,
height,
};
}
/**
* Test whether given item or any of its ancestors has a transform attribute.
*
* @param {String} path
* @return {Boolean}
*/
function hasTransform(item) {
return (
item.attributes.transform != null ||
(item.parentNode &&
item.parentNode.type === 'element' &&
hasTransform(item.parentNode))
);
}
// consider that any item with a transform attribute is visible
if (node.attributes.transform != null) {
return visitSkip;
}
/**
* Parse the viewBox coordinates and compute the JS representation of its path.
*
* @param {Object} svg svg element item
*/
function parseViewBox(svg) {
var viewBoxData = '';
if (svg.attributes.viewBox != null) {
// Remove commas and plus signs, normalize and trim whitespace
viewBoxData = svg.attributes.viewBox;
} else if (svg.attributes.height != null && svg.attributes.width != null) {
viewBoxData = `0 0 ${svg.attributes.width} ${svg.attributes.height}`;
}
if (
node.name === 'path' &&
node.attributes.d != null &&
viewBoxData != null
) {
const pathData = parsePathData(node.attributes.d);
// Remove commas and plus signs, normalize and trim whitespace
viewBoxData = viewBoxData
.replace(/[,+]|px/g, ' ')
.replace(/\s+/g, ' ')
.replace(/^\s*|\s*$/g, '');
// 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;
}
// Ensure that the dimensions are 4 values separated by space
var m = /^(-?\d*\.?\d+) (-?\d*\.?\d+) (\d*\.?\d+) (\d*\.?\d+)$/.exec(
viewBoxData
);
if (!m) {
return;
}
if (pathData.length === 2) {
// close the path too short for intersects()
pathData.push({ command: 'z', args: [] });
}
// Store the viewBox boundaries
viewBox = {
left: parseFloat(m[1]),
top: parseFloat(m[2]),
right: parseFloat(m[1]) + parseFloat(m[3]),
bottom: parseFloat(m[2]) + parseFloat(m[4]),
};
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: [] },
];
var path = new JSAPI({
type: 'element',
name: 'path',
attributes: {
d: 'M' + m[1] + ' ' + m[2] + 'h' + m[3] + 'v' + m[4] + 'H' + m[1] + 'z',
if (intersects(viewBoxPathData, pathData) === false) {
detachNodeFromParent(node, parentNode);
}
}
},
},
content: [],
});
viewBoxJS = path2js(path);
}
/**
* Test whether given path has a M instruction with coordinates within the viewBox.
*
* @param {String} path
* @return {Boolean}
*/
function pathMovesWithinViewBox(path) {
var regexp = /M\s*(-?\d*\.?\d+)(?!\d)\s*(-?\d*\.?\d+)/g,
m;
while (null !== (m = regexp.exec(path))) {
if (
m[1] >= viewBox.left &&
m[1] <= viewBox.right &&
m[2] >= viewBox.top &&
m[2] <= viewBox.bottom
) {
return true;
}
}
return false;
}
};
};

@@ -16,2 +16,4 @@ 'use strict';

* @author Kir Belevich
*
* @type {import('../lib/types').Plugin<void>}
*/

@@ -18,0 +20,0 @@ exports.fn = () => {

@@ -15,4 +15,5 @@ 'use strict';

*
* @author Patrick Klingemann
*
* @author Patrick Klingemann
* @type {import('../lib/types').Plugin<void>}
*/

@@ -19,0 +20,0 @@ exports.fn = () => {

@@ -16,2 +16,4 @@ 'use strict';

* @author Betsy Dupuis
*
* @type {import('../lib/types').Plugin<void>}
*/

@@ -18,0 +20,0 @@ exports.fn = () => {

@@ -16,2 +16,4 @@ 'use strict';

* @author Igor Kalashnikov
*
* @type {import('../lib/types').Plugin<void>}
*/

@@ -18,0 +20,0 @@ exports.fn = () => {

'use strict';
const { parseName } = require('../lib/svgo/tools.js');
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.type = 'perItem';
exports.active = true;
exports.description =
'removes unknown elements content and attributes, removes attrs with default values';
exports.params = {
unknownContent: true,
unknownAttrs: true,
defaultAttrs: true,
uselessOverrides: true,
keepDataAttrs: true,
keepAriaAttrs: true,
keepRoleAttr: false,
};
// resolve all groups references
var collections = require('./_collections'),
elems = collections.elems,
attrsGroups = collections.attrsGroups,
elemsGroups = collections.elemsGroups,
attrsGroupsDefaults = collections.attrsGroupsDefaults,
attrsInheritable = collections.inheritableAttrs,
applyGroups = collections.presentationNonInheritableGroupAttrs;
/**
* @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();
// collect and extend all references
for (const elem of Object.values(elems)) {
if (elem.attrsGroups) {
elem.attrs = elem.attrs || [];
elem.attrsGroups.forEach(function (attrsGroupName) {
elem.attrs = elem.attrs.concat(attrsGroups[attrsGroupName]);
var groupDefaults = attrsGroupsDefaults[attrsGroupName];
if (groupDefaults) {
elem.defaults = elem.defaults || {};
for (const [attrName, attr] of Object.entries(groupDefaults)) {
elem.defaults[attrName] = attr;
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);
}
}
});
}
}
if (elem.contentGroups) {
elem.content = elem.content || [];
elem.contentGroups.forEach(function (contentGroupName) {
elem.content = elem.content.concat(elemsGroups[contentGroupName]);
});
/**
* @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);
}

@@ -65,67 +95,125 @@

*
* @param {Object} item current iteration item
* @param {Object} params plugin params
* @return {Boolean} if false, item will be filtered out
* @author Kir Belevich
*
* @author Kir Belevich
* @type {import('../lib/types').Plugin<{
* unknownContent?: boolean,
* unknownAttrs?: boolean,
* defaultAttrs?: boolean,
* uselessOverrides?: boolean,
* keepDataAttrs?: boolean,
* keepAriaAttrs?: boolean,
* keepRoleAttr?: boolean,
* }>}
*/
exports.fn = function (item, params) {
// elems w/o namespace prefix
if (item.type === 'element' && !parseName(item.name).prefix) {
var elem = item.name;
exports.fn = (root, params) => {
const {
unknownContent = true,
unknownAttrs = true,
defaultAttrs = true,
uselessOverrides = true,
keepDataAttrs = true,
keepAriaAttrs = true,
keepRoleAttr = false,
} = params;
const stylesheet = collectStylesheet(root);
// remove unknown element's content
if (
params.unknownContent &&
elems[elem] && // make sure we know of this element before checking its children
elem !== 'foreignObject' // Don't check foreignObject
) {
item.children.forEach(function (content, i) {
if (
content.type === 'element' &&
!parseName(content.name).prefix &&
((elems[elem].content && // Do we have a record of its permitted content?
elems[elem].content.indexOf(content.name) === -1) ||
(!elems[elem].content && // we dont know about its permitted content
!elems[content.name])) // check that we know about the element at all
) {
item.children.splice(i, 1);
return {
element: {
enter: (node, parentNode) => {
// skip namespaced elements
if (node.name.includes(':')) {
return;
}
});
}
// skip visiting foreignObject subtree
if (node.name === 'foreignObject') {
return visitSkip;
}
// remove element's unknown attrs and attrs with default values
if (elems[elem] && elems[elem].attrs) {
for (const [name, value] of Object.entries(item.attributes)) {
const { prefix } = parseName(name);
if (
name !== 'xmlns' &&
(prefix === 'xml' || !prefix) &&
(!params.keepDataAttrs || name.indexOf('data-') != 0) &&
(!params.keepAriaAttrs || name.indexOf('aria-') != 0) &&
(!params.keepRoleAttr || name != 'role')
) {
// 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 (
// unknown attrs
(params.unknownAttrs && elems[elem].attrs.indexOf(name) === -1) ||
// attrs with default values
(params.defaultAttrs &&
item.attributes.id == null &&
elems[elem].defaults &&
elems[elem].defaults[name] === value &&
(attrsInheritable.includes(name) === false ||
!item.parentNode.computedAttr(name))) ||
// useless overrides
(params.uselessOverrides &&
item.attributes.id == null &&
applyGroups.includes(name) === false &&
attrsInheritable.includes(name) === true &&
item.parentNode.computedAttr(name, value))
unknownAttrs &&
allowedAttributes &&
allowedAttributes.has(name) === false
) {
delete item.attributes[name];
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];
}
}
}
}
}
}
},
},
};
};
'use strict';
const { elemsGroups } = require('./_collections');
/**
* @typedef {import('../lib/types').XastElement} XastElement
*/
const { detachNodeFromParent } = require('../lib/xast.js');
const { elemsGroups } = require('./_collections.js');
exports.type = 'visitor';
exports.name = 'removeUselessDefs';
exports.type = 'perItem';
exports.active = true;
exports.description = 'removes elements in <defs> without id';

@@ -16,36 +18,49 @@

*
* @param {Object} item current iteration item
* @return {Boolean} if false, item will be filtered out
* @author Lev Solntsev
*
* @author Lev Solntsev
* @type {import('../lib/types').Plugin<void>}
*/
exports.fn = function (item) {
if (item.type === 'element') {
if (item.name === 'defs') {
item.children = getUsefulItems(item, []);
if (item.children.length === 0) {
return false;
}
} else if (
elemsGroups.nonRendering.includes(item.name) &&
item.attributes.id == null
) {
return false;
}
}
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);
}
},
},
};
};
function getUsefulItems(item, usefulItems) {
for (const child of item.children) {
/**
* @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') {
usefulItems.push(child);
child.parentNode = item;
usefulNodes.push(child);
} else {
child.children = getUsefulItems(child, usefulItems);
collectUsefulNodes(child, usefulNodes);
}
}
}
return usefulItems;
}
};

@@ -19,4 +19,2 @@ 'use strict';

var shape = require('./_collections').elemsGroups.shape,
regStrokeProps = /^stroke/,
regFillProps = /^fill-/,
styleOrScript = ['style', 'script'];

@@ -63,3 +61,3 @@

for (const name of Object.keys(item.attributes)) {
if (regStrokeProps.test(name)) {
if (name.startsWith('stroke')) {
delete item.attributes[name];

@@ -78,3 +76,3 @@ }

for (const name of Object.keys(item.attributes)) {
if (regFillProps.test(name)) {
if (name.startsWith('fill-')) {
delete item.attributes[name];

@@ -81,0 +79,0 @@ }

'use strict';
const { closestByName } = require('../lib/xast.js');
exports.type = 'visitor';
exports.name = 'removeViewBox';
exports.type = 'perItem';
exports.active = true;
exports.description = 'removes viewBox attribute when possible';

@@ -25,31 +20,33 @@

*
* @param {Object} item current iteration item
* @return {Boolean} if false, item will be filtered out
* @author Kir Belevich
*
* @author Kir Belevich
* @type {import('../lib/types').Plugin<void>}
*/
exports.fn = function (item) {
if (
item.type === 'element' &&
viewBoxElems.includes(item.name) &&
item.attributes.viewBox != null &&
item.attributes.width != null &&
item.attributes.height != null
) {
// TODO remove width/height for such case instead
if (item.name === 'svg' && closestByName(item.parentNode, 'svg')) {
return;
}
const nums = item.attributes.viewBox.split(/[ ,]+/g);
if (
nums[0] === '0' &&
nums[1] === '0' &&
item.attributes.width.replace(/px$/, '') === nums[2] && // could use parseFloat too
item.attributes.height.replace(/px$/, '') === nums[3]
) {
delete item.attributes.viewBox;
}
}
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;
}
}
},
},
};
};

@@ -17,2 +17,4 @@ 'use strict';

* @author Kir Belevich
*
* @type {import('../lib/types').Plugin<void>}
*/

@@ -19,0 +21,0 @@ exports.fn = () => {

@@ -65,12 +65,3 @@ 'use strict';

for (let def of defs) {
// Remove class and style before copying to avoid circular refs in
// JSON.stringify. This is fine because we don't actually want class or
// style information to be copied.
const style = def.style;
const defClass = def.class;
delete def.style;
delete def.class;
const defClone = def.clone();
def.style = style;
def.class = defClass;
delete defClone.attributes.transform;

@@ -77,0 +68,0 @@ defsTag.spliceContent(0, 0, defClone);

'use strict';
exports.type = 'visitor';
exports.name = 'sortDefsChildren';
exports.type = 'perItem';
exports.active = true;
exports.description = 'Sorts children of <defs> to improve compression';

@@ -15,30 +12,50 @@

*
* @param {Object} item current iteration item
* @return {Boolean} if false, item will be filtered out
* @author David Leston
*
* @author David Leston
* @type {import('../lib/types').Plugin<void>}
*/
exports.fn = function (item) {
if (item.isElem('defs')) {
var frequency = item.children.reduce(function (frequency, child) {
if (child.name in frequency) {
frequency[child.name]++;
} else {
frequency[child.name] = 1;
}
return frequency;
}, {});
item.children.sort(function (a, b) {
var frequencyComparison = frequency[b.name] - frequency[a.name];
if (frequencyComparison !== 0) {
return frequencyComparison;
}
var lengthComparison = b.name.length - a.name.length;
if (lengthComparison !== 0) {
return lengthComparison;
}
return a.name != b.name ? (a.name > b.name ? -1 : 1) : 0;
});
return true;
}
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;
});
}
},
},
};
};

@@ -92,3 +92,3 @@ <div align="center">

overrides: {
// customize options
// customize options for plugins included in preset
builtinPluginName: {

@@ -102,2 +102,11 @@ optionName: 'optionValue',

},
// Enable builtin plugin not included in preset
'moreBuiltinPlugin',
// Enable and configure builtin plugin not included in preset
{
name: 'manyBuiltInPlugin',
params: {
optionName: 'value',
},
},
],

@@ -165,3 +174,3 @@ };

SVGO provides a few low level utilities. `extendDefaultPlugins` is described above.
SVGO provides a few low level utilities.

@@ -168,0 +177,0 @@ ### optimize

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc