You're Invited:Meet the Socket Team at BlackHat and DEF CON in Las Vegas, Aug 4-6.RSVP
Socket
Book a DemoInstallSign in
Socket

svgo

Package Overview
Dependencies
Maintainers
4
Versions
109
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

to
4.0.0-rc.2

lib/types.js

8

lib/builtin.js

@@ -56,3 +56,9 @@ import presetDefault from '../plugins/preset-default.js';

export const builtin = Object.freeze([
/**
* Plugins that are bundled with SVGO. This includes plugin presets, and plugins
* that are not enabled by default.
*
* @type {ReadonlyArray<{[Name in keyof import('./types.js').PluginsParams]: import('./types.js').BuiltinPluginOrPreset<Name, import('./types.js').PluginsParams[Name]>;}[keyof import('./types.js').PluginsParams]>}
*/
export const builtinPlugins = Object.freeze([
presetDefault,

@@ -59,0 +65,0 @@ addAttributesToSVGElement,

80

lib/parser.js

@@ -1,25 +0,11 @@

/**
* @typedef {import('./types.js').XastNode} XastNode
* @typedef {import('./types.js').XastInstruction} XastInstruction
* @typedef {import('./types.js').XastDoctype} XastDoctype
* @typedef {import('./types.js').XastComment} XastComment
* @typedef {import('./types.js').XastRoot} XastRoot
* @typedef {import('./types.js').XastElement} XastElement
* @typedef {import('./types.js').XastCdata} XastCdata
* @typedef {import('./types.js').XastText} XastText
* @typedef {import('./types.js').XastParent} XastParent
* @typedef {import('./types.js').XastChild} XastChild
*/
// @ts-ignore sax will be replaced with something else later
import SAX from 'sax';
import { textElems } from '../plugins/_collections.js';
class SvgoParserError extends Error {
export class SvgoParserError extends Error {
/**
* @param message {string}
* @param line {number}
* @param column {number}
* @param source {string}
* @param file {void | string}
* @param {string} message
* @param {number} line
* @param {number} column
* @param {string} source
* @param {string=} file
*/

@@ -38,2 +24,3 @@ constructor(message, line, column, source, file) {

}
toString() {

@@ -90,28 +77,19 @@ const lines = this.source.split(/\r?\n/);

*
* @type {(data: string, from?: string) => XastRoot}
* @param {string} data
* @param {string=} from
* @returns {import('./types.js').XastRoot}
*/
export const parseSvg = (data, from) => {
const sax = SAX.parser(config.strict, config);
/**
* @type {XastRoot}
*/
/** @type {import('./types.js').XastRoot} */
const root = { type: 'root', children: [] };
/**
* @type {XastParent}
*/
/** @type {import('./types.js').XastParent} */
let current = root;
/**
* @type {XastParent[]}
*/
/** @type {import('./types.js').XastParent[]} */
const stack = [root];
/**
* @type {(node: XastChild) => void}
* @param {import('./types.js').XastChild} node
*/
const pushToContent = (node) => {
// TODO remove legacy parentNode in v4
Object.defineProperty(node, 'parentNode', {
writable: true,
value: current,
});
current.children.push(node);

@@ -121,5 +99,3 @@ };

sax.ondoctype = (doctype) => {
/**
* @type {XastDoctype}
*/
/** @type {import('./types.js').XastDoctype} */
const node = {

@@ -146,5 +122,3 @@ type: 'doctype',

sax.onprocessinginstruction = (data) => {
/**
* @type {XastInstruction}
*/
/** @type {import('./types.js').XastInstruction} */
const node = {

@@ -159,5 +133,3 @@ type: 'instruction',

sax.oncomment = (comment) => {
/**
* @type {XastComment}
*/
/** @type {import('./types.js').XastComment} */
const node = {

@@ -171,5 +143,3 @@ type: 'comment',

sax.oncdata = (cdata) => {
/**
* @type {XastCdata}
*/
/** @type {import('./types.js').XastCdata} */
const node = {

@@ -183,6 +153,4 @@ type: 'cdata',

sax.onopentag = (data) => {
/**
* @type {XastElement}
*/
let element = {
/** @type {import('./types.js').XastElement} */
const element = {
type: 'element',

@@ -205,5 +173,3 @@ name: data.name,

if (textElems.has(current.name)) {
/**
* @type {XastText}
*/
/** @type {import('./types.js').XastText} */
const node = {

@@ -215,5 +181,3 @@ type: 'text',

} else if (/\S/.test(text)) {
/**
* @type {XastText}
*/
/** @type {import('./types.js').XastText} */
const node = {

@@ -220,0 +184,0 @@ type: 'text',

@@ -0,11 +1,16 @@

/**
* @fileoverview Based on https://www.w3.org/TR/SVG11/paths.html#PathDataBNF.
*/
import { removeLeadingZero, toFixed } from './svgo/tools.js';
/**
* @typedef {import('./types.js').PathDataItem} PathDataItem
* @typedef {import('./types.js').PathDataCommand} PathDataCommand
* @typedef {'none' | 'sign' | 'whole' | 'decimal_point' | 'decimal' | 'e' | 'exponent_sign' | 'exponent'} ReadNumberState
*
* @typedef StringifyPathDataOptions
* @property {ReadonlyArray<import('./types.js').PathDataItem>} pathData
* @property {number=} precision
* @property {boolean=} disableSpaceAfterFlags
*/
// Based on https://www.w3.org/TR/SVG11/paths.html#PathDataBNF
const argsCountPerCommand = {

@@ -35,3 +40,4 @@ M: 2,

/**
* @type {(c: string) => c is PathDataCommand}
* @param {string} c
* @returns {c is import('./types.js').PathDataCommand}
*/

@@ -63,3 +69,5 @@ const isCommand = (c) => {

/**
* @type {(string: string, cursor: number) => [number, ?number]}
* @param {string} string
* @param {number} cursor
* @returns {[number, ?number]}
*/

@@ -69,3 +77,4 @@ const readNumber = (string, cursor) => {

let value = '';
let state = /** @type {ReadNumberState} */ ('none');
/** @type {ReadNumberState} */
let state = 'none';
for (; i < string.length; i += 1) {

@@ -133,12 +142,8 @@ const c = string[i];

* @param {string} string
* @returns {PathDataItem[]}
* @returns {import('./types.js').PathDataItem[]}
*/
export const parsePathData = (string) => {
/**
* @type {PathDataItem[]}
*/
/** @type {import('./types.js').PathDataItem[]} */
const pathData = [];
/**
* @type {?PathDataCommand}
*/
/** @type {?import('./types.js').PathDataCommand} */
let command = null;

@@ -239,6 +244,5 @@ let args = /** @type {number[]} */ ([]);

/**
* @type {(number: number, precision?: number) => {
* roundedStr: string,
* rounded: number
* }}
* @param {number} number
* @param {number=} precision
* @returns {{ roundedStr: string, rounded: number }}
*/

@@ -260,8 +264,7 @@ const roundAndStringify = (number, precision) => {

*
* @type {(
* command: string,
* args: number[],
* precision?: number,
* disableSpaceAfterFlags?: boolean
* ) => string}
* @param {string} command
* @param {ReadonlyArray<number>} args
* @param {number=} precision
* @param {boolean=} disableSpaceAfterFlags
* @returns {string}
*/

@@ -298,10 +301,2 @@ const stringifyArgs = (command, args, precision, disableSpaceAfterFlags) => {

/**
* @typedef {{
* pathData: PathDataItem[];
* precision?: number;
* disableSpaceAfterFlags?: boolean;
* }} StringifyPathDataOptions
*/
/**
* @param {StringifyPathDataOptions} options

@@ -308,0 +303,0 @@ * @returns {string}

import { textElems } from '../plugins/_collections.js';
/**
* @typedef {import('./types.js').XastParent} XastParent
* @typedef {import('./types.js').XastRoot} XastRoot
* @typedef {import('./types.js').XastElement} XastElement
* @typedef {import('./types.js').XastInstruction} XastInstruction
* @typedef {import('./types.js').XastDoctype} XastDoctype
* @typedef {import('./types.js').XastText} XastText
* @typedef {import('./types.js').XastCdata} XastCdata
* @typedef {import('./types.js').XastComment} XastComment
* @typedef {import('./types.js').StringifyOptions} StringifyOptions
* @typedef {{
* indent: string,
* textContext: ?XastElement,
* indentLevel: number,
* }} State
* @typedef {Required<StringifyOptions>} Options
* @typedef {Required<import('./types.js').StringifyOptions>} Options
*
* @typedef State
* @property {string} indent
* @property {?import('./types.js').XastElement} textContext
* @property {number} indentLevel
*/
/**
* @type {(char: string) => string}
* @param {string} char
* @returns {string}
*/

@@ -68,10 +60,10 @@ const encodeEntity = (char) => {

/**
* convert XAST to SVG string
* Converts XAST to SVG string.
*
* @type {(data: XastRoot, config: StringifyOptions) => string}
* @param {import('./types.js').XastRoot} data
* @param {import('./types.js').StringifyOptions=} userOptions
* @returns {string}
*/
export const stringifySvg = (data, userOptions = {}) => {
/**
* @type {Options}
*/
/** @type {Options} */
const config = { ...defaults, ...userOptions };

@@ -85,5 +77,3 @@ const indent = config.indent;

}
/**
* @type {State}
*/
/** @type {State} */
const state = {

@@ -113,28 +103,32 @@ indent: newIndent,

/**
* @type {(node: XastParent, config: Options, state: State) => string}
* @param {import('./types.js').XastParent} data
* @param {Options} config
* @param {State} state
* @returns {string}
*/
const stringifyNode = (data, config, state) => {
let svg = '';
state.indentLevel += 1;
state.indentLevel++;
for (const item of data.children) {
if (item.type === 'element') {
svg += stringifyElement(item, config, state);
switch (item.type) {
case 'element':
svg += stringifyElement(item, config, state);
break;
case 'text':
svg += stringifyText(item, config, state);
break;
case 'doctype':
svg += stringifyDoctype(item, config);
break;
case 'instruction':
svg += stringifyInstruction(item, config);
break;
case 'comment':
svg += stringifyComment(item, config);
break;
case 'cdata':
svg += stringifyCdata(item, config, state);
}
if (item.type === 'text') {
svg += stringifyText(item, config, state);
}
if (item.type === 'doctype') {
svg += stringifyDoctype(item, config);
}
if (item.type === 'instruction') {
svg += stringifyInstruction(item, config);
}
if (item.type === 'comment') {
svg += stringifyComment(item, config);
}
if (item.type === 'cdata') {
svg += stringifyCdata(item, config, state);
}
}
state.indentLevel -= 1;
state.indentLevel--;
return svg;

@@ -144,5 +138,7 @@ };

/**
* create indent string in accordance with the current node level.
* Create indent string in accordance with the current node level.
*
* @type {(config: Options, state: State) => string}
* @param {Options} config
* @param {State} state
* @returns {string}
*/

@@ -158,3 +154,5 @@ const createIndent = (config, state) => {

/**
* @type {(node: XastDoctype, config: Options) => string}
* @param {import('./types.js').XastDoctype} node
* @param {Options} config
* @returns {string}
*/

@@ -166,3 +164,5 @@ const stringifyDoctype = (node, config) => {

/**
* @type {(node: XastInstruction, config: Options) => string}
* @param {import('./types.js').XastInstruction} node
* @param {Options} config
* @returns {string}
*/

@@ -176,3 +176,5 @@ const stringifyInstruction = (node, config) => {

/**
* @type {(node: XastComment, config: Options) => string}
* @param {import('./types.js').XastComment} node
* @param {Options} config
* @returns {string}
*/

@@ -184,3 +186,6 @@ const stringifyComment = (node, config) => {

/**
* @type {(node: XastCdata, config: Options, state: State) => string}
* @param {import('./types.js').XastCdata} node
* @param {Options} config
* @param {State} state
* @returns {string}
*/

@@ -197,3 +202,6 @@ const stringifyCdata = (node, config, state) => {

/**
* @type {(node: XastElement, config: Options, state: State) => string}
* @param {import('./types.js').XastElement} node
* @param {Options} config
* @param {State} state
* @returns {string}
*/

@@ -211,59 +219,61 @@ const stringifyElement = (node, config, state) => {

);
} else {
return (
createIndent(config, state) +
config.tagShortStart +
node.name +
stringifyAttributes(node, config) +
config.tagOpenEnd +
config.tagCloseStart +
node.name +
config.tagCloseEnd
);
}
// non-empty element
} else {
let tagOpenStart = config.tagOpenStart;
let tagOpenEnd = config.tagOpenEnd;
let tagCloseStart = config.tagCloseStart;
let tagCloseEnd = config.tagCloseEnd;
let openIndent = createIndent(config, state);
let closeIndent = createIndent(config, state);
if (state.textContext) {
tagOpenStart = defaults.tagOpenStart;
tagOpenEnd = defaults.tagOpenEnd;
tagCloseStart = defaults.tagCloseStart;
tagCloseEnd = defaults.tagCloseEnd;
openIndent = '';
} else if (textElems.has(node.name)) {
tagOpenEnd = defaults.tagOpenEnd;
tagCloseStart = defaults.tagCloseStart;
closeIndent = '';
state.textContext = node;
}
const children = stringifyNode(node, config, state);
if (state.textContext === node) {
state.textContext = null;
}
return (
openIndent +
tagOpenStart +
createIndent(config, state) +
config.tagShortStart +
node.name +
stringifyAttributes(node, config) +
tagOpenEnd +
children +
closeIndent +
tagCloseStart +
config.tagOpenEnd +
config.tagCloseStart +
node.name +
tagCloseEnd
config.tagCloseEnd
);
}
// non-empty element
let tagOpenStart = config.tagOpenStart;
let tagOpenEnd = config.tagOpenEnd;
let tagCloseStart = config.tagCloseStart;
let tagCloseEnd = config.tagCloseEnd;
let openIndent = createIndent(config, state);
let closeIndent = createIndent(config, state);
if (state.textContext) {
tagOpenStart = defaults.tagOpenStart;
tagOpenEnd = defaults.tagOpenEnd;
tagCloseStart = defaults.tagCloseStart;
tagCloseEnd = defaults.tagCloseEnd;
openIndent = '';
} else if (textElems.has(node.name)) {
tagOpenEnd = defaults.tagOpenEnd;
tagCloseStart = defaults.tagCloseStart;
closeIndent = '';
state.textContext = node;
}
const children = stringifyNode(node, config, state);
if (state.textContext === node) {
state.textContext = null;
}
return (
openIndent +
tagOpenStart +
node.name +
stringifyAttributes(node, config) +
tagOpenEnd +
children +
closeIndent +
tagCloseStart +
node.name +
tagCloseEnd
);
};
/**
* @type {(node: XastElement, config: Options) => string}
* @param {import('./types.js').XastElement} node
* @param {Options} config
* @returns {string}
*/

@@ -273,3 +283,4 @@ const stringifyAttributes = (node, config) => {

for (const [name, value] of Object.entries(node.attributes)) {
// TODO remove attributes without values support in v3
attrs += ' ' + name;
if (value !== undefined) {

@@ -279,5 +290,3 @@ const encodedValue = value

.replace(config.regValEntities, config.encodeEntity);
attrs += ' ' + name + config.attrStart + encodedValue + config.attrEnd;
} else {
attrs += ' ' + name;
attrs += config.attrStart + encodedValue + config.attrEnd;
}

@@ -289,3 +298,6 @@ }

/**
* @type {(node: XastText, config: Options, state: State) => string}
* @param {import('./types.js').XastText} node
* @param {Options} config
* @param {State} state
* @returns {string}
*/

@@ -292,0 +304,0 @@ const stringifyText = (node, config, state) => {

import * as csstree from 'css-tree';
import * as csswhat from 'css-what';
import { syntax } from 'csso';
import { visit, matches } from './xast.js';
import { matches, visit } from './xast.js';
import {

@@ -11,24 +11,11 @@ attrsGroups,

/**
* @typedef {import('css-tree').Rule} CsstreeRule
* @typedef {import('./types.js').Specificity} Specificity
* @typedef {import('./types.js').Stylesheet} Stylesheet
* @typedef {import('./types.js').StylesheetRule} StylesheetRule
* @typedef {import('./types.js').StylesheetDeclaration} StylesheetDeclaration
* @typedef {import('./types.js').ComputedStyles} ComputedStyles
* @typedef {import('./types.js').XastRoot} XastRoot
* @typedef {import('./types.js').XastElement} XastElement
* @typedef {import('./types.js').XastParent} XastParent
* @typedef {import('./types.js').XastChild} XastChild
*/
const csstreeWalkSkip = csstree.walk.skip;
/**
* @type {(ruleNode: CsstreeRule, dynamic: boolean) => StylesheetRule[]}
* @param {import('css-tree').Rule} ruleNode
* @param {boolean} dynamic
* @returns {import('./types.js').StylesheetRule[]}
*/
const parseRule = (ruleNode, dynamic) => {
/**
* @type {StylesheetDeclaration[]}
*/
/** @type {import('./types.js').StylesheetDeclaration[]} */
const declarations = [];

@@ -46,3 +33,3 @@ // collect declarations

/** @type {StylesheetRule[]} */
/** @type {import('./types.js').StylesheetRule[]} */
const rules = [];

@@ -73,6 +60,8 @@ csstree.walk(ruleNode.prelude, (node) => {

/**
* @type {(css: string, dynamic: boolean) => StylesheetRule[]}
* @param {string} css
* @param {boolean} dynamic
* @returns {import('./types.js').StylesheetRule[]}
*/
const parseStylesheet = (css, dynamic) => {
/** @type {StylesheetRule[]} */
/** @type {import('./types.js').StylesheetRule[]} */
const rules = [];

@@ -112,6 +101,7 @@ const ast = csstree.parse(css, {

/**
* @type {(css: string) => StylesheetDeclaration[]}
* @param {string} css
* @returns {import('./types.js').StylesheetDeclaration[]}
*/
const parseStyleDeclarations = (css) => {
/** @type {StylesheetDeclaration[]} */
/** @type {import('./types.js').StylesheetDeclaration[]} */
const declarations = [];

@@ -135,8 +125,9 @@ const ast = csstree.parse(css, {

/**
* @param {Stylesheet} stylesheet
* @param {XastElement} node
* @returns {ComputedStyles}
* @param {import('./types.js').Stylesheet} stylesheet
* @param {import('./types.js').XastElement} node
* @param {Map<import('./types.js').XastNode, import('./types.js').XastParent>=} parents
* @returns {import('./types.js').ComputedStyles}
*/
const computeOwnStyle = (stylesheet, node) => {
/** @type {ComputedStyles} */
const computeOwnStyle = (stylesheet, node, parents) => {
/** @type {import('./types.js').ComputedStyles} */
const computedStyle = {};

@@ -155,3 +146,3 @@ const importantStyles = new Map();

for (const { selector, declarations, dynamic } of stylesheet.rules) {
if (matches(node, selector)) {
if (matches(node, selector, parents)) {
for (const { name, value, important } of declarations) {

@@ -205,4 +196,4 @@ const computed = computedStyle[name];

*
* @param {Specificity} a
* @param {Specificity} b
* @param {import('./types.js').Specificity} a
* @param {import('./types.js').Specificity} b
* @returns {number}

@@ -223,8 +214,9 @@ */

/**
* @type {(root: XastRoot) => Stylesheet}
* @param {import('./types.js').XastRoot} root
* @returns {import('./types.js').Stylesheet}
*/
export const collectStylesheet = (root) => {
/** @type {StylesheetRule[]} */
/** @type {import('./types.js').StylesheetRule[]} */
const rules = [];
/** @type {Map<XastElement, XastParent>} */
/** @type {Map<import('./types.js').XastElement, import('./types.js').XastParent>} */
const parents = new Map();

@@ -264,12 +256,12 @@

/**
* @param {Stylesheet} stylesheet
* @param {XastElement} node
* @returns {ComputedStyles}
* @param {import('./types.js').Stylesheet} stylesheet
* @param {import('./types.js').XastElement} node
* @returns {import('./types.js').ComputedStyles}
*/
export const computeStyle = (stylesheet, node) => {
const { parents } = stylesheet;
const computedStyles = computeOwnStyle(stylesheet, node);
const computedStyles = computeOwnStyle(stylesheet, node, parents);
let parent = parents.get(node);
while (parent != null && parent.type !== 'root') {
const inheritedStyles = computeOwnStyle(stylesheet, parent);
const inheritedStyles = computeOwnStyle(stylesheet, parent, parents);
for (const [name, computed] of Object.entries(inheritedStyles)) {

@@ -292,7 +284,7 @@ if (

*
* Classes and IDs are generated as attribute selectors, so you can check for
* if a `.class` or `#id` is included by passing `name=class` or `name=id`
* Classes and IDs are generated as attribute selectors, so you can check for if
* a `.class` or `#id` is included by passing `name=class` or `name=id`
* respectively.
*
* @param {csstree.ListItem<csstree.CssNode>|string} selector
* @param {csstree.ListItem<csstree.CssNode> | string} selector
* @param {string} name

@@ -299,0 +291,0 @@ * @param {?string} value

import os from 'os';
import fs from 'fs';
import { pathToFileURL } from 'url';
import fs from 'fs/promises';
import path from 'path';
import {
VERSION,
optimize as optimizeAgnostic,
builtinPlugins,
querySelector,
querySelectorAll,
_collections,
} from './svgo.js';
import * as svgo from './svgo.js';
/**
* @param {string} configFile
* @returns {Promise<import('./types.js').Config>}
*/
const importConfig = async (configFile) => {
// dynamic import expects file url instead of path and may fail
// when windows path is provided
const imported = await import(pathToFileURL(configFile));
const imported = await import(path.resolve(configFile));
const config = imported.default;

@@ -26,5 +20,9 @@

/**
* @param {string} file
* @returns {Promise<boolean>}
*/
const isFile = async (file) => {
try {
const stats = await fs.promises.stat(file);
const stats = await fs.stat(file);
return stats.isFile();

@@ -36,16 +34,17 @@ } catch {

export {
VERSION,
builtinPlugins,
querySelector,
querySelectorAll,
_collections,
};
export * from './svgo.js';
/**
* If you write a tool on top of svgo you might need a way to load svgo config.
* You can also specify relative or absolute path and customize current working
* directory.
*
* @type {<T extends string>(configFile: T | null, cwd?: string) => Promise<T extends string ? import('./svgo.js').Config : import('./svgo.js').Config | null>}
*/
export const loadConfig = async (configFile, cwd = process.cwd()) => {
if (configFile != null) {
if (path.isAbsolute(configFile)) {
return await importConfig(configFile);
return importConfig(configFile);
} else {
return await importConfig(path.join(cwd, configFile));
return importConfig(path.join(cwd, configFile));
}

@@ -58,14 +57,15 @@ }

if (await isFile(js)) {
return await importConfig(js);
return importConfig(js);
}
const mjs = path.join(dir, 'svgo.config.mjs');
if (await isFile(mjs)) {
return await importConfig(mjs);
return importConfig(mjs);
}
const cjs = path.join(dir, 'svgo.config.cjs');
if (await isFile(cjs)) {
return await importConfig(cjs);
return importConfig(cjs);
}
const parent = path.dirname(dir);
if (dir === parent) {
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/33912
return null;

@@ -77,2 +77,9 @@ }

/**
* The core of SVGO.
*
* @param {string} input
* @param {import('./svgo.js').Config=} config
* @returns {import('./svgo.js').Output}
*/
export const optimize = (input, config) => {

@@ -85,3 +92,3 @@ if (config == null) {

}
return optimizeAgnostic(input, {
return svgo.optimize(input, {
...config,

@@ -95,11 +102,1 @@ js2svg: {

};
export default {
VERSION,
builtinPlugins,
loadConfig,
optimize,
querySelector,
querySelectorAll,
_collections,
};

@@ -0,16 +1,12 @@

import { builtinPlugins } from './builtin.js';
import { encodeSVGDatauri } from './svgo/tools.js';
import { invokePlugins } from './svgo/plugins.js';
import { mapNodesToParents, querySelector, querySelectorAll } from './xast.js';
import { parseSvg } from './parser.js';
import { stringifySvg } from './stringifier.js';
import { builtin } from './builtin.js';
import { invokePlugins } from './svgo/plugins.js';
import { encodeSVGDatauri } from './svgo/tools.js';
import { VERSION } from './version.js';
import { querySelector, querySelectorAll } from './xast.js';
import _collections from '../plugins/_collections.js';
import * as _collections from '../plugins/_collections.js';
/**
* @typedef {import('./svgo.js').BuiltinPluginOrPreset<?, ?>} BuiltinPluginOrPreset
*/
const pluginsMap = new Map();
for (const plugin of builtin) {
for (const plugin of builtinPlugins) {
pluginsMap.set(plugin.name, plugin);

@@ -21,3 +17,3 @@ }

* @param {string} name
* @returns {BuiltinPluginOrPreset}
* @returns {import('./types.js').BuiltinPluginOrPreset<?, ?>}
*/

@@ -35,2 +31,6 @@ function getPlugin(name) {

/**
* @param {string | import('./types.js').PluginConfig} plugin
* @returns {?import('./types.js').PluginConfig}
*/
const resolvePluginConfig = (plugin) => {

@@ -54,2 +54,3 @@ if (typeof plugin === 'string') {

// use custom plugin implementation
// @ts-expect-error Checking for CustomPlugin with the presence of fn
let fn = plugin.fn;

@@ -73,10 +74,11 @@ if (fn == null) {

export {
VERSION,
builtin as builtinPlugins,
querySelector,
querySelectorAll,
_collections,
};
export * from './types.js';
/**
* The core of SVGO.
*
* @param {string} input
* @param {import('./types.js').Config=} config
* @returns {import('./types.js').Output}
*/
export const optimize = (input, config) => {

@@ -114,2 +116,4 @@ if (config == null) {

}
/** @type {import('./types.js').Config} */
const globalOverrides = {};

@@ -136,6 +140,6 @@ if (config.floatPrecision != null) {

export default {
export {
VERSION,
optimize,
builtinPlugins: builtin,
builtinPlugins,
mapNodesToParents,
querySelector,

@@ -142,0 +146,0 @@ querySelectorAll,

@@ -5,16 +5,11 @@ import fs from 'fs';

import { fileURLToPath } from 'url';
import { encodeSVGDatauri, decodeSVGDatauri } from './tools.js';
import { decodeSVGDatauri, encodeSVGDatauri } from './tools.js';
import { loadConfig, optimize } from '../svgo-node.js';
import { builtin } from '../builtin.js';
import { builtinPlugins } from '../builtin.js';
import { SvgoParserError } from '../parser.js';
/**
* @typedef {import('commander').Command} Command
*/
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const pkgPath = path.join(__dirname, '../../package.json');
const PKG = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
const PKG = JSON.parse(await fs.promises.readFile(pkgPath, 'utf-8'));
const regSVGFile = /\.svg$/i;
/**

@@ -34,3 +29,3 @@ * Synchronously check if path is a directory. Tolerant to errors like ENOENT.

/**
* @param {Command} program
* @param {import('commander').Command} program
*/

@@ -94,19 +89,14 @@ export default function makeProgram(program) {

/**
* @param {ReadonlyArray<string>} args
* @param {any} opts
* @param {import('commander').Command} command
* @returns
*/
async function action(args, opts, command) {
var input = opts.input || args;
var output = opts.output;
var config = {};
const input = opts.input || args;
let output = opts.output;
/** @type {any} */
let config = {};
if (opts.precision != null) {
const number = Number.parseInt(opts.precision, 10);
if (Number.isNaN(number)) {
console.error(
"error: option '-p, --precision' argument must be an integer number",
);
process.exit(1);
} else {
opts.precision = number;
}
}
if (opts.datauri != null) {

@@ -161,10 +151,5 @@ if (

if (
typeof process == 'object' &&
process.versions &&
process.versions.node &&
PKG &&
PKG.engines.node
) {
var nodeVersion = String(PKG.engines.node).match(/\d*(\.\d+)*/)[0];
if (process?.versions?.node && PKG.engines.node) {
// @ts-expect-error We control this and ensure it is never null.
const nodeVersion = String(PKG.engines.node).match(/\d*(\.\d+)*/)[0];
if (parseFloat(process.versions.node) < parseFloat(nodeVersion)) {

@@ -195,3 +180,3 @@ throw Error(

config.exclude = opts.exclude
? opts.exclude.map((pattern) => RegExp(pattern))
? opts.exclude.map((/** @type {string} */ pattern) => RegExp(pattern))
: [];

@@ -201,4 +186,11 @@

if (opts.precision != null) {
var precision = Math.min(Math.max(0, opts.precision), 20);
config.floatPrecision = precision;
const number = Number.parseInt(opts.precision, 10);
if (Number.isNaN(number)) {
console.error(
"error: option '-p, --precision' argument must be an integer number",
);
process.exit(1);
} else {
config.floatPrecision = Math.min(Math.max(0, number), 20);
}
}

@@ -236,4 +228,4 @@

if (output.length == 1 && checkIsDir(output[0])) {
var dir = output[0];
for (var i = 0; i < input.length; i++) {
const dir = output[0];
for (let i = 0; i < input.length; i++) {
output[i] = checkIsDir(input[i])

@@ -259,3 +251,3 @@ ? input[i]

if (opts.folder) {
var outputFolder = (output && output[0]) || opts.folder;
const outputFolder = (output && output[0]) || opts.folder;
await optimizeFolder(config, opts.folder, outputFolder);

@@ -269,4 +261,4 @@ }

return new Promise((resolve, reject) => {
var data = '',
file = output[0];
let data = '';
const file = output[0];

@@ -282,3 +274,5 @@ process.stdin

await Promise.all(
input.map((file, n) => optimizeFile(config, file, output[n])),
input.map((/** @type {string} */ file, /** @type {number} */ n) =>
optimizeFile(config, file, output[n]),
),
);

@@ -289,3 +283,3 @@ }

} else if (opts.string) {
var data = decodeSVGDatauri(opts.string);
const data = decodeSVGDatauri(opts.string);

@@ -299,6 +293,6 @@ return processSVGData(config, null, data, output[0]);

*
* @param {Object} config options
* @param {any} config options
* @param {string} dir input directory
* @param {string} output output directory
* @return {Promise}
* @return {Promise<any>}
*/

@@ -317,11 +311,11 @@ function optimizeFolder(config, dir, output) {

*
* @param {Object} config options
* @param {any} config options
* @param {string} dir input directory
* @param {Array} files list of file names in the directory
* @param {ReadonlyArray<string>} files list of file names in the directory
* @param {string} output output directory
* @return {Promise}
* @return {Promise<any>}
*/
function processDirectory(config, dir, files, output) {
// take only *.svg files, recursively if necessary
var svgFilesDescriptions = getFilesDescriptions(config, dir, files, output);
const svgFilesDescriptions = getFilesDescriptions(config, dir, files, output);

@@ -346,7 +340,7 @@ return svgFilesDescriptions.length

*
* @param {Object} config options
* @param {any} config options
* @param {string} dir input directory
* @param {Array} files list of file names in the directory
* @param {ReadonlyArray<string>} files list of file names in the directory
* @param {string} output output directory
* @return {Array}
* @return {any[]}
*/

@@ -357,4 +351,6 @@ function getFilesDescriptions(config, dir, files, output) {

(name) =>
regSVGFile.test(name) &&
!config.exclude.some((regExclude) => regExclude.test(name)),
name.slice(-4).toLowerCase() === '.svg' &&
!config.exclude.some((/** @type {RegExp} */ regExclude) =>
regExclude.test(name),
),
)

@@ -366,21 +362,22 @@ .map((name) => ({

return config.recursive
? [].concat(
filesInThisFolder,
files
.filter((name) => checkIsDir(path.resolve(dir, name)))
.map((subFolderName) => {
const subFolderPath = path.resolve(dir, subFolderName);
const subFolderFiles = fs.readdirSync(subFolderPath);
const subFolderOutput = path.resolve(output, subFolderName);
return getFilesDescriptions(
config,
subFolderPath,
subFolderFiles,
subFolderOutput,
);
})
.reduce((a, b) => [].concat(a, b), []),
)
: filesInThisFolder;
if (!config.recursive) {
return filesInThisFolder;
}
return filesInThisFolder.concat(
files
.filter((name) => checkIsDir(path.resolve(dir, name)))
.map((subFolderName) => {
const subFolderPath = path.resolve(dir, subFolderName);
const subFolderFiles = fs.readdirSync(subFolderPath);
const subFolderOutput = path.resolve(output, subFolderName);
return getFilesDescriptions(
config,
subFolderPath,
subFolderFiles,
subFolderOutput,
);
})
.reduce((a, b) => a.concat(b), []),
);
}

@@ -391,6 +388,6 @@

*
* @param {Object} config options
* @param {any} config options
* @param {string} file
* @param {string} output
* @return {Promise}
* @return {Promise<any>}
*/

@@ -407,11 +404,12 @@ function optimizeFile(config, file, output) {

*
* @param {Object} config options
* @param {any} config options
* @param {?{ path: string }} info
* @param {string} data SVG content to optimize
* @param {string} output where to write optimized file
* @param {string} [input] input file name (being used if output is a directory)
* @return {Promise}
* @param {any=} input input file name (being used if output is a directory)
* @return {Promise<any>}
*/
function processSVGData(config, info, data, output, input) {
var startTime = Date.now(),
prevFileSize = Buffer.byteLength(data, 'utf8');
const startTime = Date.now();
const prevFileSize = Buffer.byteLength(data, 'utf8');

@@ -422,3 +420,3 @@ let result;

} catch (error) {
if (error.name === 'SvgoParserError') {
if (error instanceof SvgoParserError) {
console.error(colors.red(error.toString()));

@@ -433,4 +431,4 @@ process.exit(1);

}
var resultFileSize = Buffer.byteLength(result.data, 'utf8'),
processingTime = Date.now() - startTime;
const resultFileSize = Buffer.byteLength(result.data, 'utf8');
const processingTime = Date.now() - startTime;

@@ -464,5 +462,5 @@ return writeOutput(input, output, result.data).then(

* @param {string} data data to write
* @return {Promise}
* @return {Promise<void>}
*/
function writeOutput(input, output, data) {
async function writeOutput(input, output, data) {
if (output == '-') {

@@ -473,3 +471,3 @@ process.stdout.write(data);

fs.mkdirSync(path.dirname(output), { recursive: true });
await fs.promises.mkdir(path.dirname(output), { recursive: true });

@@ -513,7 +511,7 @@ return fs.promises

*
* @param {Object} config
* @param {any} config
* @param {string} input
* @param {string} output
* @param {Error} error
* @return {Promise}
* @param {Error & { code: string, path: string }} error
* @return {Promise<void>}
*/

@@ -537,4 +535,4 @@ function checkOptimizeFileError(config, input, output, error) {

* @param {string} data
* @param {Error} error
* @return {Promise}
* @param {Error & { code: string }} error
* @return {Promise<void>}
*/

@@ -555,3 +553,3 @@ function checkWriteFileError(input, output, data, error) {

function showAvailablePlugins() {
const list = builtin
const list = builtinPlugins
.map((plugin) => ` [ ${colors.green(plugin.name)} ] ${plugin.description}`)

@@ -558,0 +556,0 @@ .join('\n');

@@ -0,1 +1,2 @@

/** @type {Required<import('css-select').Options<import('../types.js').XastNode & { children?: any }, import('../types.js').XastElement>>['adapter']['isTag']} */
const isTag = (node) => {

@@ -5,12 +6,10 @@ return node.type === 'element';

/** @type {Required<import('css-select').Options<import('../types.js').XastNode & { children?: any }, import('../types.js').XastElement>>['adapter']['existsOne']} */
const existsOne = (test, elems) => {
return elems.some((elem) => {
if (isTag(elem)) {
return test(elem) || existsOne(test, getChildren(elem));
} else {
return false;
}
return isTag(elem) && (test(elem) || existsOne(test, getChildren(elem)));
});
};
/** @type {Required<import('css-select').Options<import('../types.js').XastNode & { children?: any }, import('../types.js').XastElement>>['adapter']['getAttributeValue']} */
const getAttributeValue = (elem, name) => {

@@ -20,2 +19,3 @@ return elem.attributes[name];

/** @type {Required<import('css-select').Options<import('../types.js').XastNode & { children?: any }, import('../types.js').XastElement>>['adapter']['getChildren']} */
const getChildren = (node) => {

@@ -25,2 +25,3 @@ return node.children || [];

/** @type {Required<import('css-select').Options<import('../types.js').XastNode & { children?: any }, import('../types.js').XastElement>>['adapter']['getName']} */
const getName = (elemAst) => {

@@ -30,13 +31,5 @@ return elemAst.name;

const getParent = (node) => {
return node.parentNode || null;
};
const getSiblings = (elem) => {
var parent = getParent(elem);
return parent ? getChildren(parent) : [];
};
/** @type {Required<import('css-select').Options<import('../types.js').XastNode & { children?: any }, import('../types.js').XastElement>>['adapter']['getText']} */
const getText = (node) => {
if (node.children[0].type === 'text' && node.children[0].type === 'cdata') {
if (node.children[0].type === 'text' || node.children[0].type === 'cdata') {
return node.children[0].value;

@@ -47,2 +40,3 @@ }

/** @type {Required<import('css-select').Options<import('../types.js').XastNode & { children?: any }, import('../types.js').XastElement>>['adapter']['hasAttrib']} */
const hasAttrib = (elem, name) => {

@@ -52,30 +46,3 @@ return elem.attributes[name] !== undefined;

const removeSubsets = (nodes) => {
let idx = nodes.length;
let node;
let ancestor;
let replace;
// Check if each node (or one of its ancestors) is already contained in the
// array.
while (--idx > -1) {
node = ancestor = nodes[idx];
// Temporarily remove the node under consideration
nodes[idx] = null;
replace = true;
while (ancestor) {
if (nodes.includes(ancestor)) {
replace = false;
nodes.splice(idx, 1);
break;
}
ancestor = getParent(ancestor);
}
// If the node has been found to be unique, re-insert it.
if (replace) {
nodes[idx] = node;
}
}
return nodes;
};
/** @type {Required<import('css-select').Options<import('../types.js').XastNode & { children?: any }, import('../types.js').XastElement>>['adapter']['findAll']} */
const findAll = (test, elems) => {

@@ -94,2 +61,3 @@ const result = [];

/** @type {Required<import('css-select').Options<import('../types.js').XastNode & { children?: any }, import('../types.js').XastElement>>['adapter']['findOne']} */
const findOne = (test, elems) => {

@@ -110,17 +78,67 @@ for (const elem of elems) {

const svgoCssSelectAdapter = {
isTag,
existsOne,
getAttributeValue,
getChildren,
getName,
getParent,
getSiblings,
getText,
hasAttrib,
removeSubsets,
findAll,
findOne,
};
/**
* @param {Map<import('../types.js').XastNode, import('../types.js').XastParent>} parents
* @returns {Required<import('css-select').Options<import('../types.js').XastNode & { children?: any }, import('../types.js').XastElement>>['adapter']}
*/
export function createAdapter(parents) {
/** @type {Required<import('css-select').Options<import('../types.js').XastNode & { children?: any }, import('../types.js').XastElement>>['adapter']['getParent']} */
const getParent = (node) => {
return parents.get(node) || null;
};
export default svgoCssSelectAdapter;
/**
* @param {any} elem
* @returns {any}
*/
const getSiblings = (elem) => {
const parent = getParent(elem);
return parent ? getChildren(parent) : [];
};
/**
* @param {any} nodes
* @returns {any}
*/
const removeSubsets = (nodes) => {
let idx = nodes.length;
let node;
let ancestor;
let replace;
// Check if each node (or one of its ancestors) is already contained in the
// array.
while (--idx > -1) {
node = ancestor = nodes[idx];
// Temporarily remove the node under consideration
nodes[idx] = null;
replace = true;
while (ancestor) {
if (nodes.includes(ancestor)) {
replace = false;
nodes.splice(idx, 1);
break;
}
ancestor = getParent(ancestor);
}
// If the node has been found to be unique, re-insert it.
if (replace) {
nodes[idx] = node;
}
}
return nodes;
};
return {
isTag,
existsOne,
getAttributeValue,
getChildren,
getName,
getParent,
getSiblings,
getText,
hasAttrib,
removeSubsets,
findAll,
findOne,
};
}
import { visit } from '../xast.js';
/**
* @typedef {import('../svgo').BuiltinPlugin<string, Object>} BuiltinPlugin
* @typedef {import('../svgo').BuiltinPluginOrPreset<?, ?>} BuiltinPreset
*/
/**
* Plugins engine.

@@ -13,6 +8,7 @@ *

*
* @param {Object} ast input ast
* @param {Object} info extra information
* @param {Array} plugins plugins object from config
* @return {Object} output ast
* @param {import('../types.js').XastNode} ast Input AST.
* @param {any} info Extra information.
* @param {ReadonlyArray<any>} plugins Plugins property from config.
* @param {any} overrides
* @param {any} globalOverrides
*/

@@ -41,4 +37,5 @@ export const invokePlugins = (

/**
* @param {{ name: string, plugins: BuiltinPlugin[] }} arg0
* @returns {BuiltinPreset}
* @template {string} T
* @param {{ name: T, plugins: ReadonlyArray<import('../types.js').BuiltinPlugin<string, any>> }} arg0
* @returns {import('../types.js').BuiltinPluginOrPreset<T, any>}
*/

@@ -45,0 +42,0 @@ export const createPreset = ({ name, plugins }) => {

@@ -0,9 +1,10 @@

import { attrsGroups, referencesProps } from '../../plugins/_collections.js';
/**
* @typedef {import('../types.js').DataUri} DataUri
* @typedef {import('../types.js').PathDataCommand} PathDataCommand
* @typedef {import('../../lib/types.js').XastElement} XastElement
* @typedef CleanupOutDataParams
* @property {boolean=} noSpaceAfterFlags
* @property {boolean=} leadingZero
* @property {boolean=} negativeExtraSpace
*/
import { attrsGroups, referencesProps } from '../../plugins/_collections.js';
const regReferencesUrl = /\burl\((["'])?#(.+?)\1\)/g;

@@ -16,6 +17,8 @@ const regReferencesHref = /^#(.+?)$/;

*
* @type {(str: string, type?: DataUri) => string}
* @param {string} str
* @param {import('../types.js').DataUri=} type
* @returns {string}
*/
export const encodeSVGDatauri = (str, type) => {
var prefix = 'data:image/svg+xml';
let prefix = 'data:image/svg+xml';
if (!type || type === 'base64') {

@@ -38,12 +41,15 @@ // base64

*
* @type {(str: string) => string}
* @param {string} str
* @returns {string}
*/
export const decodeSVGDatauri = (str) => {
var regexp = /data:image\/svg\+xml(;charset=[^;,]*)?(;base64)?,(.*)/;
var match = regexp.exec(str);
const regexp = /data:image\/svg\+xml(;charset=[^;,]*)?(;base64)?,(.*)/;
const match = regexp.exec(str);
// plain string
if (!match) return str;
if (!match) {
return str;
}
var data = match[3];
const data = match[3];

@@ -64,10 +70,2 @@ if (match[2]) {

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

@@ -78,3 +76,6 @@ *

*
* @type {(data: number[], params: CleanupOutDataParams, command?: PathDataCommand) => string}
* @param {ReadonlyArray<number>} data
* @param {CleanupOutDataParams} params
* @param {import('../types.js').PathDataCommand=} command
* @returns {string}
*/

@@ -84,5 +85,3 @@ export const cleanupOutData = (data, params, command) => {

let delimiter;
/**
* @type {number}
*/
/** @type {number} */
let prev;

@@ -95,9 +94,13 @@

// no extra space in front of first number
if (i == 0) delimiter = '';
if (i == 0) {
delimiter = '';
}
// no extra space after 'arcto' command flags(large-arc and sweep flags)
// no extra space after arc command flags (large-arc and sweep flags)
// a20 60 45 0 1 30 20 → a20 60 45 0130 20
if (params.noSpaceAfterFlags && (command == 'A' || command == 'a')) {
var pos = i % 7;
if (pos == 4 || pos == 5) delimiter = '';
const pos = i % 7;
if (pos == 4 || pos == 5) {
delimiter = '';
}
}

@@ -155,3 +158,3 @@

*
* @param {XastElement} node Current node to check against.
* @param {import('../types.js').XastElement} node Current node to check against.
* @returns {boolean} If the current node contains scripts.

@@ -158,0 +161,0 @@ */

@@ -1,2 +0,7 @@

/** Version of SVGO. */
export const VERSION = '4.0.0-rc.1';
/**
* Version of SVGO.
*
* @type {string}
* @since 4.0.0
*/
export const VERSION = '4.0.0-rc.2';

@@ -1,37 +0,79 @@

import { selectAll, selectOne, is } from 'css-select';
import xastAdaptor from './svgo/css-select-adapter.js';
import { is, selectAll, selectOne } from 'css-select';
import { createAdapter } from './svgo/css-select-adapter.js';
/**
* @typedef {import('./types.js').XastNode} XastNode
* @typedef {import('./types.js').XastChild} XastChild
* @typedef {import('./types.js').XastParent} XastParent
* @typedef {import('./types.js').Visitor} Visitor
* @typedef {import('./svgo.ts').querySelector} querySelector
* @typedef {import('./svgo.ts').querySelectorAll} querySelectorAll
* @param {Map<import('./types.js').XastNode, import('./types.js').XastParent>} parents
* @returns {import('css-select').Options<import('./types.js').XastNode & { children?: any }, import('./types.js').XastElement>}
*/
function createCssSelectOptions(parents) {
return {
xmlMode: true,
adapter: createAdapter(parents),
};
}
const cssSelectOptions = {
xmlMode: true,
adapter: xastAdaptor,
};
/**
* Maps all nodes to their parent node recursively.
*
* @param {import('./types.js').XastParent} node
* @returns {Map<import('./types.js').XastNode, import('./types.js').XastParent>}
*/
export function mapNodesToParents(node) {
/** @type {Map<import('./types.js').XastNode, import('./types.js').XastParent>} */
const parents = new Map();
for (const child of node.children) {
parents.set(child, node);
visit(
child,
{
element: {
enter: (child, parent) => {
parents.set(child, parent);
},
},
},
node,
);
}
return parents;
}
/**
* @type {querySelectorAll}
* @param {import('./types.js').XastParent} node Element to query the children of.
* @param {string} selector CSS selector string.
* @param {Map<import('./types.js').XastNode, import('./types.js').XastParent>=} parents
* @returns {import('./types.js').XastChild[]} All matching elements.
*/
export const querySelectorAll = (node, selector) => {
return selectAll(selector, node, cssSelectOptions);
export const querySelectorAll = (
node,
selector,
parents = mapNodesToParents(node),
) => {
return selectAll(selector, node, createCssSelectOptions(parents));
};
/**
* @type {querySelector}
* @param {import('./types.js').XastParent} node Element to query the children of.
* @param {string} selector CSS selector string.
* @param {Map<import('./types.js').XastNode, import('./types.js').XastParent>=} parents
* @returns {?import('./types.js').XastChild} First match, or null if there was no match.
*/
export const querySelector = (node, selector) => {
return selectOne(selector, node, cssSelectOptions);
export const querySelector = (
node,
selector,
parents = mapNodesToParents(node),
) => {
return selectOne(selector, node, createCssSelectOptions(parents));
};
/**
* @type {(node: XastChild, selector: string) => boolean}
* @param {import('./types.js').XastElement} node
* @param {string} selector
* @param {Map<import('./types.js').XastNode, import('./types.js').XastParent>=} parents
* @returns {boolean}
*/
export const matches = (node, selector) => {
return is(node, selector, cssSelectOptions);
export const matches = (node, selector, parents = mapNodesToParents(node)) => {
return is(node, selector, createCssSelectOptions(parents));
};

@@ -42,8 +84,10 @@

/**
* @type {(node: XastNode, visitor: Visitor, parentNode?: any) => void}
* @param {import('./types.js').XastNode} node
* @param {import('./types.js').Visitor} visitor
* @param {any=} parentNode
*/
export const visit = (node, visitor, parentNode) => {
const callbacks = visitor[node.type];
if (callbacks && callbacks.enter) {
// @ts-ignore hard to infer
if (callbacks?.enter) {
// @ts-expect-error hard to infer
const symbol = callbacks.enter(node, parentNode);

@@ -56,3 +100,3 @@ if (symbol === visitSkip) {

if (node.type === 'root') {
// copy children array to not loose cursor when children is spliced
// copy children array to not lose cursor when children is spliced
for (const child of node.children) {

@@ -70,4 +114,4 @@ visit(child, visitor, node);

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

@@ -78,4 +122,4 @@ }

/**
* @param {XastChild} node
* @param {XastParent} parentNode
* @param {import('./types.js').XastChild} node
* @param {import('./types.js').XastParent} parentNode
*/

@@ -82,0 +126,0 @@ export const detachNodeFromParent = (node, parentNode) => {

{
"packageManager": "yarn@3.8.2",
"packageManager": "yarn@3.8.7",
"name": "svgo",
"version": "4.0.0-rc.1",
"version": "4.0.0-rc.2",
"description": "SVGO is a Node.js library and command-line application for optimizing vector images.",

@@ -53,5 +53,4 @@ "license": "MIT",

},
"main": "./lib/svgo-node.js",
"bin": "./bin/svgo.js",
"types": "./lib/svgo-node.d.ts",
"types": "./types/lib/svgo-node.d.ts",
"exports": {

@@ -61,39 +60,35 @@ ".": {

"require": "./dist/svgo-node.cjs",
"types": "./lib/svgo-node.d.ts"
"types": "./types/lib/svgo-node.d.ts"
},
"./browser": {
"import": "./dist/svgo.browser.js",
"types": "./lib/svgo.d.ts"
"types": "./types/lib/svgo.d.ts"
}
},
"typesVersions": {
"*": {
".": [
"./lib/svgo-node.d.ts"
],
"browser": [
"./lib/svgo.d.ts"
]
}
},
"files": [
"bin",
"dist",
"lib",
"plugins",
"dist",
"types",
"!**/*.test.js"
],
"engines": {
"node": ">=16.0.0"
"node": ">=16"
},
"scripts": {
"build": "node scripts/sync-version.js && rollup -c",
"typecheck": "tsc",
"build": "node scripts/sync-version.js && yarn build:bundles && yarn build:types",
"build:bundles": "yarn clean:build && rollup -c",
"build:types": "yarn clean:types && tsc && tsc -p tsconfig.build.json",
"lint": "eslint . && prettier --check .",
"fix": "eslint --fix . && prettier --write .",
"lint:fix": "eslint --fix . && prettier --write .",
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --maxWorkers=4 --coverage",
"test:bundles": "yarn build && node ./test/svgo.cjs && node ./test/browser.js",
"test:bundles": "yarn build:bundles && node ./test/svgo.cjs && node ./test/browser.js",
"test:types": "yarn build:types && tsc && tsd",
"test:regression": "node ./test/regression-extract.js && cross-env NO_DIFF=1 node ./test/regression.js",
"qa": "yarn typecheck && yarn lint && yarn test && yarn test:bundles && yarn test:regression",
"prepublishOnly": "rimraf dist && yarn build"
"qa": "yarn lint && yarn test:types && yarn test && yarn test:bundles && yarn test:regression",
"clean": "yarn clean:build && yarn clean:types",
"clean:build": "rimraf dist",
"clean:types": "rimraf types",
"prepublishOnly": "yarn clean && yarn build"
},

@@ -113,30 +108,33 @@ "jest": {

"css-select": "^5.1.0",
"css-tree": "^2.3.1",
"css-tree": "^3.0.1",
"css-what": "^6.1.0",
"csso": "^5.0.5",
"picocolors": "^1.0.0",
"picocolors": "^1.1.1",
"sax": "^1.4.1"
},
"devDependencies": {
"@eslint/js": "^9.3.0",
"@rollup/plugin-commonjs": "^25.0.7",
"@eslint/js": "^9.25.1",
"@jest/globals": "^29.7.0",
"@rollup/plugin-commonjs": "^26.0.3",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-terser": "^0.4.4",
"@types/css-tree": "^2.3.7",
"@types/css-tree": "^2.3.10",
"@types/csso": "^5.0.4",
"@types/jest": "^29.5.12",
"@types/node": "^20.12.11",
"@types/jest": "^29.5.14",
"@types/node": "^22.15.3",
"@types/sax": "^1.2.7",
"@types/tar-stream": "^3.1.3",
"cross-env": "^7.0.3",
"eslint": "^9.3.0",
"eslint": "^9.25.1",
"globals": "^14.0.0",
"jest": "^29.7.0",
"pixelmatch": "^5.3.0",
"playwright": "^1.44.0",
"pixelmatch": "^7.1.0",
"playwright": "^1.51.1",
"pngjs": "^7.0.0",
"prettier": "^3.2.5",
"rimraf": "^5.0.7",
"rollup": "^4.17.2",
"prettier": "^3.5.3",
"rimraf": "^5.0.10",
"rollup": "^4.22.4",
"tar-stream": "^3.1.7",
"typescript": "^5.4.5"
"tsd": "^0.32.0",
"typescript": "^5.8.3"
},

@@ -143,0 +141,0 @@ "resolutions": {

@@ -1,10 +0,7 @@

// https://www.w3.org/TR/SVG11/intro.html#Definitions
/**
* @typedef {import('../lib/svgo.ts')} svgo
* @fileoverview Based on https://www.w3.org/TR/SVG11/intro.html#Definitions.
*/
/**
* @type {Record<string, Set<string>>}
* @see svgo#_collections
* @type {Readonly<Record<string, Set<string>>>}
*/

@@ -109,3 +106,7 @@ export const elemsGroups = {

/**
* @see svgo#_collections
* Elements where adding or removing whitespace may affect rendering, metadata,
* or semantic meaning.
*
* @see https://developer.mozilla.org/docs/Web/HTML/Element/pre
* @type {Readonly<Set<string>>}
*/

@@ -115,3 +116,3 @@ export const textElems = new Set([...elemsGroups.textContent, 'pre', 'title']);

/**
* @see svgo#_collections
* @type {Readonly<Set<string>>}
*/

@@ -121,4 +122,4 @@ export const pathElems = new Set(['glyph', 'missing-glyph', 'path']);

/**
* @type {Record<string, Set<string>>}
* @see svgo#_collections
* @type {Readonly<Record<string, Set<string>>>}
* @see https://www.w3.org/TR/SVG11/intro.html#Definitions
*/

@@ -321,4 +322,3 @@ export const attrsGroups = {

/**
* @type {Record<string, Record<string, string>>}
* @see svgo#_collections
* @type {Readonly<Record<string, Record<string, string>>>}
*/

@@ -390,4 +390,4 @@ export const attrsGroupsDefaults = {

/**
* @type {Record<string, { safe?: Set<string>, unsafe?: Set<string> }>}
* @see svgo#_collections
* @type {Readonly<Record<string, { safe?: Set<string>; unsafe?: Set<string> }>>}
* @see https://www.w3.org/TR/SVG11/intro.html#Definitions
*/

@@ -411,3 +411,3 @@ export const attrsGroupsDeprecated = {

/**
* @type {Record<string, {
* @type {Readonly<Record<string, {
* attrsGroups: Set<string>,

@@ -422,4 +422,4 @@ * attrs?: Set<string>,

* content?: Set<string>,
* }>}
* @see svgo#_collections
* }>>}
* @see https://www.w3.org/TR/SVG11/eltindex.html
*/

@@ -2084,3 +2084,4 @@ export const elems = {

/**
* @see svgo#_collections
* @type {Readonly<Set<string>>}
* @see https://wiki.inkscape.org/wiki/index.php/Inkscape-specific_XML_attributes
*/

@@ -2113,3 +2114,4 @@ export const editorNamespaces = new Set([

/**
* @see svgo#_collections
* @type {Readonly<Set<string>>}
* @see https://www.w3.org/TR/SVG11/linking.html#processingIRI
*/

@@ -2130,3 +2132,4 @@ export const referencesProps = new Set([

/**
* @see svgo#_collections
* @type {Readonly<Set<string>>}
* @see https://www.w3.org/TR/SVG11/propidx.html
*/

@@ -2181,2 +2184,5 @@ export const inheritableAttrs = new Set([

/**
* @type {Readonly<Set<string>>}
*/
export const presentationNonInheritableGroupAttrs = new Set([

@@ -2194,4 +2200,4 @@ 'clip-path',

/**
* @type {Record<string, string>}
* @see svgo#_collections
* @type {Readonly<Record<string, string>>}
* @see https://www.w3.org/TR/SVG11/single-page.html#types-ColorKeywords
*/

@@ -2350,3 +2356,3 @@ export const colorsNames = {

/**
* @type {Record<string, string>}
* @type {Readonly<Record<string, string>>}
*/

@@ -2389,3 +2395,4 @@ export const colorsShortNames = {

/**
* @see svgo#_collections
* @type {Readonly<Set<string>>}
* @see https://www.w3.org/TR/SVG11/single-page.html#types-DataTypeColor
*/

@@ -2402,3 +2409,4 @@ export const colorsProps = new Set([

/**
* @see svgo#_collections
* @type {Readonly<Record<string, Set<string>>>}
* @see https://developer.mozilla.org/docs/Web/CSS/Pseudo-classes
*/

@@ -2415,3 +2423,3 @@ export const pseudoClasses = {

'in-range',
'indetermined',
'indeterminate',
'invalid',

@@ -2462,19 +2470,1 @@ 'optional',

};
export default {
elemsGroups,
textElems,
pathElems,
attrsGroups,
attrsGroupsDefaults,
attrsGroupsDeprecated,
elems,
editorNamespaces,
referencesProps,
inheritableAttrs,
presentationNonInheritableGroupAttrs,
colorsNames,
colorsShortNames,
colorsProps,
pseudoClasses,
};
import { parsePathData, stringifyPathData } from '../lib/path.js';
/**
* @typedef {import('../lib/types.js').XastElement} XastElement
* @typedef {import('../lib/types.js').PathDataItem} PathDataItem
* @typedef Js2PathParams
* @property {number=} floatPrecision
* @property {boolean=} noSpaceAfterFlags
*
* @typedef Point
* @property {number[][]} list
* @property {number} minX
* @property {number} minY
* @property {number} maxX
* @property {number} maxY
*
* @typedef Points
* @property {Point[]} list
* @property {number} minX
* @property {number} minY
* @property {number} maxX
* @property {number} maxY
*/
/**
* @type {[number, number]}
*/
var prevCtrlPoint;
/** @type {[number, number]} */
let prevCtrlPoint;

@@ -16,10 +29,12 @@ /**

*
* @type {(path: XastElement) => PathDataItem[]}
* @param {import('../lib/types.js').XastElement} path
* @returns {import('../lib/types.js').PathDataItem[]}
*/
export const path2js = (path) => {
// @ts-ignore legacy
if (path.pathJS) return path.pathJS;
/**
* @type {PathDataItem[]}
*/
// @ts-expect-error legacy
if (path.pathJS) {
// @ts-expect-error legacy
return path.pathJS;
}
/** @type {import('../lib/types.js').PathDataItem[]} */
const pathData = []; // JS representation of the path data

@@ -34,3 +49,3 @@ const newPathData = parsePathData(path.attributes.d);

}
// @ts-ignore legacy
// @ts-expect-error legacy
path.pathJS = pathData;

@@ -43,12 +58,10 @@ return pathData;

*
* @type {(data: PathDataItem[]) => PathDataItem[]}
*
* @param {ReadonlyArray<import('../lib/types.js').PathDataItem>} data
* @returns {import('../lib/types.js').PathDataItem[]}
*/
const convertRelativeToAbsolute = (data) => {
/**
* @type {PathDataItem[]}
*/
/** @type {import('../lib/types.js').PathDataItem[]} */
const newData = [];
let start = [0, 0];
let cursor = [0, 0];
const start = [0, 0];
const cursor = [0, 0];

@@ -176,12 +189,10 @@ for (let { command, args } of data) {

/**
* @typedef {{ floatPrecision?: number, noSpaceAfterFlags?: boolean }} Js2PathParams
*/
/**
* Convert path array to string.
*
* @type {(path: XastElement, data: PathDataItem[], params: Js2PathParams) => void}
* @param {import('../lib/types.js').XastElement} path
* @param {ReadonlyArray<import('../lib/types.js').PathDataItem>} data
* @param {Js2PathParams} params
*/
export const js2path = function (path, data, params) {
// @ts-ignore legacy
// @ts-expect-error legacy
path.pathJS = data;

@@ -215,3 +226,5 @@

/**
* @type {(dest: number[], source: number[]) => number[]}
* @param {number[]} dest
* @param {ReadonlyArray<number>} source
* @returns {number[]}
*/

@@ -229,3 +242,5 @@ function set(dest, source) {

*
* @type {(path1: PathDataItem[], path2: PathDataItem[]) => boolean}
* @param {ReadonlyArray<import('../lib/types.js').PathDataItem>} path1
* @param {ReadonlyArray<import('../lib/types.js').PathDataItem>} path2
* @returns {boolean}
*/

@@ -253,4 +268,5 @@ export const intersects = function (path1, path2) {

})
)
) {
return false;
}

@@ -263,11 +279,15 @@ // Get a convex hull from points of each subpath. Has the most complexity O(n·log n).

return hullNest1.some(function (hull1) {
if (hull1.list.length < 3) return false;
if (hull1.list.length < 3) {
return false;
}
return hullNest2.some(function (hull2) {
if (hull2.list.length < 3) return false;
if (hull2.list.length < 3) {
return false;
}
var simplex = [getSupport(hull1, hull2, [1, 0])], // create the initial simplex
direction = minus(simplex[0]); // set the direction to point towards the origin
const simplex = [getSupport(hull1, hull2, [1, 0])]; // create the initial simplex
const direction = minus(simplex[0]); // set the direction to point towards the origin
var iterations = 1e4; // infinite loop protection, 10 000 iterations is more than enough
let iterations = 1e4; // infinite loop protection, 10 000 iterations is more than enough

@@ -284,5 +304,9 @@ while (true) {

// see if the new point was on the correct side of the origin
if (dot(direction, simplex[simplex.length - 1]) <= 0) return false;
if (dot(direction, simplex[simplex.length - 1]) <= 0) {
return false;
}
// process the simplex
if (processSimplex(simplex, direction)) return true;
if (processSimplex(simplex, direction)) {
return true;
}
}

@@ -293,3 +317,6 @@ });

/**
* @type {(a: Point, b: Point, direction: number[]) => number[]}
* @param {Point} a
* @param {Point} b
* @param {ReadonlyArray<number>} direction
* @returns {number[]}
*/

@@ -300,19 +327,23 @@ function getSupport(a, b, direction) {

// Computes farthest polygon point in particular direction.
// Thanks to knowledge of min/max x and y coordinates we can choose a quadrant to search in.
// Since we're working on convex hull, the dot product is increasing until we find the farthest point.
/**
* @type {(polygon: Point, direction: number[]) => number[]}
* Computes farthest polygon point in particular direction. Thanks to
* knowledge of min/max x and y coordinates we can choose a quadrant to search
* in. Since we're working on convex hull, the dot product is increasing until
* we find the farthest point.
*
* @param {Point} polygon
* @param {ReadonlyArray<number>} direction
* @returns {number[]}
*/
function supportPoint(polygon, direction) {
var index =
direction[1] >= 0
? direction[0] < 0
? polygon.maxY
: polygon.maxX
: direction[0] < 0
? polygon.minX
: polygon.minY,
max = -Infinity,
value;
let index =
direction[1] >= 0
? direction[0] < 0
? polygon.maxY
: polygon.maxX
: direction[0] < 0
? polygon.minX
: polygon.minY;
let max = -Infinity;
let value;
while ((value = dot(polygon.list[index], direction)) > max) {

@@ -327,3 +358,5 @@ max = value;

/**
* @type {(simplex: number[][], direction: number[]) => boolean}
* @param {number[][]} simplex
* @param {number[]} direction
* @returns {boolean}
*/

@@ -334,6 +367,6 @@ function processSimplex(simplex, direction) {

// 1-simplex
let a = simplex[1],
b = simplex[0],
AO = minus(simplex[1]),
AB = sub(b, a);
const a = simplex[1];
const b = simplex[0];
const AO = minus(simplex[1]);
const AB = sub(b, a);
// AO is in the same direction as AB

@@ -350,10 +383,10 @@ if (dot(AO, AB) > 0) {

// 2-simplex
let a = simplex[2], // [a, b, c] = simplex
b = simplex[1],
c = simplex[0],
AB = sub(b, a),
AC = sub(c, a),
AO = minus(a),
ACB = orth(AB, AC), // the vector perpendicular to AB facing away from C
ABC = orth(AC, AB); // the vector perpendicular to AC facing away from B
const a = simplex[2]; // [a, b, c] = simplex
const b = simplex[1];
const c = simplex[0];
const AB = sub(b, a);
const AC = sub(c, a);
const AO = minus(a);
const ACB = orth(AB, AC); // the vector perpendicular to AB facing away from C
const ABC = orth(AC, AB); // the vector perpendicular to AC facing away from B

@@ -381,3 +414,5 @@ if (dot(ACB, AO) > 0) {

} // region 7
else return true;
else {
return true;
}
}

@@ -388,3 +423,4 @@ return false;

/**
* @type {(v: number[]) => number[]}
* @param {ReadonlyArray<number>} v
* @returns {number[]}
*/

@@ -396,3 +432,5 @@ function minus(v) {

/**
* @type {(v1: number[], v2: number[]) => number[]}
* @param {ReadonlyArray<number>} v1
* @param {ReadonlyArray<number>} v2
* @returns {number[]}
*/

@@ -404,3 +442,5 @@ function sub(v1, v2) {

/**
* @type {(v1: number[], v2: number[]) => number}
* @param {ReadonlyArray<number>} v1
* @param {ReadonlyArray<number>} v2
* @returns {number}
*/

@@ -412,6 +452,8 @@ function dot(v1, v2) {

/**
* @type {(v1: number[], v2: number[]) => number[]}
* @param {ReadonlyArray<number>} v
* @param {ReadonlyArray<number>} from
* @returns {number[]}
*/
function orth(v, from) {
var o = [-v[1], v[0]];
const o = [-v[1], v[0]];
return dot(o, minus(from)) < 0 ? minus(o) : o;

@@ -421,33 +463,14 @@ }

/**
* @typedef {{
* list: number[][],
* minX: number,
* minY: number,
* maxX: number,
* maxY: number
* }} Point
* @param {ReadonlyArray<import('../lib/types.js').PathDataItem>} pathData
* @returns {Points}
*/
/**
* @typedef {{
* list: Point[],
* minX: number,
* minY: number,
* maxX: number,
* maxY: number
* }} Points
*/
/**
* @type {(pathData: PathDataItem[]) => Points}
*/
function gatherPoints(pathData) {
/**
* @type {Points}
*/
/** @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: number[]) => void}
* Writes data about the extreme points on each axle.
*
* @param {Point} path
* @param {number[]} point
*/

@@ -488,11 +511,13 @@ const addPoint = (path, point) => {

: points.list[points.list.length - 1];
let prev = i === 0 ? null : pathData[i - 1];
const prev = i === 0 ? null : pathData[i - 1];
let basePoint =
subPath.list.length === 0 ? null : subPath.list[subPath.list.length - 1];
let data = pathDataItem.args;
const data = pathDataItem.args;
let ctrlPoint = basePoint;
// TODO fix null hack
/**
* @type {(n: number, i: number) => number}
* TODO fix null hack
* @param {number} n
* @param {number} i
* @returns {number}
*/

@@ -541,3 +566,3 @@ const toAbsolute = (n, i) => n + (basePoint == null ? 0 : basePoint[i % 2]);

if (basePoint != null) {
// Approximate quibic Bezier curve with middle points between control points
// Approximate cubic Bezier curve with middle points between control points
addPoint(subPath, [

@@ -589,5 +614,5 @@ 0.5 * (basePoint[0] + data[0]),

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));
// Convert the arc to Bézier curves and use the same approximation
// @ts-expect-error no idea what's going on here
const curves = a2c.apply(0, basePoint.concat(data));
for (

@@ -612,3 +637,5 @@ var cData;

]);
if (curves.length) addPoint(subPath, (basePoint = cData.slice(-2)));
if (curves.length) {
addPoint(subPath, (basePoint = cData.slice(-2)));
}
}

@@ -620,3 +647,5 @@ }

// Save final command coordinates
if (data.length >= 2) addPoint(subPath, data.slice(-2));
if (data.length >= 2) {
addPoint(subPath, data.slice(-2));
}
}

@@ -628,6 +657,8 @@

/**
* Forms a convex hull from set of points of every subpath using monotone chain convex hull algorithm.
* https://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain
* Forms a convex hull from set of points of every subpath using monotone chain
* convex hull algorithm.
*
* @type {(points: Point) => Point}
* @see https://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain
* @param {Point} points
* @returns {Point}
*/

@@ -639,5 +670,5 @@ function convexHull(points) {

var lower = [],
minY = 0,
bottom = 0;
const lower = [];
let minY = 0;
let bottom = 0;
for (let i = 0; i < points.list.length; i++) {

@@ -658,5 +689,5 @@ while (

var upper = [],
maxY = points.list.length - 1,
top = 0;
const upper = [];
let maxY = points.list.length - 1;
let top = 0;
for (let i = points.list.length; i--; ) {

@@ -683,5 +714,3 @@ while (

/**
* @type {Point}
*/
/** @type {Point} */
const hull = {

@@ -699,3 +728,6 @@ list: hullList,

/**
* @type {(o: number[], a: number[], b: number[]) => number}
* @param {ReadonlyArray<number>} o
* @param {ReadonlyArray<number>} a
* @param {ReadonlyArray<number>} b
* @returns {number}
*/

@@ -707,17 +739,16 @@ function cross(o, a, b) {

/**
* Based on code from Snap.svg (Apache 2 license). http://snapsvg.io/
* Thanks to Dmitry Baranovskiy for his great work!
* Based on code from [Snap.svg](http://snapsvg.io/) (Apache 2 license). 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: number[]
* ) => number[]}
* @param {number} x1
* @param {number} y1
* @param {number} rx
* @param {number} ry
* @param {number} angle
* @param {number} large_arc_flag
* @param {number} sweep_flag
* @param {number} x2
* @param {number} y2
* @param {ReadonlyArray<number>} recursive
* @returns {number[]}
*/

@@ -740,8 +771,9 @@ const a2c = (

const rad = (Math.PI / 180) * (+angle || 0);
/**
* @type {number[]}
*/
/** @type {number[]} */
let res = [];
/**
* @type {(x: number, y: number, rad: number) => number}
* @param {number} x
* @param {number} y
* @param {number} rad
* @returns {number}
*/

@@ -751,4 +783,8 @@ const rotateX = (x, y, rad) => {

};
/**
* @type {(x: number, y: number, rad: number) => number}
* @param {number} x
* @param {number} y
* @param {number} rad
* @returns {number}
*/

@@ -763,5 +799,5 @@ const rotateY = (x, y, rad) => {

y2 = rotateY(x2, y2, -rad);
var x = (x1 - x2) / 2,
y = (y1 - y2) / 2;
var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
const x = (x1 - x2) / 2;
const y = (y1 - y2) / 2;
let h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
if (h > 1) {

@@ -772,5 +808,5 @@ h = Math.sqrt(h);

}
var rx2 = rx * rx;
var ry2 = ry * ry;
var k =
const rx2 = rx * rx;
const ry2 = ry * ry;
const k =
(large_arc_flag == sweep_flag ? -1 : 1) *

@@ -803,7 +839,7 @@ Math.sqrt(

}
var df = f2 - f1;
let df = f2 - f1;
if (Math.abs(df) > _120) {
var f2old = f2,
x2old = x2,
y2old = y2;
const f2old = f2;
const x2old = x2;
const y2old = y2;
f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);

@@ -820,17 +856,17 @@ x2 = cx + rx * Math.cos(f2);

df = f2 - f1;
var c1 = Math.cos(f1),
s1 = Math.sin(f1),
c2 = Math.cos(f2),
s2 = Math.sin(f2),
t = Math.tan(df / 4),
hx = (4 / 3) * rx * t,
hy = (4 / 3) * ry * t,
m = [
-hx * s1,
hy * c1,
x2 + hx * s2 - x1,
y2 - hy * c2 - y1,
x2 - x1,
y2 - y1,
];
const c1 = Math.cos(f1);
const s1 = Math.sin(f1);
const c2 = Math.cos(f2);
const s2 = Math.sin(f2);
const t = Math.tan(df / 4);
const hx = (4 / 3) * rx * t;
const hy = (4 / 3) * ry * t;
const m = [
-hx * s1,
hy * c1,
x2 + hx * s2 - x1,
y2 - hy * c2 - y1,
x2 - x1,
y2 - y1,
];
if (recursive) {

@@ -840,4 +876,4 @@ return m.concat(res);

res = m.concat(res);
var newres = [];
for (var i = 0, n = res.length; i < n; i++) {
const newres = [];
for (let i = 0, n = res.length; i < n; i++) {
newres[i] =

@@ -844,0 +880,0 @@ i % 2

import { cleanupOutData, toFixed } from '../lib/svgo/tools.js';
/**
* @typedef {{ name: string, data: number[] }} TransformItem
* @typedef {{
* convertToShorts: boolean,
* degPrecision?: number,
* floatPrecision: number,
* transformPrecision: number,
* matrixToTransform: boolean,
* shortTranslate: boolean,
* shortScale: boolean,
* shortRotate: boolean,
* removeUseless: boolean,
* collapseIntoOne: boolean,
* leadingZero: boolean,
* negativeExtraSpace: boolean,
* }} TransformParams
* @typedef TransformItem
* @property {string} name
* @property {number[]} data
*
* @typedef TransformParams
* @property {boolean} convertToShorts
* @property {number=} degPrecision
* @property {number} floatPrecision
* @property {number} transformPrecision
* @property {boolean} matrixToTransform
* @property {boolean} shortTranslate
* @property {boolean} shortScale
* @property {boolean} shortRotate
* @property {boolean} removeUseless
* @property {boolean} collapseIntoOne
* @property {boolean} leadingZero
* @property {boolean} negativeExtraSpace
*
*/

@@ -74,3 +77,3 @@

*
* @param {TransformItem[]} transforms
* @param {ReadonlyArray<TransformItem>} transforms
* @returns {TransformItem}

@@ -187,4 +190,4 @@ */

* @param {TransformItem} matrix
* @returns {TransformItem[]|undefined}
* @see {@link https://frederic-wang.fr/decomposition-of-2d-transform-matrices.html} Where applicable, variables are named in accordance with this document.
* @returns {TransformItem[] | undefined}
* @see {@link https://frederic-wang.fr/2013/12/01/decomposition-of-2d-transform-matrices/} Where applicable, variables are named in accordance with this document.
*/

@@ -243,4 +246,4 @@ const decomposeQRAB = (matrix) => {

* @param {TransformItem} matrix
* @returns {TransformItem[]|undefined}
* @see {@link https://frederic-wang.fr/decomposition-of-2d-transform-matrices.html} Where applicable, variables are named in accordance with this document.
* @returns {TransformItem[] | undefined}
* @see {@link https://frederic-wang.fr/2013/12/01/decomposition-of-2d-transform-matrices/} Where applicable, variables are named in accordance with this document.
*/

@@ -347,4 +350,4 @@ const decomposeQRCD = (matrix) => {

* Optimize matrix of simple transforms.
* @param {TransformItem[]} roundedTransforms
* @param {TransformItem[]} rawTransforms
* @param {ReadonlyArray<TransformItem>} roundedTransforms
* @param {ReadonlyArray<TransformItem>} rawTransforms
* @returns {TransformItem[]}

@@ -447,3 +450,3 @@ */

/**
* @param {number[]} data
* @param {ReadonlyArray<number>} data
* @returns {TransformItem}

@@ -496,3 +499,4 @@ */

*
* @type {(transform: TransformItem) => number[] }
* @param {TransformItem} transform
* @returns {number[]}
*/

@@ -519,6 +523,6 @@ const transformToMatrix = (transform) => {

// [cos(a), sin(a), -sin(a), cos(a), x, y]
var cos = mth.cos(transform.data[0]),
sin = mth.sin(transform.data[0]),
cx = transform.data[1] || 0,
cy = transform.data[2] || 0;
var cos = mth.cos(transform.data[0]);
var sin = mth.sin(transform.data[0]);
var cx = transform.data[1] || 0;
var cy = transform.data[2] || 0;
return [

@@ -544,12 +548,12 @@ cos,

/**
* Applies transformation to an arc. To do so, we represent ellipse as a matrix, multiply it
* by the transformation matrix and use a singular value decomposition to represent in a form
* rotate(θ)·scale(a b)·rotate(φ). This gives us new ellipse params a, b and θ.
* SVD is being done with the formulae provided by Wolffram|Alpha (svd {{m0, m2}, {m1, m3}})
* Applies transformation to an arc. To do so, we represent ellipse as a matrix,
* multiply it by the transformation matrix and use a singular value
* decomposition to represent in a form rotate(θ)·scale(a b)·rotate(φ). This
* gives us new ellipse params a, b and θ. SVD is being done with the formulae
* provided by Wolfram|Alpha (svd {{m0, m2}, {m1, m3}})
*
* @type {(
* cursor: [x: number, y: number],
* arc: number[],
* transform: number[]
* ) => number[]}
* @param {[number, number]} cursor
* @param {number[]} arc
* @param {ReadonlyArray<number>} transform
* @returns {number[]}
*/

@@ -615,3 +619,5 @@ export const transformArc = (cursor, arc, transform) => {

*
* @type {(a: number[], b: number[]) => number[]}
* @param {ReadonlyArray<number>} a
* @param {ReadonlyArray<number>} b
* @returns {number[]}
*/

@@ -630,3 +636,5 @@ const multiplyTransformMatrices = (a, b) => {

/**
* @type {(transform: TransformItem, params: TransformParams) => TransformItem}
* @param {TransformItem} transform
* @param {TransformParams} params
* @returns {TransformItem}
*/

@@ -662,3 +670,5 @@ export const roundTransform = (transform, params) => {

/**
* @type {(data: number[], params: TransformParams) => number[]}
* @param {number[]} data
* @param {TransformParams} params
* @returns {number[]}
*/

@@ -678,3 +688,5 @@ const degRound = (data, params) => {

/**
* @type {(data: number[], params: TransformParams) => number[]}
* @param {number[]} data
* @param {TransformParams} params
* @returns {number[]}
*/

@@ -690,3 +702,5 @@ const floatRound = (data, params) => {

/**
* @type {(data: number[], params: TransformParams) => number[]}
* @param {number[]} data
* @param {TransformParams} params
* @returns {number[]}
*/

@@ -704,3 +718,4 @@ const transformRound = (data, params) => {

*
* @type {(data: number[]) => number[]}
* @param {ReadonlyArray<number>} data
* @returns {number[]}
*/

@@ -712,5 +727,4 @@ const round = (data) => {

/**
* Decrease accuracy of floating-point numbers
* in transforms keeping a specified number of decimals.
* Smart rounds values like 2.349 to 2.35.
* Decrease accuracy of floating-point numbers in transforms keeping a specified
* number of decimals. Smart rounds values like 2.349 to 2.35.
*

@@ -723,3 +737,3 @@ * @param {number} precision

for (
var i = data.length,
let i = data.length,
tolerance = +Math.pow(0.1, precision).toFixed(precision);

@@ -730,3 +744,3 @@ i--;

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

@@ -745,3 +759,3 @@ +Math.abs(rounded - data[i]).toFixed(precision + 1) >= tolerance

*
* @param {TransformItem[]} transformJS
* @param {ReadonlyArray<TransformItem>} transformJS
* @param {TransformParams} params

@@ -748,0 +762,0 @@ * @returns {string}

@@ -0,5 +1,11 @@

/**
* @typedef AddAttributesToSVGElementParams
* @property {string | Record<string, null | string>=} attribute
* @property {Array<string | Record<string, null | string>>=} attributes
*/
export const name = 'addAttributesToSVGElement';
export const description = 'adds attributes to an outer <svg> element';
var ENOCLS = `Error in plugin "addAttributesToSVGElement": absent parameters.
const ENOCLS = `Error in plugin "addAttributesToSVGElement": absent parameters.
It should have a list of "attributes" or one "attribute".

@@ -48,3 +54,3 @@ Config example:

*
* @type {import('./plugins-types.js').Plugin<'addAttributesToSVGElement'>}
* @type {import('../lib/types.js').Plugin<AddAttributesToSVGElementParams>}
*/

@@ -64,3 +70,3 @@ export const fn = (root, params) => {

if (node.attributes[attribute] == null) {
// @ts-ignore disallow explicit nullable attribute value
// @ts-expect-error disallow explicit nullable attribute value
node.attributes[attribute] = undefined;

@@ -72,3 +78,3 @@ }

if (node.attributes[key] == null) {
// @ts-ignore disallow explicit nullable attribute value
// @ts-expect-error disallow explicit nullable attribute value
node.attributes[key] = attribute[key];

@@ -75,0 +81,0 @@ }

@@ -0,5 +1,11 @@

/**
* @typedef AddClassesToSVGElementParams
* @property {string | ((node: import('../lib/types.js').XastElement, info: import('../lib/types.js').PluginInfo) => string)=} className
* @property {Array<string | ((node: import('../lib/types.js').XastElement, info: import('../lib/types.js').PluginInfo) => string)>=} classNames
*/
export const name = 'addClassesToSVGElement';
export const description = 'adds classnames to an outer <svg> element';
var ENOCLS = `Error in plugin "addClassesToSVGElement": absent parameters.
const ENOCLS = `Error in plugin "addClassesToSVGElement": absent parameters.
It should have a list of classes in "classNames" or one "className".

@@ -50,3 +56,3 @@ Config example:

*
* @type {import('./plugins-types.js').Plugin<'addClassesToSVGElement'>}
* @type {import('../lib/types.js').Plugin<AddClassesToSVGElementParams>}
*/

@@ -53,0 +59,0 @@ export const fn = (root, params, info) => {

@@ -1,22 +0,12 @@

/**
* @typedef {import('../lib/types.js').PathDataItem} PathDataItem
* @typedef {import('../lib/types.js').XastElement} XastElement
*/
import { path2js } from './_path.js';
import {
transformsMultiply,
transform2js,
transformArc,
transformsMultiply,
} from './_transforms.js';
import { referencesProps, attrsGroupsDefaults } from './_collections.js';
import { attrsGroupsDefaults, referencesProps } from './_collections.js';
import { collectStylesheet, computeStyle } from '../lib/style.js';
import { removeLeadingZero, includesUrlReference } from '../lib/svgo/tools.js';
import { includesUrlReference, removeLeadingZero } from '../lib/svgo/tools.js';
/**
* @typedef {PathDataItem[]} PathData
* @typedef {number[]} Matrix
*/
const regNumericValues = /[-+]?(\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;

@@ -161,3 +151,6 @@

/**
* @type {(matrix: Matrix, x: number, y: number) => [number, number]}
* @param {ReadonlyArray<number>} matrix
* @param {number} x
* @param {number} y
* @returns {[number, number]}
*/

@@ -171,3 +164,6 @@ const transformAbsolutePoint = (matrix, x, y) => {

/**
* @type {(matrix: Matrix, x: number, y: number) => [number, number]}
* @param {ReadonlyArray<number>} matrix
* @param {number} x
* @param {number} y
* @returns {[number, number]}
*/

@@ -181,12 +177,9 @@ const transformRelativePoint = (matrix, x, y) => {

/**
* @type {(pathData: PathData, matrix: Matrix) => void}
* @param {ReadonlyArray<import('../lib/types.js').PathDataItem>} pathData
* @param {ReadonlyArray<number>} matrix
*/
const applyMatrixToPathData = (pathData, matrix) => {
/**
* @type {[number, number]}
*/
/** @type {[number, number]} */
const start = [0, 0];
/**
* @type {[number, number]}
*/
/** @type {[number, number]} */
const cursor = [0, 0];

@@ -193,0 +186,0 @@

@@ -0,1 +1,8 @@

/**
* @typedef CleanupAttrsParams
* @property {boolean=} newlines
* @property {boolean=} trim
* @property {boolean=} spaces
*/
export const name = 'cleanupAttrs';

@@ -13,3 +20,3 @@ export const description =

* @author Kir Belevich
* @type {import('./plugins-types.js').Plugin<'cleanupAttrs'>}
* @type {import('../lib/types.js').Plugin<CleanupAttrsParams>}
*/

@@ -23,3 +30,3 @@ export const fn = (root, params) => {

if (newlines) {
// new line which requires a space instead of themself
// new line which requires a space instead
node.attributes[name] = node.attributes[name].replace(

@@ -26,0 +33,0 @@ regNewlinesNeedSpace,

@@ -12,3 +12,4 @@ import * as csstree from 'css-tree';

/**
* Remove or cleanup enable-background attr which coincides with a width/height box.
* Remove or cleanup enable-background attr which coincides with a width/height
* box.
*

@@ -18,6 +19,6 @@ * @see https://www.w3.org/TR/SVG11/filters.html#EnableBackgroundProperty

* <svg width="100" height="50" enable-background="new 0 0 100 50">
* ⬇
* ⬇
* <svg width="100" height="50">
* @author Kir Belevich
* @type {import('./plugins-types.js').Plugin<'cleanupEnableBackground'>}
* @type {import('../lib/types.js').Plugin}
*/

@@ -116,3 +117,3 @@ export const fn = (root) => {

const styleValue = csstree.generate(
// @ts-ignore
// @ts-expect-error
enableBackgroundDeclaration.data.value,

@@ -128,3 +129,3 @@ );

if (styleCleaned) {
// @ts-ignore
// @ts-expect-error
enableBackgroundDeclaration.data.value = {

@@ -153,3 +154,3 @@ type: 'Raw',

/**
* @param {string} value Value of a enable-background attribute or style declaration.
* @param {string} value Value of an enable-background attribute or style declaration.
* @param {string} nodeName Name of the node the value was assigned to.

@@ -156,0 +157,0 @@ * @param {string} width Width of the node the value was assigned to.

import { visitSkip } from '../lib/xast.js';
import { hasScripts, findReferences } from '../lib/svgo/tools.js';
import { findReferences, hasScripts } from '../lib/svgo/tools.js';
/**
* @typedef {import('../lib/types.js').XastElement} XastElement
* @typedef CleanupIdsParams
* @property {boolean=} remove
* @property {boolean=} minify
* @property {string[]=} preserve
* @property {string[]=} preservePrefixes
* @property {boolean=} force
*/

@@ -70,3 +75,5 @@

*
* @type {(string: string, prefixes: string[]) => boolean}
* @param {string} string
* @param {ReadonlyArray<string>} prefixes
* @returns {boolean}
*/

@@ -111,3 +118,4 @@ const hasStringPrefix = (string, prefixes) => {

*
* @type {(arr: number[]) => string}
* @param {ReadonlyArray<number>} arr
* @returns {string}
*/

@@ -119,8 +127,8 @@ const getIdString = (arr) => {

/**
* Remove unused and minify used IDs
* (only if there are no any <style> or <script>).
* Remove unused and minify used IDs (only if there are no `<style>` or
* `<script>` nodes).
*
* @author Kir Belevich
*
* @type {import('./plugins-types.js').Plugin<'cleanupIds'>}
* @type {import('../lib/types.js').Plugin<CleanupIdsParams>}
*/

@@ -143,9 +151,5 @@ export const fn = (_root, params) => {

: [];
/**
* @type {Map<string, XastElement>}
*/
/** @type {Map<string, import('../lib/types.js').XastElement>} */
const nodeById = new Map();
/**
* @type {Map<string, {element: XastElement, name: string }[]>}
*/
/** @type {Map<string, {element: import('../lib/types.js').XastElement, name: string }[]>} */
const referencesById = new Map();

@@ -225,3 +229,3 @@ let deoptimized = false;

/** @type {?string} */
let currentIdString = null;
let currentIdString;
do {

@@ -228,0 +232,0 @@ currentId = generateId(currentId);

import { removeLeadingZero } from '../lib/svgo/tools.js';
/**
* @typedef CleanupListOfValuesParams
* @property {number=} floatPrecision
* @property {boolean=} leadingZero
* @property {boolean=} defaultPx
* @property {boolean=} convertToPx
*/
export const name = 'cleanupListOfValues';

@@ -24,7 +32,7 @@ export const description = 'rounds list of values to the fixed precision';

* <svg viewBox="0 0 200.28423 200.28423" enable-background="new 0 0 200.28423 200.28423">
* ⬇
* ⬇
* <svg viewBox="0 0 200.284 200.284" enable-background="new 0 0 200.284 200.284">
*
* <polygon points="208.250977 77.1308594 223.069336 ... "/>
* ⬇
* ⬇
* <polygon points="208.251 77.131 223.069 ... "/>

@@ -34,3 +42,3 @@ *

*
* @type {import('./plugins-types.js').Plugin<'cleanupListOfValues'>}
* @type {import('../lib/types.js').Plugin<CleanupListOfValuesParams>}
*/

@@ -46,3 +54,4 @@ export const fn = (_root, params) => {

/**
* @type {(lists: string) => string}
* @param {string} lists
* @returns {string}
*/

@@ -60,9 +69,5 @@ const roundValues = (lists) => {

let num = Number(Number(match[1]).toFixed(floatPrecision));
/**
* @type {any}
*/
let matchedUnit = match[3] || '';
/**
* @type{'' | keyof typeof absoluteLengths}
*/
/** @type {any} */
const matchedUnit = match[3] || '';
/** @type {'' | keyof typeof absoluteLengths} */
let units = matchedUnit;

@@ -69,0 +74,0 @@

import { removeLeadingZero } from '../lib/svgo/tools.js';
/**
* @typedef CleanupNumericValuesParams
* @property {number=} floatPrecision
* @property {boolean=} leadingZero
* @property {boolean=} defaultPx
* @property {boolean=} convertToPx
*/
export const name = 'cleanupNumericValues';
export const description =
'rounds numeric values to the fixed precision, removes default ‘px’ units';
'rounds numeric values to the fixed precision, removes default "px" units';

@@ -21,8 +29,7 @@ const regNumericValues =

/**
* Round numeric values to the fixed precision,
* remove default 'px' units.
* Round numeric values to the fixed precision, remove default 'px' units.
*
* @author Kir Belevich
*
* @type {import('./plugins-types.js').Plugin<'cleanupNumericValues'>}
* @type {import('../lib/types.js').Plugin<CleanupNumericValuesParams>}
*/

@@ -64,9 +71,5 @@ export const fn = (_root, params) => {

let num = Number(Number(match[1]).toFixed(floatPrecision));
/**
* @type {any}
*/
let matchedUnit = match[3] || '';
/**
* @type{'' | keyof typeof absoluteLengths}
*/
/** @type {any} */
const matchedUnit = match[3] || '';
/** @type {'' | keyof typeof absoluteLengths} */
let units = matchedUnit;

@@ -73,0 +76,0 @@

@@ -1,9 +0,4 @@

import { computeStyle, collectStylesheet } from '../lib/style.js';
import { inheritableAttrs, elemsGroups } from './_collections.js';
import { collectStylesheet, computeStyle } from '../lib/style.js';
import { elemsGroups, inheritableAttrs } from './_collections.js';
/**
* @typedef {import('../lib/types.js').XastElement} XastElement
* @typedef {import('../lib/types.js').XastNode} XastNode
*/
export const name = 'collapseGroups';

@@ -13,3 +8,5 @@ export const description = 'collapses useless groups';

/**
* @type {(node: XastNode, name: string) => boolean}
* @param {import('../lib/types.js').XastNode} node
* @param {string} name
* @returns {boolean}
*/

@@ -42,3 +39,3 @@ const hasAnimatedAttr = (node, name) => {

* </g>
* ⬇
* ⬇
* <g>

@@ -49,3 +46,3 @@ * <g>

* </g>
* ⬇
* ⬇
* <path attr1="val1" d="..."/>

@@ -55,3 +52,3 @@ *

*
* @type {import('./plugins-types.js').Plugin<'collapseGroups'>}
* @type {import('../lib/types.js').Plugin}
*/

@@ -136,9 +133,2 @@ export const fn = (root) => {

parentNode.children.splice(index, 1, ...node.children);
// TODO remove legacy parentNode in v4
for (const child of node.children) {
Object.defineProperty(child, 'parentNode', {
writable: true,
value: parentNode,
});
}
}

@@ -145,0 +135,0 @@ },

import { colorsNames, colorsProps, colorsShortNames } from './_collections.js';
import { includesUrlReference } from '../lib/svgo/tools.js';
/**
* @typedef ConvertColorsParams
* @property {boolean | string | RegExp=} currentColor
* @property {boolean=} names2hex
* @property {boolean=} rgb2hex
* @property {false | 'lower' | 'upper'=} convertCase
* @property {boolean=} shorthex
* @property {boolean=} shortname
*/
export const name = 'convertColors';

@@ -25,3 +35,4 @@ export const description =

*
* @type {(rgb: number[]) => string}
* @param {ReadonlyArray<number>} param0
* @returns {string}
*/

@@ -65,3 +76,3 @@ const convertRgbToHex = ([r, g, b]) => {

*
* @type {import('./plugins-types.js').Plugin<'convertColors'>}
* @type {import('../lib/types.js').Plugin<ConvertColorsParams>}
*/

@@ -115,5 +126,5 @@ export const fn = (_root, params) => {

if (rgb2hex) {
let match = val.match(regRGB);
const match = val.match(regRGB);
if (match != null) {
let nums = match.slice(1, 4).map((m) => {
const nums = match.slice(1, 4).map((m) => {
let n;

@@ -141,3 +152,3 @@ if (m.indexOf('%') > -1) {

if (shorthex) {
let match = regHEX.exec(val);
const match = regHEX.exec(val);
if (match != null) {

@@ -144,0 +155,0 @@ val = '#' + match[0][1] + match[0][3] + match[0][5];

@@ -11,3 +11,3 @@ export const name = 'convertEllipseToCircle';

*
* @type {import('./plugins-types.js').Plugin<'convertEllipseToCircle'>}
* @type {import('../lib/types.js').Plugin}
*/

@@ -14,0 +14,0 @@ export const fn = () => {

import { attrsGroupsDefaults, colorsProps } from './_collections.js';
import {
detachNodeFromParent,
querySelector,
querySelectorAll,
querySelector,
} from '../lib/xast.js';
import { computeStyle, collectStylesheet } from '../lib/style.js';
import { collectStylesheet, computeStyle } from '../lib/style.js';
/**
* @typedef {import('../lib/types.js').XastElement} XastElement
* @typedef {import('../lib/types.js').XastParent} XastParent
*/
export const name = 'convertOneStopGradients';

@@ -22,3 +17,3 @@ export const description =

* @author Seth Falco <seth@falco.fun>
* @type {import('./plugins-types.js').Plugin<'convertOneStopGradients'>}
* @type {import('../lib/types.js').Plugin}
* @see https://developer.mozilla.org/docs/Web/SVG/Element/linearGradient

@@ -33,14 +28,10 @@ * @see https://developer.mozilla.org/docs/Web/SVG/Element/radialGradient

*
* @type {Set<XastElement>}
* @type {Set<import('../lib/types.js').XastElement>}
*/
const effectedDefs = new Set();
/**
* @type {Map<XastElement, XastParent>}
*/
/** @type {Map<import('../lib/types.js').XastElement, import('../lib/types.js').XastParent>} */
const allDefs = new Map();
/**
* @type {Map<XastElement, XastParent>}
*/
/** @type {Map<import('../lib/types.js').XastElement, import('../lib/types.js').XastParent>} */
const gradientsToDetach = new Map();

@@ -72,3 +63,3 @@

const href = node.attributes['xlink:href'] || node.attributes['href'];
let effectiveNode =
const effectiveNode =
stops.length === 0 && href != null && href.startsWith('#')

@@ -75,0 +66,0 @@ ? querySelector(root, href)

@@ -1,2 +0,2 @@

import { path2js, js2path } from './_path.js';
import { js2path, path2js } from './_path.js';
import { pathElems } from './_collections.js';

@@ -9,3 +9,33 @@ import { applyTransforms } from './applyTransforms.js';

/**
* @typedef {import('../lib/types.js').PathDataItem} PathDataItem
* @typedef {[number, number]} Point
*
* @typedef Circle
* @property {Point} center
* @property {number} radius
*
* @typedef MakeArcs
* @property {number} threshold
* @property {number} tolerance
*
* @typedef ConvertPathDataParams
* @property {boolean=} applyTransforms
* @property {boolean=} applyTransformsStroked
* @property {MakeArcs=} makeArcs
* @property {boolean=} straightCurves
* @property {boolean=} convertToQ
* @property {boolean=} lineShorthands
* @property {boolean=} convertToZ
* @property {boolean=} curveSmoothShorthands
* @property {number | false=} floatPrecision
* @property {number=} transformPrecision
* @property {boolean=} smartArcRounding
* @property {boolean=} removeUseless
* @property {boolean=} collapseRepeated
* @property {boolean=} utilizeAbsolute
* @property {boolean=} leadingZero
* @property {boolean=} negativeExtraSpace
* @property {boolean=} noSpaceAfterFlags
* @property {boolean=} forceAbsolutePath
*
* @typedef {Required<ConvertPathDataParams>} InternalParams
*/

@@ -29,39 +59,2 @@

/**
* @typedef {{
* applyTransforms: boolean,
* applyTransformsStroked: boolean,
* makeArcs: {
* threshold: number,
* tolerance: number,
* },
* straightCurves: boolean,
* convertToQ: boolean,
* lineShorthands: boolean,
* convertToZ: boolean,
* curveSmoothShorthands: boolean,
* floatPrecision: number | false,
* transformPrecision: number,
* smartArcRounding: boolean,
* removeUseless: boolean,
* collapseRepeated: boolean,
* utilizeAbsolute: boolean,
* leadingZero: boolean,
* negativeExtraSpace: boolean,
* noSpaceAfterFlags: boolean,
* forceAbsolutePath: boolean,
* }} InternalParams
*/
/**
* @typedef {[number, number]} Point
*/
/**
* @typedef {{
* center: Point,
* radius: number
* }} Circle
*/
/**
* Convert absolute Path to relative,

@@ -78,3 +71,3 @@ * collapse repeated instructions,

*
* @type {import('./plugins-types.js').Plugin<'convertPathData'>}
* @type {import('../lib/types.js').Plugin<ConvertPathDataParams>}
*/

@@ -107,5 +100,3 @@ export const fn = (root, params) => {

/**
* @type {InternalParams}
*/
/** @type {InternalParams} */
const newParams = {

@@ -136,3 +127,3 @@ applyTransforms: _applyTransforms,

root,
// @ts-ignore
// @ts-expect-error
applyTransforms(root, {

@@ -216,3 +207,3 @@ transformPrecision,

// @ts-ignore
// @ts-expect-error
js2path(node, data, newParams);

@@ -229,7 +220,8 @@ }

*
* @type {(pathData: PathDataItem[]) => PathDataItem[]}
* @param {import('../lib/types.js').PathDataItem[]} pathData
* @returns {import('../lib/types.js').PathDataItem[]}
*/
const convertToRelative = (pathData) => {
let start = [0, 0];
let cursor = [0, 0];
const start = [0, 0];
const cursor = [0, 0];
let prevCoords = [0, 0];

@@ -389,7 +381,7 @@

// base should preserve reference from other element
// @ts-ignore
// @ts-expect-error
pathItem.base = prevCoords;
// @ts-ignore
// @ts-expect-error
pathItem.coords = [cursor[0], cursor[1]];
// @ts-ignore
// @ts-expect-error
prevCoords = pathItem.coords;

@@ -404,7 +396,6 @@ }

*
* @type {(
* path: PathDataItem[],
* params: InternalParams,
* aux: { isSafeToUseZ: boolean, maybeHasStrokeAndLinecap: boolean, hasMarkerMid: boolean }
* ) => PathDataItem[]}
* @param {import('../lib/types.js').PathDataItem[]} path
* @param {InternalParams} params
* @param {{ isSafeToUseZ: boolean, maybeHasStrokeAndLinecap: boolean, hasMarkerMid: boolean }} param2
* @returns {import('../lib/types.js').PathDataItem[]}
*/

@@ -432,4 +423,4 @@ function filters(

if (command !== 'Z' && command !== 'z') {
var sdata = data,
circle;
let sdata = data;
let circle;

@@ -454,33 +445,27 @@ if (command === 's') {

) {
var r = roundData([circle.radius])[0],
angle = findArcAngle(sdata, circle),
sweep = sdata[5] * sdata[0] - sdata[4] * sdata[1] > 0 ? 1 : 0,
/**
* @type {PathDataItem}
*/
arc = {
command: 'a',
args: [r, r, 0, 0, sweep, sdata[4], sdata[5]],
// @ts-ignore
coords: item.coords.slice(),
// @ts-ignore
base: item.base,
},
/**
* @type {PathDataItem[]}
*/
output = [arc],
// relative coordinates to adjust the found circle
/**
* @type {Point}
*/
relCenter = [
circle.center[0] - sdata[4],
circle.center[1] - sdata[5],
],
relCircle = { center: relCenter, radius: circle.radius },
arcCurves = [item],
hasPrev = 0,
suffix = '',
nextLonghand;
const r = roundData([circle.radius])[0];
let angle = findArcAngle(sdata, circle);
const sweep = sdata[5] * sdata[0] - sdata[4] * sdata[1] > 0 ? 1 : 0;
/** @type {import('../lib/types.js').PathDataItem} */
let arc = {
command: 'a',
args: [r, r, 0, 0, sweep, sdata[4], sdata[5]],
// @ts-expect-error
coords: item.coords.slice(),
// @ts-expect-error
base: item.base,
};
/** @type {import('../lib/types.js').PathDataItem[]} */
const output = [arc];
// relative coordinates to adjust the found circle
/** @type {Point} */
const relCenter = [
circle.center[0] - sdata[4],
circle.center[1] - sdata[5],
];
const relCircle = { center: relCenter, radius: circle.radius };
const arcCurves = [item];
let hasPrev = 0;
let suffix = '';
let nextLonghand;

@@ -494,10 +479,10 @@ if (

arcCurves.unshift(prev);
// @ts-ignore
// @ts-expect-error
arc.base = prev.base;
// @ts-ignore
// @ts-expect-error
arc.args[5] = arc.coords[0] - arc.base[0];
// @ts-ignore
// @ts-expect-error
arc.args[6] = arc.coords[1] - arc.base[1];
var prevData = prev.command == 'a' ? prev.sdata : prev.args;
var prevAngle = findArcAngle(prevData, {
const prevData = prev.command == 'a' ? prev.sdata : prev.args;
const prevAngle = findArcAngle(prevData, {
center: [

@@ -510,3 +495,5 @@ prevData[4] + circle.center[0],

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

@@ -521,3 +508,3 @@ }

) {
var nextData = next.args;
let nextData = next.args;
if (next.command == 's') {

@@ -534,12 +521,16 @@ nextLonghand = makeLonghand(

angle += findArcAngle(nextData, relCircle);
if (angle - 2 * Math.PI > 1e-3) break; // more than 360°
if (angle > Math.PI) arc.args[3] = 1;
if (angle - 2 * Math.PI > 1e-3) {
break;
} // more than 360°
if (angle > Math.PI) {
arc.args[3] = 1;
}
arcCurves.push(next);
if (2 * Math.PI - angle > 1e-3) {
// less than 360°
// @ts-ignore
// @ts-expect-error
arc.coords = next.coords;
// @ts-ignore
// @ts-expect-error
arc.args[5] = arc.coords[0] - arc.base[0];
// @ts-ignore
// @ts-expect-error
arc.args[6] = arc.coords[1] - arc.base[1];

@@ -550,7 +541,7 @@ } else {

arc.args[6] = 2 * (relCircle.center[1] - nextData[5]);
// @ts-ignore
// @ts-expect-error
arc.coords = [
// @ts-ignore
// @ts-expect-error
arc.base[0] + arc.args[5],
// @ts-ignore
// @ts-expect-error
arc.base[1] + arc.args[6],

@@ -566,10 +557,10 @@ ];

sweep,
// @ts-ignore
// @ts-expect-error
next.coords[0] - arc.coords[0],
// @ts-ignore
// @ts-expect-error
next.coords[1] - arc.coords[1],
],
// @ts-ignore
// @ts-expect-error
coords: next.coords,
// @ts-ignore
// @ts-expect-error
base: arc.coords,

@@ -583,3 +574,5 @@ };

relCenter[1] -= nextData[5];
} else break;
} else {
break;
}
}

@@ -592,19 +585,19 @@

if (hasPrev) {
var prevArc = output.shift();
// @ts-ignore
const prevArc = output.shift();
// @ts-expect-error
roundData(prevArc.args);
// @ts-ignore
// @ts-expect-error
relSubpoint[0] += prevArc.args[5] - prev.args[prev.args.length - 2];
// @ts-ignore
// @ts-expect-error
relSubpoint[1] += prevArc.args[6] - prev.args[prev.args.length - 1];
prev.command = 'a';
// @ts-ignore
// @ts-expect-error
prev.args = prevArc.args;
// @ts-ignore
// @ts-expect-error
item.base = prev.coords = prevArc.coords;
}
// @ts-ignore
// @ts-expect-error
arc = output.shift();
if (arcCurves.length == 1) {
// @ts-ignore
// @ts-expect-error
item.sdata = sdata.slice(); // preserve curve data for future checks

@@ -615,6 +608,8 @@ } else if (arcCurves.length - 1 - hasPrev > 0) {

}
if (!arc) return false;
if (!arc) {
return false;
}
command = 'a';
data = arc.args;
// @ts-ignore
// @ts-expect-error
item.coords = arc.coords;

@@ -636,16 +631,16 @@ }

) {
for (var i = data.length; i--; ) {
// @ts-ignore
for (let i = data.length; i--; ) {
// @ts-expect-error
data[i] += item.base[i % 2] - relSubpoint[i % 2];
}
} else if (command == 'h') {
// @ts-ignore
// @ts-expect-error
data[0] += item.base[0] - relSubpoint[0];
} else if (command == 'v') {
// @ts-ignore
// @ts-expect-error
data[0] += item.base[1] - relSubpoint[1];
} else if (command == 'a') {
// @ts-ignore
// @ts-expect-error
data[5] += item.base[0] - relSubpoint[0];
// @ts-ignore
// @ts-expect-error
data[6] += item.base[1] - relSubpoint[1];

@@ -655,5 +650,7 @@ }

if (command == 'h') relSubpoint[0] += data[0];
else if (command == 'v') relSubpoint[1] += data[0];
else {
if (command == 'h') {
relSubpoint[0] += data[0];
} else if (command == 'v') {
relSubpoint[1] += data[0];
} else {
relSubpoint[0] += data[data.length - 2];

@@ -694,7 +691,11 @@ relSubpoint[1] += data[data.length - 1];

) {
if (next && next.command == 's') makeLonghand(next, data); // fix up next curve
if (next && next.command == 's') {
makeLonghand(next, data);
} // fix up next curve
command = 'l';
data = data.slice(-2);
} else if (command === 'q' && isCurveStraightLine(data)) {
if (next && next.command == 't') makeLonghand(next, data); // fix up next curve
if (next && next.command == 't') {
makeLonghand(next, data);
} // fix up next curve
command = 'l';

@@ -724,13 +725,13 @@ data = data.slice(-2);

const x1 =
// @ts-ignore
// @ts-expect-error
0.75 * (item.base[0] + data[0]) - 0.25 * item.base[0];
const x2 =
// @ts-ignore
// @ts-expect-error
0.75 * (item.base[0] + data[2]) - 0.25 * (item.base[0] + data[4]);
if (Math.abs(x1 - x2) < error * 2) {
const y1 =
// @ts-ignore
// @ts-expect-error
0.75 * (item.base[1] + data[1]) - 0.25 * item.base[1];
const y2 =
// @ts-ignore
// @ts-expect-error
0.75 * (item.base[1] + data[3]) - 0.25 * (item.base[1] + data[5]);

@@ -742,14 +743,16 @@ if (Math.abs(y1 - y2) < error * 2) {

4,
// @ts-ignore
// @ts-expect-error
x1 + x2 - item.base[0],
// @ts-ignore
// @ts-expect-error
y1 + y2 - item.base[1],
);
roundData(newData);
const originalLength = cleanupOutData(data, params).length,
newLength = cleanupOutData(newData, params).length;
const originalLength = cleanupOutData(data, params).length;
const newLength = cleanupOutData(newData, params).length;
if (newLength < originalLength) {
command = 'q';
data = newData;
if (next && next.command == 's') makeLonghand(next, data); // fix up next curve
if (next && next.command == 's') {
makeLonghand(next, data);
} // fix up next curve
}

@@ -788,3 +791,3 @@ }

}
// @ts-ignore
// @ts-expect-error
prev.coords = item.coords;

@@ -846,11 +849,11 @@ path[index] = prev;

const predictedControlPoint = reflectPoint(
// @ts-ignore
// @ts-expect-error
qControlPoint,
// @ts-ignore
// @ts-expect-error
item.base,
);
const realControlPoint = [
// @ts-ignore
// @ts-expect-error
data[0] + item.base[0],
// @ts-ignore
// @ts-expect-error
data[1] + item.base[1],

@@ -904,5 +907,5 @@ ];

if (
// @ts-ignore
// @ts-expect-error
Math.abs(pathBase[0] - item.coords[0]) < error &&
// @ts-ignore
// @ts-expect-error
Math.abs(pathBase[1] - item.coords[1]) < error

@@ -921,3 +924,5 @@ ) {

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

@@ -928,18 +933,19 @@ if (

isSafeToUseZ &&
// @ts-ignore
// @ts-expect-error
Math.abs(item.base[0] - item.coords[0]) < error / 10 &&
// @ts-ignore
// @ts-expect-error
Math.abs(item.base[1] - item.coords[1]) < error / 10
)
) {
return false;
}
if (command === 'q') {
// @ts-ignore
// @ts-expect-error
prevQControlPoint = [data[0] + item.base[0], data[1] + item.base[1]];
} else if (command === 't') {
if (qControlPoint) {
// @ts-ignore
// @ts-expect-error
prevQControlPoint = reflectPoint(qControlPoint, item.base);
} else {
// @ts-ignore
// @ts-expect-error
prevQControlPoint = item.coords;

@@ -960,9 +966,13 @@ }

*
* @type {(path: PathDataItem[], params: InternalParams) => PathDataItem[]}
* @param {import('../lib/types.js').PathDataItem[]} path
* @param {InternalParams} params
* @returns {import('../lib/types.js').PathDataItem[]}
*/
function convertToMixed(path, params) {
var prev = path[0];
let prev = path[0];
path = path.filter(function (item, index) {
if (index == 0) return true;
if (index == 0) {
return true;
}
if (item.command === 'Z' || item.command === 'z') {

@@ -973,6 +983,6 @@ prev = item;

var command = item.command,
data = item.args,
adata = data.slice(),
rdata = data.slice();
const command = item.command;
const data = item.args;
const adata = data.slice();
const rdata = data.slice();

@@ -987,16 +997,16 @@ if (

) {
for (var i = adata.length; i--; ) {
// @ts-ignore
for (let i = adata.length; i--; ) {
// @ts-expect-error
adata[i] += item.base[i % 2];
}
} else if (command == 'h') {
// @ts-ignore
// @ts-expect-error
adata[0] += item.base[0];
} else if (command == 'v') {
// @ts-ignore
// @ts-expect-error
adata[0] += item.base[1];
} else if (command == 'a') {
// @ts-ignore
// @ts-expect-error
adata[5] += item.base[0];
// @ts-ignore
// @ts-expect-error
adata[6] += item.base[1];

@@ -1008,4 +1018,4 @@ }

var absoluteDataStr = cleanupOutData(adata, params),
relativeDataStr = cleanupOutData(rdata, params);
const absoluteDataStr = cleanupOutData(adata, params);
const relativeDataStr = cleanupOutData(rdata, params);

@@ -1030,3 +1040,3 @@ // Convert to absolute coordinates if it's shorter or forceAbsolutePath is true.

) {
// @ts-ignore
// @ts-expect-error
item.command = command.toUpperCase();

@@ -1044,9 +1054,10 @@ item.args = adata;

/**
* Checks if curve is convex. Control points of such a curve must form
* a convex quadrilateral with diagonals crosspoint inside of it.
* Checks if curve is convex. Control points of such a curve must form a convex
* quadrilateral with diagonals crosspoint inside of it.
*
* @type {(data: number[]) => boolean}
* @param {ReadonlyArray<number>} data
* @returns {boolean}
*/
function isConvex(data) {
var center = getIntersection([
const center = getIntersection([
0,

@@ -1074,21 +1085,22 @@ 0,

*
* @type {(coords: number[]) => undefined | Point}
* @param {ReadonlyArray<number>} coords
* @returns {Point | undefined}
*/
function getIntersection(coords) {
// Prev line equation parameters.
var a1 = coords[1] - coords[3], // y1 - y2
b1 = coords[2] - coords[0], // x2 - x1
c1 = coords[0] * coords[3] - coords[2] * coords[1], // x1 * y2 - x2 * y1
// Next line equation parameters
a2 = coords[5] - coords[7], // y1 - y2
b2 = coords[6] - coords[4], // x2 - x1
c2 = coords[4] * coords[7] - coords[5] * coords[6], // x1 * y2 - x2 * y1
denom = a1 * b2 - a2 * b1;
const a1 = coords[1] - coords[3]; // y1 - y2
const b1 = coords[2] - coords[0]; // x2 - x1
const c1 = coords[0] * coords[3] - coords[2] * coords[1]; // x1 * y2 - x2 * y1
// Next line equation parameters
const a2 = coords[5] - coords[7]; // y1 - y2
const b2 = coords[6] - coords[4]; // x2 - x1
const c2 = coords[4] * coords[7] - coords[5] * coords[6]; // x1 * y2 - x2 * y1
const denom = a1 * b2 - a2 * b1;
if (!denom) return; // parallel lines haven't an intersection
if (!denom) {
return;
} // parallel lines haven't an intersection
/**
* @type {Point}
*/
var cross = [(b1 * c2 - b2 * c1) / denom, (a1 * c2 - a2 * c1) / -denom];
/** @type {Point} */
const cross = [(b1 * c2 - b2 * c1) / denom, (a1 * c2 - a2 * c1) / -denom];
if (

@@ -1105,8 +1117,8 @@ !isNaN(cross[0]) &&

/**
* Decrease accuracy of floating-point numbers
* in path data keeping a specified number of decimals.
* Smart rounds values like 2.3491 to 2.35 instead of 2.349.
* Decrease accuracy of floating-point numbers in path data keeping a specified
* number of decimals. Smart rounds values like 2.3491 to 2.35 instead of 2.349.
* Doesn't apply "smartness" if the number precision fits already.
*
* @type {(data: number[]) => number[]}
* @param {number[]} data
* @returns {number[]}
*/

@@ -1131,6 +1143,7 @@ function strongRound(data) {

*
* @type {(data: number[]) => number[]}
* @param {number[]} data
* @returns {number[]}
*/
function round(data) {
for (var i = data.length; i-- > 0; ) {
for (let i = data.length; i-- > 0; ) {
data[i] = Math.round(data[i]);

@@ -1142,20 +1155,24 @@ }

/**
* Checks if a curve is a straight line by measuring distance
* from middle points to the line formed by end points.
* Checks if a curve is a straight line by measuring distance from middle points
* to the line formed by end points.
*
* @type {(data: number[]) => boolean}
* @param {ReadonlyArray<number>} data
* @returns {boolean}
*/
function isCurveStraightLine(data) {
// Get line equation a·x + b·y + c = 0 coefficients a, b (c = 0) by start and end points.
var i = data.length - 2,
a = -data[i + 1], // y1 − y2 (y1 = 0)
b = data[i], // x2 − x1 (x1 = 0)
d = 1 / (a * a + b * b); // same part for all points
let i = data.length - 2;
const a = -data[i + 1]; // y1 − y2 (y1 = 0)
const b = data[i]; // x2 − x1 (x1 = 0)
const d = 1 / (a * a + b * b); // same part for all points
if (i <= 1 || !isFinite(d)) return false; // curve that ends at start point isn't the case
if (i <= 1 || !isFinite(d)) {
return false;
} // curve that ends at start point isn't the case
// Distance from point (x0, y0) to the line is sqrt((c − a·x0 − b·y0)² / (a² + b²))
while ((i -= 2) >= 0) {
if (Math.sqrt(Math.pow(a * data[i] + b * data[i + 1], 2) * d) > error)
if (Math.sqrt(Math.pow(a * data[i] + b * data[i + 1], 2) * d) > error) {
return false;
}
}

@@ -1169,11 +1186,18 @@

*
* @type {(data: number[]) => number | undefined}
* @see https://wikipedia.org/wiki/Sagitta_(geometry)#Formulas
* @param {ReadonlyArray<number>} data
* @returns {number | undefined}
*/
function calculateSagitta(data) {
if (data[3] === 1) return undefined;
if (data[3] === 1) {
return undefined;
}
const [rx, ry] = data;
if (Math.abs(rx - ry) > error) return undefined;
if (Math.abs(rx - ry) > error) {
return undefined;
}
const chord = Math.hypot(data[5], data[6]);
if (chord > rx * 2) return undefined;
if (chord > rx * 2) {
return undefined;
}
return rx - Math.sqrt(rx ** 2 - 0.25 * chord ** 2);

@@ -1185,3 +1209,5 @@ }

*
* @type {(item: PathDataItem, data: number[]) => PathDataItem}
* @param {import('../lib/types.js').PathDataItem} item
* @param {ReadonlyArray<number>} data
* @returns {import('../lib/types.js').PathDataItem}
*/

@@ -1207,3 +1233,5 @@ function makeLonghand(item, data) {

*
* @type {(point1: Point, point2: Point) => number}
* @param {Point} point1
* @param {Point} point2
* @returns {number}
*/

@@ -1230,9 +1258,11 @@ function getDistance(point1, point2) {

*
* @type {(curve: number[], t: number) => Point}
* @param {ReadonlyArray<number>} curve
* @param {number} t
* @returns {Point}
*/
function getCubicBezierPoint(curve, t) {
var sqrT = t * t,
cubT = sqrT * t,
mt = 1 - t,
sqrMt = mt * mt;
const sqrT = t * t;
const cubT = sqrT * t;
const mt = 1 - t;
const sqrMt = mt * mt;

@@ -1248,25 +1278,29 @@ return [

*
* @type {(curve: number[]) => undefined | Circle}
* @param {ReadonlyArray<number>} curve
* @returns {Circle | undefined}
*/
function findCircle(curve) {
var midPoint = getCubicBezierPoint(curve, 1 / 2),
m1 = [midPoint[0] / 2, midPoint[1] / 2],
m2 = [(midPoint[0] + curve[4]) / 2, (midPoint[1] + curve[5]) / 2],
center = getIntersection([
m1[0],
m1[1],
m1[0] + m1[1],
m1[1] - m1[0],
m2[0],
m2[1],
m2[0] + (m2[1] - midPoint[1]),
m2[1] - (m2[0] - midPoint[0]),
]),
radius = center && getDistance([0, 0], center),
// @ts-ignore
tolerance = Math.min(arcThreshold * error, (arcTolerance * radius) / 100);
const midPoint = getCubicBezierPoint(curve, 1 / 2);
const m1 = [midPoint[0] / 2, midPoint[1] / 2];
const m2 = [(midPoint[0] + curve[4]) / 2, (midPoint[1] + curve[5]) / 2];
const center = getIntersection([
m1[0],
m1[1],
m1[0] + m1[1],
m1[1] - m1[0],
m2[0],
m2[1],
m2[0] + (m2[1] - midPoint[1]),
m2[1] - (m2[0] - midPoint[0]),
]);
const radius = center && getDistance([0, 0], center);
const tolerance = Math.min(
arcThreshold * error,
// @ts-expect-error
(arcTolerance * radius) / 100,
);
if (
center &&
// @ts-ignore
// @ts-expect-error
radius < 1e15 &&

@@ -1276,3 +1310,3 @@ [1 / 4, 3 / 4].every(function (point) {

Math.abs(
// @ts-ignore
// @ts-expect-error
getDistance(getCubicBezierPoint(curve, point), center) - radius,

@@ -1282,5 +1316,6 @@ ) <= tolerance

})
)
// @ts-ignore
) {
// @ts-expect-error
return { center: center, radius: radius };
}
}

@@ -1291,6 +1326,8 @@

*
* @type {(curve: number[], circle: Circle) => boolean}
* @param {ReadonlyArray<number>} curve
* @param {Circle} circle
* @returns {boolean}
*/
function isArc(curve, circle) {
var tolerance = Math.min(
const tolerance = Math.min(
arcThreshold * error,

@@ -1313,3 +1350,5 @@ (arcTolerance * circle.radius) / 100,

*
* @type {(curve: number[], circle: Circle) => boolean}
* @param {ReadonlyArray<number>} curve
* @param {Circle} circle
* @returns {boolean}
*/

@@ -1325,10 +1364,12 @@ function isArcPrev(curve, circle) {

* Finds angle of a curve fitting the given arc.
* @type {(curve: number[], relCircle: Circle) => number}
*
* @param {ReadonlyArray<number>} curve
* @param {Circle} relCircle
* @returns {number}
*/
function findArcAngle(curve, relCircle) {
var x1 = -relCircle.center[0],
y1 = -relCircle.center[1],
x2 = curve[4] - relCircle.center[0],
y2 = curve[5] - relCircle.center[1];
const x1 = -relCircle.center[0];
const y1 = -relCircle.center[1];
const x2 = curve[4] - relCircle.center[0];
const y2 = curve[5] - relCircle.center[1];

@@ -1343,7 +1384,9 @@ return Math.acos(

*
* @type {(params: InternalParams, pathData: PathDataItem[]) => string}
* @param {InternalParams} params
* @param {ReadonlyArray<import('../lib/types.js').PathDataItem>} pathData
* @returns {string}
*/
function data2Path(params, pathData) {
return pathData.reduce(function (pathString, item) {
var strData = '';
let strData = '';
if (item.args) {

@@ -1350,0 +1393,0 @@ strData = cleanupOutData(roundData(item.args.slice()), params);

@@ -5,3 +5,5 @@ import { stringifyPathData } from '../lib/path.js';

/**
* @typedef {import('../lib/types.js').PathDataItem} PathDataItem
* @typedef ConvertShapeToPathParams
* @property {boolean=} convertArcs
* @property {number=} floatPrecision
*/

@@ -15,5 +17,4 @@

/**
* Converts basic shape to more compact path.
* It also allows further optimizations like
* combining paths with similar attributes.
* Converts basic shape to more compact path. It also allows further
* optimizations like combining paths with similar attributes.
*

@@ -24,3 +25,3 @@ * @see https://www.w3.org/TR/SVG11/shapes.html

*
* @type {import('./plugins-types.js').Plugin<'convertShapeToPath'>}
* @type {import('../lib/types.js').Plugin<ConvertShapeToPathParams>}
*/

@@ -48,6 +49,6 @@ export const fn = (root, params) => {

// TODO: Calculate sizes from % and non-px units if possible.
if (Number.isNaN(x - y + width - height)) return;
/**
* @type {PathDataItem[]}
*/
if (Number.isNaN(x - y + width - height)) {
return;
}
/** @type {import('../lib/types.js').PathDataItem[]} */
const pathData = [

@@ -74,6 +75,6 @@ { command: 'M', args: [x, y] },

const y2 = Number(node.attributes.y2 || '0');
if (Number.isNaN(x1 - y1 + x2 - y2)) return;
/**
* @type {PathDataItem[]}
*/
if (Number.isNaN(x1 - y1 + x2 - y2)) {
return;
}
/** @type {import('../lib/types.js').PathDataItem[]} */
const pathData = [

@@ -103,5 +104,3 @@ { command: 'M', args: [x1, y1] },

}
/**
* @type {PathDataItem[]}
*/
/** @type {import('../lib/types.js').PathDataItem[]} */
const pathData = [];

@@ -130,5 +129,3 @@ for (let i = 0; i < coords.length; i += 2) {

}
/**
* @type {PathDataItem[]}
*/
/** @type {import('../lib/types.js').PathDataItem[]} */
const pathData = [

@@ -156,5 +153,3 @@ { command: 'M', args: [cx, cy - r] },

}
/**
* @type {PathDataItem[]}
*/
/** @type {import('../lib/types.js').PathDataItem[]} */
const pathData = [

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

import { attrsGroups } from './_collections.js';
/**
* @typedef ConvertStyleToAttrsParams
* @property {boolean=} keepImportant
*/
export const name = 'convertStyleToAttrs';

@@ -7,3 +12,4 @@ export const description = 'converts style to attributes';

/**
* @type {(...args: string[]) => string}
* @param {...string} args
* @returns {string}
*/

@@ -16,5 +22,12 @@ const g = (...args) => {

const rEscape = '\\\\(?:[0-9a-f]{1,6}\\s?|\\r\\n|.)'; // Like \" or \2051. Code points consume one space.
const rAttr = '\\s*(' + g('[^:;\\\\]', rEscape) + '*?)\\s*'; // attribute name like ‘fill’
const rSingleQuotes = "'(?:[^'\\n\\r\\\\]|" + rEscape + ")*?(?:'|$)"; // string in single quotes: 'smth'
const rQuotes = '"(?:[^"\\n\\r\\\\]|' + rEscape + ')*?(?:"|$)'; // string in double quotes: "smth"
/** Pattern to match attribute name like: 'fill' */
const rAttr = '\\s*(' + g('[^:;\\\\]', rEscape) + '*?)\\s*';
/** Pattern to match string in single quotes like: 'foo' */
const rSingleQuotes = "'(?:[^'\\n\\r\\\\]|" + rEscape + ")*?(?:'|$)";
/** Pattern to match string in double quotes like: "foo" */
const rQuotes = '"(?:[^"\\n\\r\\\\]|' + rEscape + ')*?(?:"|$)';
const rQuotedString = new RegExp('^' + g(rSingleQuotes, rQuotes) + '$');

@@ -58,3 +71,3 @@ // Parentheses, E.g.: url(data:image/png;base64,iVBO...).

* <g style="fill:#000; color: #fff;">
* ⬇
* ⬇
* <g fill="#000" color="#fff">

@@ -64,3 +77,3 @@ *

* <g style="fill:#000; color: #fff; -webkit-blah: blah">
* ⬇
* ⬇
* <g fill="#000" color="#fff" style="-webkit-blah: blah">

@@ -70,3 +83,3 @@ *

*
* @type {import('./plugins-types.js').Plugin<'convertStyleToAttrs'>}
* @type {import('../lib/types.js').Plugin<ConvertStyleToAttrsParams>}
*/

@@ -81,5 +94,3 @@ export const fn = (_root, params) => {

let styles = [];
/**
* @type {Record<string, string>}
*/
/** @type {Record<string, string>} */
const newAttributes = {};

@@ -109,4 +120,4 @@

if (style[0]) {
var prop = style[0].toLowerCase(),
val = style[1];
const prop = style[0].toLowerCase();
let val = style[1];

@@ -113,0 +124,0 @@ if (rQuotedString.test(val)) {

@@ -10,5 +10,33 @@ import {

/**
* @typedef {import('../lib/types.js').XastChild} XastChild
* @typedef {import('../lib/types.js').XastElement} XastElement
* @typedef {import('../lib/types.js').XastParent} XastParent
* @typedef ConvertTransformParams
* @property {boolean=} convertToShorts
* @property {number=} degPrecision
* @property {number=} floatPrecision
* @property {number=} transformPrecision
* @property {boolean=} matrixToTransform
* @property {boolean=} shortTranslate
* @property {boolean=} shortScale
* @property {boolean=} shortRotate
* @property {boolean=} removeUseless
* @property {boolean=} collapseIntoOne
* @property {boolean=} leadingZero
* @property {boolean=} negativeExtraSpace
*
* @typedef TransformParams
* @property {boolean} convertToShorts
* @property {number=} degPrecision
* @property {number} floatPrecision
* @property {number} transformPrecision
* @property {boolean} matrixToTransform
* @property {boolean} shortTranslate
* @property {boolean} shortScale
* @property {boolean} shortRotate
* @property {boolean} removeUseless
* @property {boolean} collapseIntoOne
* @property {boolean} leadingZero
* @property {boolean} negativeExtraSpace
*
* @typedef TransformItem
* @property {string} name
* @property {number[]} data
*/

@@ -30,3 +58,3 @@

*
* @type {import('./plugins-types.js').Plugin<'convertTransform'>}
* @type {import('../lib/types.js').Plugin<ConvertTransformParams>}
*/

@@ -83,24 +111,3 @@ export const fn = (_root, 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
*/
/**
* @typedef {{ name: string, data: number[] }} TransformItem
*/
/**
* @param {XastElement} item
* @param {import('../lib/types.js').XastElement} item
* @param {string} attrName

@@ -136,10 +143,13 @@ * @param {TransformParams} params

* Defines precision to work with certain parts.
* transformPrecision - for scale and four first matrix parameters (needs a better precision due to multiplying),
* floatPrecision - for translate including two last matrix and rotate parameters,
* degPrecision - for rotate and skew. By default it's equal to (roughly)
* transformPrecision - 2 or floatPrecision whichever is lower. Can be set in params.
*
* @type {(data: TransformItem[], params: TransformParams) => TransformParams}
* - `transformPrecision` - for scale and four first matrix parameters (needs a better precision due to multiplying).
* - `floatPrecision` - for translate including two last matrix and rotate parameters.
* - `degPrecision` - for rotate and skew. By default it's equal to (roughly).
* - `transformPrecision` - 2 or floatPrecision whichever is lower. Can be set in params.
*
* clone params so it don't affect other elements transformations.
* Clone parameters so that it doesn't affect other element transformations.
*
* @param {ReadonlyArray<TransformItem>} data
* @param {TransformParams} param1
* @returns {TransformParams}
*/

@@ -168,3 +178,3 @@ const definePrecision = (data, { ...newParams }) => {

}
// No sense in angle precision more then number of significant digits in matrix.
// No sense in angle precision more than number of significant digits in matrix.
if (newParams.degPrecision == null) {

@@ -180,5 +190,7 @@ newParams.degPrecision = Math.max(

/**
* Returns number of digits after the point. 0.125 → 3
* Returns number of digits after the point.
*
* @type {(n: number) => number}
* @example 0.125 → 3
* @param {number} n
* @returns {number}
*/

@@ -198,3 +210,3 @@ const floatDigits = (n) => {

const convertToShorts = (transforms, params) => {
for (var i = 0; i < transforms.length; i++) {
for (let i = 0; i < transforms.length; i++) {
let transform = transforms[i];

@@ -204,3 +216,3 @@

if (params.matrixToTransform && transform.name === 'matrix') {
var decomposed = matrixToTransform(transform, params);
const decomposed = matrixToTransform(transform, params);
if (

@@ -271,3 +283,4 @@ js2transform(decomposed, params).length <=

*
* @type {(transforms: TransformItem[]) => TransformItem[]}
* @param {ReadonlyArray<TransformItem>} transforms
* @returns {TransformItem[]}
*/

@@ -274,0 +287,0 @@ const removeUseless = (transforms) => {

@@ -5,5 +5,5 @@ import * as csstree from 'css-tree';

import {
detachNodeFromParent,
querySelectorAll,
visitSkip,
querySelectorAll,
detachNodeFromParent,
} from '../lib/xast.js';

@@ -13,4 +13,12 @@ import { compareSpecificity, includesAttrSelector } from '../lib/style.js';

/**
* @typedef {import('../lib/types.js').XastElement} XastElement
* @typedef {import('../lib/types.js').XastParent} XastParent
* @typedef InlineStylesParams
* @property {boolean=} onlyMatchedOnce Inlines selectors that match once only.
* @property {boolean=} removeMatchedSelectors
* Clean up matched selectors. Unused selects are left as-is.
* @property {string[]=} useMqs
* Media queries to use. An empty string indicates all selectors outside of
* media queries.
* @property {string[]=} usePseudos
* Pseudo-classes and elements to use. An empty string indicates all
* non-pseudo-classes and elements.
*/

@@ -26,4 +34,4 @@

*
* The list of pseudo-classes that we can evaluate during optimization, and
* shouldn't be toggled conditionally through the `usePseudos` parameter.
* Pseudo-classes that we can evaluate during optimization, and shouldn't be
* toggled conditionally through the `usePseudos` parameter.
*

@@ -40,3 +48,3 @@ * @see https://developer.mozilla.org/docs/Web/CSS/Pseudo-classes

*
* @type {import('./plugins-types.js').Plugin<'inlineStyles'>}
* @type {import('../lib/types.js').Plugin<InlineStylesParams>}
* @author strarsis <strarsis@gmail.com>

@@ -53,3 +61,7 @@ */

/**
* @type {{ node: XastElement, parentNode: XastParent, cssAst: csstree.StyleSheet }[]}
* @type {{
* node: import('../lib/types.js').XastElement,
* parentNode: import('../lib/types.js').XastParent,
* cssAst: csstree.StyleSheet
* }[]}
*/

@@ -62,6 +74,6 @@ const styles = [];

* rule: csstree.Rule,
* matchedElements?: XastElement[]
* matchedElements?: import('../lib/types.js').XastElement[]
* }[]}
*/
let selectors = [];
const selectors = [];

@@ -87,3 +99,2 @@ return {

.filter((child) => child.type === 'text' || child.type === 'cdata')
// @ts-ignore
.map((child) => child.value)

@@ -192,3 +203,3 @@ .join('');

const selectorText = csstree.generate(selector.item.data);
/** @type {XastElement[]} */
/** @type {import('../lib/types.js').XastElement[]} */
const matchedElements = [];

@@ -379,3 +390,2 @@ try {

// csstree v2 changed this type
if (style.cssAst.children.isEmpty) {

@@ -382,0 +392,0 @@ // remove empty style element

@@ -0,14 +1,12 @@

import { collectStylesheet, computeStyle } from '../lib/style.js';
import { intersects, js2path, path2js } from './_path.js';
import { includesUrlReference } from '../lib/svgo/tools.js';
/**
* @typedef {import('../lib/types.js').ComputedStyles} ComputedStyles
* @typedef {import('../lib/types.js').StaticStyle} StaticStyle
* @typedef {import('../lib/types.js').DynamicStyle} DynamicStyle
* @typedef {import("../lib/types.js").PathDataItem} PathDataItem
* @typedef {import('../lib/types.js').XastChild} XastChild
* @typedef {import('../lib/types.js').XastElement} XastElement
* @typedef MergePathsParams
* @property {boolean=} force
* @property {number=} floatPrecision
* @property {boolean=} noSpaceAfterFlags
*/
import { collectStylesheet, computeStyle } from '../lib/style.js';
import { path2js, js2path, intersects } from './_path.js';
import { includesUrlReference } from '../lib/svgo/tools.js';
export const name = 'mergePaths';

@@ -18,3 +16,3 @@ export const description = 'merges multiple paths in one if possible';

/**
* @param {ComputedStyles} computedStyle
* @param {import('../lib/types.js').ComputedStyles} computedStyle
* @param {string} attName

@@ -38,3 +36,3 @@ * @returns {boolean}

*
* @type {import('./plugins-types.js').Plugin<'mergePaths'>}
* @type {import('../lib/types.js').Plugin<MergePathsParams>}
*/

@@ -56,3 +54,3 @@ export const fn = (root, params) => {

/** @type {XastChild[]} */
/** @type {import('../lib/types.js').XastChild[]} */
const elementsToRemove = [];

@@ -63,4 +61,4 @@ let prevChild = node.children[0];

/**
* @param {XastElement} child
* @param {PathDataItem[]} pathData
* @param {import('../lib/types.js').XastElement} child
* @param {ReadonlyArray<import("../lib/types.js").PathDataItem>} pathData
*/

@@ -67,0 +65,0 @@ const updatePreviousPath = (child, pathData) => {

@@ -1,8 +0,3 @@

import { visitSkip, detachNodeFromParent } from '../lib/xast.js';
import { detachNodeFromParent, visitSkip } from '../lib/xast.js';
/**
* @typedef {import('../lib/types.js').XastElement} XastElement
* @typedef {import('../lib/types.js').XastChild} XastChild
*/
export const name = 'mergeStyles';

@@ -16,13 +11,9 @@ export const description = 'merge multiple style elements into one';

*
* @type {import('./plugins-types.js').Plugin<'mergeStyles'>}
* @type {import('../lib/types.js').Plugin}
*/
export const fn = () => {
/**
* @type {?XastElement}
*/
/** @type {?import('../lib/types.js').XastElement} */
let firstStyleElement = null;
let collectedStyles = '';
/**
* @type {'text' | 'cdata'}
*/
/** @type {'text' | 'cdata'} */
let styleContentType = 'text';

@@ -83,11 +74,4 @@

detachNodeFromParent(node, parentNode);
/**
* @type {XastChild}
*/
/** @type {import('../lib/types.js').XastChild} */
const child = { type: styleContentType, value: collectedStyles };
// TODO remove legacy parentNode in v4
Object.defineProperty(child, 'parentNode', {
writable: true,
value: firstStyleElement,
});
firstStyleElement.children = [child];

@@ -94,0 +78,0 @@ }

@@ -1,6 +0,1 @@

/**
* @typedef {import('../lib/types.js').XastElement} XastElement
* @typedef {import('../lib/types.js').XastParent} XastParent
*/
import * as csso from 'csso';

@@ -10,2 +5,23 @@ import { detachNodeFromParent } from '../lib/xast.js';

/**
* @typedef Usage
* @property {boolean=} force
* @property {boolean=} ids
* @property {boolean=} classes
* @property {boolean=} tags
*
* @typedef MinifyStylesParams
* @property {boolean=} restructure Disable or enable a structure optimizations.
* @property {boolean=} forceMediaMerge
* Enables merging of `@media` rules with the same media query split by other
* rules. Unsafe in general, but should work fine in most cases. Use it on
* your own risk.
* @property {'exclamation' | 'first-exclamation' | boolean=} comments
* Specify what comments to leave:
* - `'exclamation'` or `true` — leave all exclamation comments
* - `'first-exclamation'` — remove every comment except first one
* - `false` — remove all comments
* @property {boolean | Usage=} usage Advanced optimizations.
*/
export const name = 'minifyStyles';

@@ -18,9 +34,9 @@ export const description = 'minifies styles and removes unused styles';

* @author strarsis <strarsis@gmail.com>
* @type {import('./plugins-types.js').Plugin<'minifyStyles'>}
* @type {import('../lib/types.js').Plugin<MinifyStylesParams>}
*/
export const fn = (_root, { usage, ...params }) => {
/** @type {Map<XastElement, XastParent>} */
/** @type {Map<import('../lib/types.js').XastElement, import('../lib/types.js').XastParent>} */
const styleElements = new Map();
/** @type {XastElement[]} */
/** @type {import('../lib/types.js').XastElement[]} */
const elementsWithStyleAttributes = [];

@@ -43,3 +59,3 @@

* Force to use usage data even if it unsafe. For example, the document
* contains scripts or in attributes..
* contains scripts or in attributes.
*/

@@ -64,3 +80,3 @@ let forceUsageDeoptimized = false;

enter: (node, parentNode) => {
// detect deoptimisations
// detect deoptimizations
if (hasScripts(node)) {

@@ -122,3 +138,3 @@ deoptimized = true;

// preserve cdata if necessary
// TODO split cdata -> text optimisation into separate plugin
// TODO split cdata -> text optimization into separate plugin
if (cssText.indexOf('>') >= 0 || cssText.indexOf('<') >= 0) {

@@ -125,0 +141,0 @@ styleNode.children[0].type = 'cdata';

@@ -18,3 +18,3 @@ import { visit } from '../lib/xast.js';

* </g>
* ⬇
* ⬇
* <g attr1="val1" attr2="val2">

@@ -29,3 +29,3 @@ * <g>

*
* @type {import('./plugins-types.js').Plugin<'moveElemsAttrsToGroup'>}
* @type {import('../lib/types.js').Plugin}
*/

@@ -48,3 +48,3 @@ export const fn = (root) => {

exit: (node) => {
// process only groups with more than 1 children
// process only groups with more than 1 child
if (node.name !== 'g' || node.children.length <= 1) {

@@ -61,3 +61,4 @@ return;

/**
* find common attributes in group children
* Find common attributes in group children.
*
* @type {Map<string, string>}

@@ -64,0 +65,0 @@ */

@@ -18,3 +18,3 @@ import { pathElems, referencesProps } from './_collections.js';

* </g>
* ⬇
* ⬇
* <g>

@@ -27,3 +27,3 @@ * <path transform="scale(2) rotate(45)" d="M0,0 L10,20"/>

*
* @type {import('./plugins-types.js').Plugin<'moveGroupAttrsToElems'>}
* @type {import('../lib/types.js').Plugin}
*/

@@ -30,0 +30,0 @@ export const fn = () => {

@@ -5,4 +5,7 @@ import * as csstree from 'css-tree';

/**
* @typedef {import('../lib/types.js').PluginInfo} PluginInfo
* @typedef {import('../lib/types.js').XastElement} XastElement
* @typedef PrefixIdsParams
* @property {boolean | string | ((node: import('../lib/types.js').XastElement, info: import('../lib/types.js').PluginInfo) => string)=} prefix
* @property {string=} delim
* @property {boolean=} prefixIds
* @property {boolean=} prefixClassNames
*/

@@ -14,4 +17,6 @@

/**
* extract basename from path
* @type {(path: string) => string}
* Extract basename from path.
*
* @param {string} path
* @returns {string}
*/

@@ -28,4 +33,6 @@ const getBasename = (path) => {

/**
* escapes a string for being used as ID
* @type {(string: string) => string}
* Escapes a string for being used as ID.
*
* @param {string} str
* @returns {string}
*/

@@ -37,3 +44,4 @@ const escapeIdentifierName = (str) => {

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

@@ -84,5 +92,5 @@ const unquote = (string) => {

* @param {string} body An arbitrary string.
* @param {XastElement} node XML node that the identifier belongs to.
* @param {PluginInfo} info
* @param {((node: XastElement, info: PluginInfo) => string)|string|boolean|undefined} prefixGenerator Some way of obtaining a prefix.
* @param {import('../lib/types.js').XastElement} node XML node that the identifier belongs to.
* @param {import('../lib/types.js').PluginInfo} info
* @param {((node: import('../lib/types.js').XastElement, info: import('../lib/types.js').PluginInfo) => string) | string | boolean | undefined} prefixGenerator Some way of obtaining a prefix.
* @param {string} delim Content to insert between the prefix and original value.

@@ -124,3 +132,3 @@ * @param {Map<string, string>} history Map of previously generated prefixes to IDs.

* @author strarsis <strarsis@gmail.com>
* @type {import('./plugins-types.js').Plugin<'prefixIds'>}
* @type {import('../lib/types.js').Plugin<PrefixIdsParams>}
*/

@@ -162,3 +170,3 @@ export const fn = (_root, params, info) => {

/** @type {?csstree.CssNode} */
let cssAst = null;
let cssAst;
try {

@@ -217,3 +225,3 @@ cssAst = csstree.parse(cssText, {

// prefix a href attribute value
// prefix an href attribute value
// xlink:href is deprecated, must be still supported

@@ -220,0 +228,0 @@ for (const name of ['href', 'xlink:href']) {

@@ -74,3 +74,3 @@ import { querySelectorAll } from '../lib/xast.js';

*
* @type {import('./plugins-types.js').Plugin<'removeAttributesBySelector'>}
* @type {import('../lib/types.js').Plugin<any>}
*/

@@ -77,0 +77,0 @@ export const fn = (root, params) => {

@@ -0,1 +1,8 @@

/**
* @typedef RemoveAttrsParams
* @property {string=} elemSeparator
* @property {boolean=} preserveCurrentColor
* @property {string | string[]} attrs
*/
export const name = 'removeAttrs';

@@ -82,3 +89,3 @@ export const description = 'removes specified attributes';

*
* @type {import('./plugins-types.js').Plugin<'removeAttrs'>}
* @type {import('../lib/types.js').Plugin<RemoveAttrsParams>}
*/

@@ -85,0 +92,0 @@ export const fn = (root, params) => {

import { detachNodeFromParent } from '../lib/xast.js';
/**
* @typedef RemoveCommentsParams
* @property {ReadonlyArray<RegExp | string> | false=} preservePatterns
*/
export const name = 'removeComments';

@@ -21,3 +26,3 @@ export const description = 'removes comments';

*
* @type {import('./plugins-types.js').Plugin<'removeComments'>}
* @type {import('../lib/types.js').Plugin<RemoveCommentsParams>}
*/

@@ -24,0 +29,0 @@ export const fn = (_root, params) => {

@@ -5,2 +5,7 @@ import * as csswhat from 'css-what';

/**
* @typedef RemoveDeprecatedAttrsParams
* @property {boolean=} removeUnsafe
*/
export const name = 'removeDeprecatedAttrs';

@@ -10,7 +15,2 @@ export const description = 'removes deprecated attributes';

/**
* @typedef {{ safe?: Set<string>; unsafe?: Set<string> }} DeprecatedAttrs
* @typedef {import('../lib/types.js').XastElement} XastElement
*/
/**
* @param {import('../lib/types.js').Stylesheet} stylesheet

@@ -39,5 +39,5 @@ * @returns {Set<string>}

/**
* @param {XastElement} node
* @param {DeprecatedAttrs | undefined} deprecatedAttrs
* @param {import('./plugins-types.js').DefaultPlugins['removeDeprecatedAttrs']} params
* @param {import('../lib/types.js').XastElement} node
* @param {{ safe?: Set<string>; unsafe?: Set<string> }|undefined} deprecatedAttrs
* @param {import('../lib/types.js').DefaultPlugins['removeDeprecatedAttrs']} params
* @param {Set<string>} attributesInStylesheet

@@ -77,3 +77,3 @@ */

*
* @type {import('./plugins-types.js').Plugin<'removeDeprecatedAttrs'>}
* @type {import('../lib/types.js').Plugin<RemoveDeprecatedAttrsParams>}
*/

@@ -80,0 +80,0 @@ export function fn(root, params) {

import { detachNodeFromParent } from '../lib/xast.js';
/**
* @typedef RemoveDescParams
* @property {boolean=} removeAny
*/
export const name = 'removeDesc';

@@ -17,3 +22,3 @@ export const description = 'removes <desc>';

*
* @type {import('./plugins-types.js').Plugin<'removeDesc'>}
* @type {import('../lib/types.js').Plugin<RemoveDescParams>}
*/

@@ -20,0 +25,0 @@ export const fn = (root, params) => {

@@ -15,3 +15,3 @@ export const name = 'removeDimensions';

*
* @type {import('./plugins-types.js').Plugin<'removeDimensions'>}
* @type {import('../lib/types.js').Plugin}
*/

@@ -18,0 +18,0 @@ export const fn = () => {

@@ -28,3 +28,3 @@ import { detachNodeFromParent } from '../lib/xast.js';

*
* @type {import('./plugins-types.js').Plugin<'removeDoctype'>}
* @type {import('../lib/types.js').Plugin}
*/

@@ -31,0 +31,0 @@ export const fn = () => {

import { editorNamespaces } from './_collections.js';
import { detachNodeFromParent } from '../lib/xast.js';
/**
* @typedef RemoveEditorsNSDataParams
* @property {string[]=} additionalNamespaces
*/
export const name = 'removeEditorsNSData';

@@ -18,3 +23,3 @@ export const description =

*
* @type {import('./plugins-types.js').Plugin<'removeEditorsNSData'>}
* @type {import('../lib/types.js').Plugin<RemoveEditorsNSDataParams>}
*/

@@ -26,5 +31,3 @@ export const fn = (_root, params) => {

}
/**
* @type {string[]}
*/
/** @type {string[]} */
const prefixes = [];

@@ -31,0 +34,0 @@ return {

import { detachNodeFromParent } from '../lib/xast.js';
/**
* @typedef RemoveElementsByAttrParams
* @property {string | string[]=} id
* @property {string | string[]=} class
*/
export const name = 'removeElementsByAttr';

@@ -38,3 +44,3 @@ export const description =

*
* @type {import('./plugins-types.js').Plugin<'removeElementsByAttr'>}
* @type {import('../lib/types.js').Plugin<RemoveElementsByAttrParams>}
*/

@@ -41,0 +47,0 @@ export const fn = (root, params) => {

@@ -11,3 +11,3 @@ import { attrsGroups } from './_collections.js';

*
* @type {import('./plugins-types.js').Plugin<'removeEmptyAttrs'>}
* @type {import('../lib/types.js').Plugin}
*/

@@ -14,0 +14,0 @@ export const fn = () => {

import { elemsGroups } from './_collections.js';
import { detachNodeFromParent } from '../lib/xast.js';
import { collectStylesheet, computeStyle } from '../lib/style.js';

@@ -20,5 +21,7 @@ export const name = 'removeEmptyContainers';

*
* @type {import('./plugins-types.js').Plugin<'removeEmptyContainers'>}
* @type {import('../lib/types.js').Plugin}
*/
export const fn = () => {
export const fn = (root) => {
const stylesheet = collectStylesheet(root);
return {

@@ -42,7 +45,3 @@ element: {

}
// The <g> may not have content, but the filter may cause a rectangle
// to be created and filled with pattern.
if (node.name === 'g' && node.attributes.filter != null) {
return;
}
// empty <mask> hides masked element

@@ -55,2 +54,13 @@ if (node.name === 'mask' && node.attributes.id != null) {

}
// The <g> may not have content, but the filter may cause a rectangle
// to be created and filled with pattern.
if (
node.name === 'g' &&
(node.attributes.filter != null ||
computeStyle(stylesheet, node).filter)
) {
return;
}
detachNodeFromParent(node, parentNode);

@@ -57,0 +67,0 @@ },

import { detachNodeFromParent } from '../lib/xast.js';
/**
* @typedef RemoveEmptyTextParams
* @property {boolean=} text
* @property {boolean=} tspan
* @property {boolean=} tref
*/
export const name = 'removeEmptyText';

@@ -23,3 +30,3 @@ export const description = 'removes empty <text> elements';

*
* @type {import('./plugins-types.js').Plugin<'removeEmptyText'>}
* @type {import('../lib/types.js').Plugin<RemoveEmptyTextParams>}
*/

@@ -26,0 +33,0 @@ export const fn = (root, params) => {

@@ -1,18 +0,31 @@

/**
* @typedef {import('../lib/types.js').XastChild} XastChild
* @typedef {import('../lib/types.js').XastElement} XastElement
* @typedef {import('../lib/types.js').XastParent} XastParent
*/
import { elemsGroups } from './_collections.js';
import {
detachNodeFromParent,
querySelector,
visit,
visitSkip,
querySelector,
detachNodeFromParent,
} from '../lib/xast.js';
import { collectStylesheet, computeStyle } from '../lib/style.js';
import { parsePathData } from '../lib/path.js';
import { hasScripts, findReferences } from '../lib/svgo/tools.js';
import { findReferences, hasScripts } from '../lib/svgo/tools.js';
/**
* @typedef RemoveHiddenElemsParams
* @property {boolean=} isHidden
* @property {boolean=} displayNone
* @property {boolean=} opacity0
* @property {boolean=} circleR0
* @property {boolean=} ellipseRX0
* @property {boolean=} ellipseRY0
* @property {boolean=} rectWidth0
* @property {boolean=} rectHeight0
* @property {boolean=} patternWidth0
* @property {boolean=} patternHeight0
* @property {boolean=} imageWidth0
* @property {boolean=} imageHeight0
* @property {boolean=} pathEmptyD
* @property {boolean=} polylineEmptyPoints
* @property {boolean=} polygonEmptyPoints
*/
const nonRendering = elemsGroups.nonRendering;

@@ -39,3 +52,3 @@

*
* @type {import('./plugins-types.js').Plugin<'removeHiddenElems'>}
* @type {import('../lib/types.js').Plugin<RemoveHiddenElemsParams>}
*/

@@ -66,3 +79,3 @@ export const fn = (root, params) => {

*
* @type {Map<XastElement, XastParent>}
* @type {Map<import('../lib/types.js').XastElement, import('../lib/types.js').XastParent>}
*/

@@ -78,5 +91,3 @@ const nonRenderedNodes = new Map();

/**
* @type {Map<XastElement, XastParent>}
*/
/** @type {Map<import('../lib/types.js').XastElement, import('../lib/types.js').XastParent>} */
const allDefs = new Map();

@@ -87,5 +98,3 @@

/**
* @type {Map<string, Array<{ node: XastElement, parentNode: XastParent }>>}
*/
/** @type {Map<string, Array<{ node: import('../lib/types.js').XastElement, parentNode: import('../lib/types.js').XastParent }>>} */
const referencesById = new Map();

@@ -100,3 +109,3 @@

* Nodes can't be removed if they or any of their children have an id attribute that is referenced.
* @param {XastElement} node
* @param {import('../lib/types.js').XastElement} node
* @returns boolean

@@ -117,4 +126,4 @@ */

/**
* @param {XastChild} node
* @param {XastParent} parentNode
* @param {import('../lib/types.js').XastChild} node
* @param {import('../lib/types.js').XastParent} parentNode
*/

@@ -179,3 +188,5 @@ function removeElement(node, parentNode) {

for (const attr of Object.keys(node.attributes)) {
if (attr !== 'href' && !attr.endsWith(':href')) continue;
if (attr !== 'href' && !attr.endsWith(':href')) {
continue;
}
const value = node.attributes[attr];

@@ -182,0 +193,0 @@ const id = value.slice(1);

@@ -13,3 +13,3 @@ import { detachNodeFromParent } from '../lib/xast.js';

*
* @type {import('./plugins-types.js').Plugin<'removeMetadata'>}
* @type {import('../lib/types.js').Plugin}
*/

@@ -16,0 +16,0 @@ export const fn = () => {

import {
attrsGroups,
inheritableAttrs,
attrsGroups,
presentationNonInheritableGroupAttrs,

@@ -9,3 +9,3 @@ } from './_collections.js';

export const description =
'removes non-inheritable group’s presentational attributes';
"removes non-inheritable group's presentational attributes";

@@ -17,3 +17,3 @@ /**

*
* @type {import('./plugins-types.js').Plugin<'removeNonInheritableGroupAttrs'>}
* @type {import('../lib/types.js').Plugin}
*/

@@ -20,0 +20,0 @@ export const fn = () => {

@@ -1,6 +0,2 @@

/**
* @typedef {import('../lib/types.js').PathDataItem} PathDataItem
*/
import { visitSkip, detachNodeFromParent } from '../lib/xast.js';
import { detachNodeFromParent, visitSkip } from '../lib/xast.js';
import { parsePathData } from '../lib/path.js';

@@ -11,10 +7,10 @@ import { intersects } from './_path.js';

export const description =
'removes elements that are drawn outside of the viewbox (disabled by default)';
'removes elements that are drawn outside of the viewBox (disabled by default)';
/**
* Remove elements that are drawn outside of the viewbox.
* Remove elements that are drawn outside of the viewBox.
*
* @author JoshyPHP
*
* @type {import('./plugins-types.js').Plugin<'removeOffCanvasPaths'>}
* @type {import('../lib/types.js').Plugin}
*/

@@ -39,3 +35,3 @@ export const fn = () => {

let viewBox = '';
// find viewbox
// find viewBox
if (node.attributes.viewBox != null) {

@@ -51,3 +47,3 @@ // remove commas and plus signs, normalize and trim whitespace

// parse viewbox
// parse viewBox
// remove commas and plus signs, normalize and trim whitespace

@@ -94,3 +90,3 @@ viewBox = viewBox

// consider that a M command within the viewBox is visible
// consider that an M command within the viewBox is visible
let visible = false;

@@ -120,5 +116,3 @@ for (const pathDataItem of pathData) {

const { left, top, width, height } = viewBoxData;
/**
* @type {PathDataItem[]}
*/
/** @type {ReadonlyArray<import('../lib/types.js').PathDataItem>} */
const viewBoxPathData = [

@@ -125,0 +119,0 @@ { command: 'M', args: [left, top] },

@@ -13,3 +13,3 @@ import { detachNodeFromParent } from '../lib/xast.js';

*
* @type {import('./plugins-types.js').Plugin<'removeRasterImages'>}
* @type {import('../lib/types.js').Plugin}
*/

@@ -16,0 +16,0 @@ export const fn = () => {

@@ -22,3 +22,3 @@ import { attrsGroups } from './_collections.js';

* @author Patrick Klingemann
* @type {import('./plugins-types.js').Plugin<'removeScripts'>}
* @type {import('../lib/types.js').Plugin}
*/

@@ -59,10 +59,2 @@ export const fn = () => {

parentNode.children.splice(index, 1, ...usefulChildren);
// TODO remove legacy parentNode in v4
for (const child of node.children) {
Object.defineProperty(child, 'parentNode', {
writable: true,
value: parentNode,
});
}
}

@@ -69,0 +61,0 @@ }

@@ -13,3 +13,3 @@ import { detachNodeFromParent } from '../lib/xast.js';

*
* @type {import('./plugins-types.js').Plugin<'removeStyleElement'>}
* @type {import('../lib/types.js').Plugin}
*/

@@ -16,0 +16,0 @@ export const fn = () => {

@@ -13,3 +13,3 @@ import { detachNodeFromParent } from '../lib/xast.js';

*
* @type {import('./plugins-types.js').Plugin<'removeTitle'>}
* @type {import('../lib/types.js').Plugin}
*/

@@ -16,0 +16,0 @@ export const fn = () => {

import {
attrsGroups,
attrsGroupsDefaults,
elems,
attrsGroups,
elemsGroups,
attrsGroupsDefaults,
presentationNonInheritableGroupAttrs,
} from './_collections.js';
import { visitSkip, detachNodeFromParent } from '../lib/xast.js';
import { detachNodeFromParent, visitSkip } from '../lib/xast.js';
import { collectStylesheet, computeStyle } from '../lib/style.js';
/**
* @typedef RemoveUnknownsAndDefaultsParams
* @property {boolean=} unknownContent
* @property {boolean=} unknownAttrs
* @property {boolean=} defaultAttrs
* @property {boolean=} defaultMarkupDeclarations
* If to remove XML declarations that are assigned their default value. XML
* declarations are the properties in the `<?xml … ?>` block at the top of the
* document.
* @property {boolean=} uselessOverrides
* @property {boolean=} keepDataAttrs
* @property {boolean=} keepAriaAttrs
* @property {boolean=} keepRoleAttr
*/
export const name = 'removeUnknownsAndDefaults';

@@ -17,19 +32,11 @@ export const description =

/**
* @type {Map<string, Set<string>>}
*/
/** @type {Map<string, Set<string>>} */
const allowedChildrenPerElement = new Map();
/**
* @type {Map<string, Set<string>>}
*/
/** @type {Map<string, Set<string>>} */
const allowedAttributesPerElement = new Map();
/**
* @type {Map<string, Map<string, string>>}
*/
/** @type {Map<string, Map<string, string>>} */
const attributesDefaultsPerElement = new Map();
for (const [name, config] of Object.entries(elems)) {
/**
* @type {Set<string>}
*/
/** @type {Set<string>} */
const allowedChildren = new Set();

@@ -51,5 +58,3 @@ if (config.content) {

}
/**
* @type {Set<string>}
*/
/** @type {Set<string>} */
const allowedAttributes = new Set();

@@ -61,5 +66,3 @@ if (config.attrs) {

}
/**
* @type {Map<string, string>}
*/
/** @type {Map<string, string>} */
const attributesDefaults = new Map();

@@ -96,3 +99,3 @@ if (config.defaults) {

*
* @type {import('./plugins-types.js').Plugin<'removeUnknownsAndDefaults'>}
* @type {import('../lib/types.js').Plugin<RemoveUnknownsAndDefaultsParams>}
*/

@@ -99,0 +102,0 @@ export const fn = (root, params) => {

@@ -10,8 +10,6 @@ export const name = 'removeUnusedNS';

*
* @type {import('./plugins-types.js').Plugin<'removeUnusedNS'>}
* @type {import('../lib/types.js').Plugin}
*/
export const fn = () => {
/**
* @type {Set<string>}
*/
/** @type {Set<string>} */
const unusedNamespaces = new Set();

@@ -18,0 +16,0 @@ return {

import { detachNodeFromParent } from '../lib/xast.js';
import { elemsGroups } from './_collections.js';
/**
* @typedef {import('../lib/types.js').XastElement} XastElement
*/
export const name = 'removeUselessDefs';

@@ -16,3 +12,3 @@ export const description = 'removes elements in <defs> without id';

*
* @type {import('./plugins-types.js').Plugin<'removeUselessDefs'>}
* @type {import('../lib/types.js').Plugin}
*/

@@ -28,5 +24,3 @@ export const fn = () => {

) {
/**
* @type {XastElement[]}
*/
/** @type {import('../lib/types.js').XastElement[]} */
const usefulNodes = [];

@@ -37,9 +31,2 @@ collectUsefulNodes(node, usefulNodes);

}
// TODO remove legacy parentNode in v4
for (const usefulNode of usefulNodes) {
Object.defineProperty(usefulNode, 'parentNode', {
writable: true,
value: node,
});
}
node.children = usefulNodes;

@@ -53,3 +40,4 @@ }

/**
* @type {(node: XastElement, usefulNodes: XastElement[]) => void}
* @param {import('../lib/types.js').XastElement} node
* @param {import('../lib/types.js').XastElement[]} usefulNodes
*/

@@ -56,0 +44,0 @@ const collectUsefulNodes = (node, usefulNodes) => {

@@ -1,2 +0,2 @@

import { visit, visitSkip, detachNodeFromParent } from '../lib/xast.js';
import { detachNodeFromParent, visit, visitSkip } from '../lib/xast.js';
import { collectStylesheet, computeStyle } from '../lib/style.js';

@@ -6,2 +6,9 @@ import { hasScripts } from '../lib/svgo/tools.js';

/**
* @typedef RemoveUselessStrokeAndFillParams
* @property {boolean=} stroke
* @property {boolean=} fill
* @property {boolean=} removeNone
*/
export const name = 'removeUselessStrokeAndFill';

@@ -15,3 +22,3 @@ export const description = 'removes useless stroke and fill attributes';

*
* @type {import('./plugins-types.js').Plugin<'removeUselessStrokeAndFill'>}
* @type {import('../lib/types.js').Plugin<RemoveUselessStrokeAndFillParams>}
*/

@@ -25,3 +32,3 @@ export const fn = (root, params) => {

// style and script elements deoptimise this plugin
// style and script elements deoptimize this plugin
let hasStyleOrScript = false;

@@ -46,3 +53,3 @@ visit(root, {

enter: (node, parentNode) => {
// id attribute deoptimise the whole subtree
// id attribute deoptimize the whole subtree
if (node.attributes.id != null) {

@@ -49,0 +56,0 @@ return visitSkip;

@@ -13,3 +13,3 @@ export const name = 'removeViewBox';

* <svg width="100" height="50" viewBox="0 0 100 50">
* ⬇
* ⬇
* <svg width="100" height="50">

@@ -19,3 +19,3 @@ *

*
* @type {import('./plugins-types.js').Plugin<'removeViewBox'>}
* @type {import('../lib/types.js').Plugin}
*/

@@ -22,0 +22,0 @@ export const fn = () => {

import { elems } from './_collections.js';
/**
* @typedef {import('../lib/types.js').XastElement} XastElement
* @typedef RemoveXlinkParams
* @property {boolean=} includeLegacy
* By default this plugin ignores legacy elements that were deprecated or
* removed in SVG 2. Set to true to force performing operations on those too.
*/

@@ -42,4 +45,4 @@

/**
* @param {XastElement} node
* @param {string[]} prefixes
* @param {import('../lib/types.js').XastElement} node
* @param {ReadonlyArray<string>} prefixes
* @param {string} attr

@@ -58,5 +61,5 @@ * @returns {string[]}

*
* The XLink namespace is deprecated in SVG 2.
* XLink namespace is deprecated in SVG 2.
*
* @type {import('./plugins-types.js').Plugin<'removeXlink'>}
* @type {import('../lib/types.js').Plugin<RemoveXlinkParams>}
* @see https://developer.mozilla.org/docs/Web/SVG/Attribute/xlink:href

@@ -147,3 +150,3 @@ */

/** @type {XastElement} */
/** @type {import('../lib/types.js').XastElement} */
const titleTag = {

@@ -150,0 +153,0 @@ type: 'element',

@@ -15,3 +15,3 @@ export const name = 'removeXMLNS';

*
* @type {import('./plugins-types.js').Plugin<'removeXMLNS'>}
* @type {import('../lib/types.js').Plugin}
*/

@@ -18,0 +18,0 @@ export const fn = () => {

@@ -14,3 +14,3 @@ import { detachNodeFromParent } from '../lib/xast.js';

*
* @type {import('./plugins-types.js').Plugin<'removeXMLProcInst'>}
* @type {import('../lib/types.js').Plugin}
*/

@@ -17,0 +17,0 @@ export const fn = () => {

import { collectStylesheet } from '../lib/style.js';
import { detachNodeFromParent, querySelectorAll } from '../lib/xast.js';
/**
* @typedef {import('../lib/types.js').XastElement} XastElement
* @typedef {import('../lib/types.js').XastParent} XastParent
* @typedef {import('../lib/types.js').XastNode} XastNode
*/
export const name = 'reusePaths';

@@ -22,3 +16,3 @@ export const description =

*
* @type {import('./plugins-types.js').Plugin<'reusePaths'>}
* @type {import('../lib/types.js').Plugin}
*/

@@ -28,5 +22,3 @@ export const fn = (root) => {

/**
* @type {Map<string, XastElement[]>}
*/
/** @type {Map<string, import('../lib/types.js').XastElement[]>} */
const paths = new Map();

@@ -38,3 +30,3 @@

*
* @type {XastElement}
* @type {import('../lib/types.js').XastElement}
* @see https://developer.mozilla.org/docs/Web/SVG/Element/defs

@@ -98,7 +90,2 @@ */

};
// TODO remove legacy parentNode in v4
Object.defineProperty(defsTag, 'parentNode', {
writable: true,
value: node,
});
}

@@ -109,3 +96,3 @@

if (list.length > 1) {
/** @type {XastElement} */
/** @type {import('../lib/types.js').XastElement} */
const reusablePath = {

@@ -137,7 +124,2 @@ type: 'element',

}
// TODO remove legacy parentNode in v4
Object.defineProperty(reusablePath, 'parentNode', {
writable: true,
value: defsTag,
});
defsTag.children.push(reusablePath);

@@ -144,0 +126,0 @@ // convert paths to <use>

@@ -0,1 +1,7 @@

/**
* @typedef SortAttrsParams
* @property {ReadonlyArray<string>=} order
* @property {'front' | 'alphabetical'=} xmlnsOrder
*/
export const name = 'sortAttrs';

@@ -9,3 +15,3 @@ export const description = 'Sort element attributes for better compression';

*
* @type {import('./plugins-types.js').Plugin<'sortAttrs'>}
* @type {import('../lib/types.js').Plugin<SortAttrsParams>}
*/

@@ -37,3 +43,4 @@ export const fn = (_root, params) => {

/**
* @type {(name: string) => number}
* @param {string} name
* @returns {number}
*/

@@ -60,3 +67,5 @@ const getNsPriority = (name) => {

/**
* @type {(a: [string, string], b: [string, string]) => number}
* @param {[string, string]} param0
* @param {[string, string]} param1
* @returns {number}
*/

@@ -98,5 +107,3 @@ const compareAttrs = ([aName], [bName]) => {

attrs.sort(compareAttrs);
/**
* @type {Record<string, string>}
*/
/** @type {Record<string, string>} */
const sortedAttributes = {};

@@ -103,0 +110,0 @@ for (const [name, value] of attrs) {

@@ -5,8 +5,9 @@ export const name = 'sortDefsChildren';

/**
* Sorts children of defs in order to improve compression.
* Sorted first by frequency then by element name length then by element name (to ensure grouping).
* Sorts children of defs in order to improve compression. Sorted first by
* frequency then by element name length then by element name (to ensure
* grouping).
*
* @author David Leston
*
* @type {import('./plugins-types.js').Plugin<'sortDefsChildren'>}
* @type {import('../lib/types.js').Plugin}
*/

@@ -18,5 +19,3 @@ export const fn = () => {

if (node.name === 'defs') {
/**
* @type {Map<string, number>}
*/
/** @type {Map<string, number>} */
const frequencies = new Map();

@@ -23,0 +22,0 @@ for (const child of node.children) {

Sorry, the diff of this file is not supported yet

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