Comparing version
@@ -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, |
@@ -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'; |
102
lib/xast.js
@@ -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
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
1445373
6.89%150
82.93%27447
8.39%24
14.29%+ Added
+ Added
- Removed
- Removed
Updated
Updated