| 'use strict'; | ||
| const { createPreset } = require('../lib/svgo/plugins.js'); | ||
| const removeDoctype = require('./removeDoctype.js'); | ||
| const removeXMLProcInst = require('./removeXMLProcInst.js'); | ||
| const removeComments = require('./removeComments.js'); | ||
| const removeMetadata = require('./removeMetadata.js'); | ||
| const removeEditorsNSData = require('./removeEditorsNSData.js'); | ||
| const cleanupAttrs = require('./cleanupAttrs.js'); | ||
| const mergeStyles = require('./mergeStyles.js'); | ||
| const inlineStyles = require('./inlineStyles.js'); | ||
| const minifyStyles = require('./minifyStyles.js'); | ||
| const cleanupIDs = require('./cleanupIDs.js'); | ||
| const removeUselessDefs = require('./removeUselessDefs.js'); | ||
| const cleanupNumericValues = require('./cleanupNumericValues.js'); | ||
| const convertColors = require('./convertColors.js'); | ||
| const removeUnknownsAndDefaults = require('./removeUnknownsAndDefaults.js'); | ||
| const removeNonInheritableGroupAttrs = require('./removeNonInheritableGroupAttrs.js'); | ||
| const removeUselessStrokeAndFill = require('./removeUselessStrokeAndFill.js'); | ||
| const removeViewBox = require('./removeViewBox.js'); | ||
| const cleanupEnableBackground = require('./cleanupEnableBackground.js'); | ||
| const removeHiddenElems = require('./removeHiddenElems.js'); | ||
| const removeEmptyText = require('./removeEmptyText.js'); | ||
| const convertShapeToPath = require('./convertShapeToPath.js'); | ||
| const convertEllipseToCircle = require('./convertEllipseToCircle.js'); | ||
| const moveElemsAttrsToGroup = require('./moveElemsAttrsToGroup.js'); | ||
| const moveGroupAttrsToElems = require('./moveGroupAttrsToElems.js'); | ||
| const collapseGroups = require('./collapseGroups.js'); | ||
| const convertPathData = require('./convertPathData.js'); | ||
| const convertTransform = require('./convertTransform.js'); | ||
| const removeEmptyAttrs = require('./removeEmptyAttrs.js'); | ||
| const removeEmptyContainers = require('./removeEmptyContainers.js'); | ||
| const mergePaths = require('./mergePaths.js'); | ||
| const removeUnusedNS = require('./removeUnusedNS.js'); | ||
| const sortDefsChildren = require('./sortDefsChildren.js'); | ||
| const removeTitle = require('./removeTitle.js'); | ||
| const removeDesc = require('./removeDesc.js'); | ||
| const presetDefault = createPreset({ | ||
| name: 'presetDefault', | ||
| plugins: [ | ||
| removeDoctype, | ||
| removeXMLProcInst, | ||
| removeComments, | ||
| removeMetadata, | ||
| removeEditorsNSData, | ||
| cleanupAttrs, | ||
| mergeStyles, | ||
| inlineStyles, | ||
| minifyStyles, | ||
| cleanupIDs, | ||
| removeUselessDefs, | ||
| cleanupNumericValues, | ||
| convertColors, | ||
| removeUnknownsAndDefaults, | ||
| removeNonInheritableGroupAttrs, | ||
| removeUselessStrokeAndFill, | ||
| removeViewBox, | ||
| cleanupEnableBackground, | ||
| removeHiddenElems, | ||
| removeEmptyText, | ||
| convertShapeToPath, | ||
| convertEllipseToCircle, | ||
| moveElemsAttrsToGroup, | ||
| moveGroupAttrsToElems, | ||
| collapseGroups, | ||
| convertPathData, | ||
| convertTransform, | ||
| removeEmptyAttrs, | ||
| removeEmptyContainers, | ||
| mergePaths, | ||
| removeUnusedNS, | ||
| sortDefsChildren, | ||
| removeTitle, | ||
| removeDesc, | ||
| ], | ||
| }); | ||
| module.exports = presetDefault; |
+2
-1
@@ -281,3 +281,4 @@ 'use strict'; | ||
| (command === 'A' || command === 'a') && | ||
| (i === 4 || i === 5) | ||
| // consider combined arcs | ||
| (i % 7 === 4 || i % 7 === 5) | ||
| ) { | ||
@@ -284,0 +285,0 @@ result += numberString; |
+32
-36
@@ -6,4 +6,3 @@ 'use strict'; | ||
| const specificity = require('csso/lib/restructure/prepare/specificity'); | ||
| const { selectAll, is } = require('css-select'); | ||
| const svgoCssSelectAdapter = require('./svgo/css-select-adapter.js'); | ||
| const { visit, matches } = require('./xast.js'); | ||
| const { compareSpecificity } = require('./css-tools.js'); | ||
@@ -16,7 +15,2 @@ const { | ||
| const cssSelectOptions = { | ||
| xmlMode: true, | ||
| adapter: svgoCssSelectAdapter, | ||
| }; | ||
| const parseRule = (ruleNode, dynamic) => { | ||
@@ -81,3 +75,3 @@ let selectors; | ||
| const computeOwnStyle = (node, stylesheet) => { | ||
| const computeOwnStyle = (stylesheet, node) => { | ||
| const computedStyle = {}; | ||
@@ -96,3 +90,3 @@ const importantStyles = new Map(); | ||
| for (const { selectors, declarations, dynamic } of stylesheet) { | ||
| if (is(node, selectors, cssSelectOptions)) { | ||
| if (matches(node, selectors)) { | ||
| for (const { name, value, important } of declarations) { | ||
@@ -139,29 +133,27 @@ const computed = computedStyle[name]; | ||
| const computeStyle = (node) => { | ||
| // find root | ||
| let root = node; | ||
| while (root.parentNode) { | ||
| root = root.parentNode; | ||
| } | ||
| // find all styles | ||
| const styleNodes = selectAll('style', root, cssSelectOptions); | ||
| // parse all styles | ||
| const collectStylesheet = (root) => { | ||
| const stylesheet = []; | ||
| for (const styleNode of styleNodes) { | ||
| const dynamic = | ||
| styleNode.attributes.media != null && | ||
| styleNode.attributes.media !== 'all'; | ||
| if ( | ||
| styleNode.attributes.type == null || | ||
| styleNode.attributes.type === '' || | ||
| styleNode.attributes.type === 'text/css' | ||
| ) { | ||
| const children = styleNode.children; | ||
| for (const child of children) { | ||
| if (child.type === 'text' || child.type === 'cdata') { | ||
| stylesheet.push(...parseStylesheet(child.value, dynamic)); | ||
| // find and parse all styles | ||
| visit(root, { | ||
| element: { | ||
| enter: (node) => { | ||
| if (node.name === 'style') { | ||
| const dynamic = | ||
| node.attributes.media != null && node.attributes.media !== 'all'; | ||
| if ( | ||
| node.attributes.type == null || | ||
| node.attributes.type === '' || | ||
| node.attributes.type === 'text/css' | ||
| ) { | ||
| const children = node.children; | ||
| for (const child of children) { | ||
| if (child.type === 'text' || child.type === 'cdata') { | ||
| stylesheet.push(...parseStylesheet(child.value, dynamic)); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| }, | ||
| }); | ||
| // sort by selectors specificity | ||
@@ -171,8 +163,12 @@ stable.inplace(stylesheet, (a, b) => | ||
| ); | ||
| return stylesheet; | ||
| }; | ||
| exports.collectStylesheet = collectStylesheet; | ||
| const computeStyle = (stylesheet, node) => { | ||
| // collect inherited styles | ||
| const computedStyles = computeOwnStyle(node, stylesheet); | ||
| const computedStyles = computeOwnStyle(stylesheet, node); | ||
| let parent = node; | ||
| while (parent.parentNode && parent.parentNode.type !== 'root') { | ||
| const inheritedStyles = computeOwnStyle(parent.parentNode, stylesheet); | ||
| const inheritedStyles = computeOwnStyle(stylesheet, parent.parentNode); | ||
| for (const [name, computed] of Object.entries(inheritedStyles)) { | ||
@@ -179,0 +175,0 @@ if ( |
+7
-5
@@ -10,3 +10,3 @@ 'use strict'; | ||
| const js2svg = require('./svgo/js2svg.js'); | ||
| const invokePlugins = require('./svgo/plugins.js'); | ||
| const { invokePlugins } = require('./svgo/plugins.js'); | ||
| const JSAPI = require('./svgo/jsAPI.js'); | ||
@@ -46,6 +46,8 @@ const { encodeSVGDatauri } = require('./svgo/tools.js'); | ||
| } | ||
| const resolvedPlugins = plugins.map((plugin) => | ||
| resolvePluginConfig(plugin, config) | ||
| ); | ||
| svgjs = invokePlugins(svgjs, info, resolvedPlugins); | ||
| const resolvedPlugins = plugins.map(resolvePluginConfig); | ||
| const globalOverrides = {}; | ||
| if (config.floatPrecision != null) { | ||
| globalOverrides.floatPrecision = config.floatPrecision; | ||
| } | ||
| svgjs = invokePlugins(svgjs, info, resolvedPlugins, null, globalOverrides); | ||
| svgjs = js2svg(svgjs, config.js2svg); | ||
@@ -52,0 +54,0 @@ if (svgjs.error) { |
+3
-3
@@ -5,3 +5,3 @@ 'use strict'; | ||
| const PATH = require('path'); | ||
| const chalk = require('chalk'); | ||
| const { green } = require('colorette'); | ||
| const { loadConfig, optimize } = require('../svgo-node.js'); | ||
@@ -437,3 +437,3 @@ const pluginsMap = require('../../plugins/plugins.js'); | ||
| (profitPercents < 0 ? ' + ' : ' - ') + | ||
| chalk.green(Math.abs(Math.round(profitPercents * 10) / 10) + '%') + | ||
| green(Math.abs(Math.round(profitPercents * 10) / 10) + '%') + | ||
| ' = ' + | ||
@@ -490,3 +490,3 @@ Math.round((outBytes / 1024) * 1000) / 1000 + | ||
| .sort(([a], [b]) => a.localeCompare(b)) | ||
| .map(([name, plugin]) => ` [ ${chalk.green(name)} ] ${plugin.description}`) | ||
| .map(([name, plugin]) => ` [ ${green(name)} ] ${plugin.description}`) | ||
| .join('\n'); | ||
@@ -493,0 +493,0 @@ console.log('Currently available plugins:\n' + list); |
+20
-5
@@ -61,2 +61,20 @@ 'use strict'; | ||
| const extendDefaultPlugins = (plugins) => { | ||
| console.warn( | ||
| '\n"extendDefaultPlugins" utility is deprecated.\n' + | ||
| 'Use "preset-default" plugin with overrides instead.\n' + | ||
| 'For example:\n' + | ||
| `{\n` + | ||
| ` name: 'preset-default',\n` + | ||
| ` params: {\n` + | ||
| ` overrides: {\n` + | ||
| ` // customize plugin options\n` + | ||
| ` convertShapeToPath: {\n` + | ||
| ` convertArcs: true\n` + | ||
| ` },\n` + | ||
| ` // disable plugins\n` + | ||
| ` convertPathData: false\n` + | ||
| ` }\n` + | ||
| ` }\n` + | ||
| `}\n` | ||
| ); | ||
| const extendedPlugins = pluginsOrder.map((name) => ({ | ||
@@ -67,3 +85,3 @@ name, | ||
| for (const plugin of plugins) { | ||
| const resolvedPlugin = resolvePluginConfig(plugin, {}); | ||
| const resolvedPlugin = resolvePluginConfig(plugin); | ||
| const index = pluginsOrder.indexOf(resolvedPlugin.name); | ||
@@ -80,7 +98,4 @@ if (index === -1) { | ||
| const resolvePluginConfig = (plugin, config) => { | ||
| const resolvePluginConfig = (plugin) => { | ||
| let configParams = {}; | ||
| if ('floatPrecision' in config) { | ||
| configParams.floatPrecision = config.floatPrecision; | ||
| } | ||
| if (typeof plugin === 'string') { | ||
@@ -87,0 +102,0 @@ // resolve builtin plugin specified as string |
@@ -335,3 +335,3 @@ 'use strict'; | ||
| // newly added class attribute | ||
| this.class.hasClass(); | ||
| this.class.addClassValueHandler(); | ||
| } | ||
@@ -341,3 +341,3 @@ | ||
| // newly added style attribute | ||
| this.style.hasStyle(); | ||
| this.style.addStyleValueHandler(); | ||
| } | ||
@@ -344,0 +344,0 @@ |
+46
-65
@@ -10,43 +10,36 @@ 'use strict'; | ||
| * | ||
| * @param {Object} data input data | ||
| * @param {Object} ast input ast | ||
| * @param {Object} info extra information | ||
| * @param {Array} plugins plugins object from config | ||
| * @return {Object} output data | ||
| * @return {Object} output ast | ||
| */ | ||
| module.exports = function (data, info, plugins) { | ||
| // Try to group sequential elements of plugins array | ||
| // to optimize ast traversing | ||
| const groups = []; | ||
| let prev; | ||
| const invokePlugins = (ast, info, plugins, overrides, globalOverrides) => { | ||
| for (const plugin of plugins) { | ||
| if (prev && plugin.type == prev[0].type) { | ||
| prev.push(plugin); | ||
| } else { | ||
| prev = [plugin]; | ||
| groups.push(prev); | ||
| const override = overrides == null ? null : overrides[plugin.name]; | ||
| if (override === false) { | ||
| continue; | ||
| } | ||
| } | ||
| for (const group of groups) { | ||
| switch (group[0].type) { | ||
| case 'perItem': | ||
| data = perItem(data, info, group); | ||
| break; | ||
| case 'perItemReverse': | ||
| data = perItem(data, info, group, true); | ||
| break; | ||
| case 'full': | ||
| data = full(data, info, group); | ||
| break; | ||
| case 'visitor': | ||
| for (const plugin of group) { | ||
| if (plugin.active) { | ||
| const visitor = plugin.fn(data, plugin.params, info); | ||
| visit(data, visitor); | ||
| } | ||
| } | ||
| break; | ||
| const params = { ...plugin.params, ...globalOverrides, ...override }; | ||
| if (plugin.type === 'perItem') { | ||
| ast = perItem(ast, info, plugin, params); | ||
| } | ||
| if (plugin.type === 'perItemReverse') { | ||
| ast = perItem(ast, info, plugin, params, true); | ||
| } | ||
| if (plugin.type === 'full') { | ||
| if (plugin.active) { | ||
| ast = plugin.fn(ast, params, info); | ||
| } | ||
| } | ||
| if (plugin.type === 'visitor') { | ||
| if (plugin.active) { | ||
| const visitor = plugin.fn(ast, params, info); | ||
| visit(ast, visitor); | ||
| } | ||
| } | ||
| } | ||
| return data; | ||
| return ast; | ||
| }; | ||
| exports.invokePlugins = invokePlugins; | ||
@@ -62,3 +55,3 @@ /** | ||
| */ | ||
| function perItem(data, info, plugins, reverse) { | ||
| function perItem(data, info, plugin, params, reverse) { | ||
| function monkeys(items) { | ||
@@ -70,14 +63,7 @@ items.children = items.children.filter(function (item) { | ||
| } | ||
| // main filter | ||
| var filter = true; | ||
| for (var i = 0; filter && i < plugins.length; i++) { | ||
| var plugin = plugins[i]; | ||
| if (plugin.active && plugin.fn(item, plugin.params, info) === false) { | ||
| filter = false; | ||
| } | ||
| let kept = true; | ||
| if (plugin.active) { | ||
| kept = plugin.fn(item, params, info) !== false; | ||
| } | ||
| // direct pass | ||
@@ -87,28 +73,23 @@ if (!reverse && item.children) { | ||
| } | ||
| return filter; | ||
| return kept; | ||
| }); | ||
| return items; | ||
| } | ||
| return monkeys(data); | ||
| } | ||
| /** | ||
| * "Full" plugins. | ||
| * | ||
| * @param {Object} data input data | ||
| * @param {Object} info extra information | ||
| * @param {Array} plugins plugins list to process | ||
| * @return {Object} output data | ||
| */ | ||
| function full(data, info, plugins) { | ||
| plugins.forEach(function (plugin) { | ||
| if (plugin.active) { | ||
| data = plugin.fn(data, plugin.params, info); | ||
| } | ||
| }); | ||
| return data; | ||
| } | ||
| const createPreset = ({ name, plugins }) => { | ||
| return { | ||
| name, | ||
| type: 'full', | ||
| fn: (ast, params, info) => { | ||
| const { floatPrecision, overrides } = params; | ||
| const globalOverrides = {}; | ||
| if (floatPrecision != null) { | ||
| globalOverrides.floatPrecision = floatPrecision; | ||
| } | ||
| return invokePlugins(ast, info, plugins, overrides, globalOverrides); | ||
| }, | ||
| }; | ||
| }; | ||
| exports.createPreset = createPreset; |
+0
-10
@@ -55,12 +55,2 @@ 'use strict'; | ||
| /** | ||
| * @param {any[]} a | ||
| * @param {any[]} b | ||
| */ | ||
| exports.intersectArrays = function (a, b) { | ||
| return a.filter(function (n) { | ||
| return b.indexOf(n) > -1; | ||
| }); | ||
| }; | ||
| /** | ||
| * Convert a row of numbers to an optimized string view. | ||
@@ -67,0 +57,0 @@ * |
+7
-8
@@ -55,6 +55,6 @@ 'use strict'; | ||
| const visit = (node, visitor) => { | ||
| const visit = (node, visitor, parentNode = null) => { | ||
| const callbacks = visitor[node.type]; | ||
| if (callbacks && callbacks.enter) { | ||
| callbacks.enter(node); | ||
| callbacks.enter(node, parentNode); | ||
| } | ||
@@ -65,3 +65,3 @@ // visit root children | ||
| for (const child of node.children) { | ||
| visit(child, visitor); | ||
| visit(child, visitor, node); | ||
| } | ||
@@ -71,5 +71,5 @@ } | ||
| if (node.type === 'element') { | ||
| if (node.parentNode.children.includes(node)) { | ||
| if (parentNode.children.includes(node)) { | ||
| for (const child of node.children) { | ||
| visit(child, visitor); | ||
| visit(child, visitor, node); | ||
| } | ||
@@ -79,3 +79,3 @@ } | ||
| if (callbacks && callbacks.exit) { | ||
| callbacks.exit(node); | ||
| callbacks.exit(node, parentNode); | ||
| } | ||
@@ -85,4 +85,3 @@ }; | ||
| const detachNodeFromParent = (node) => { | ||
| const parentNode = node.parentNode; | ||
| const detachNodeFromParent = (node, parentNode) => { | ||
| // avoid splice to not break for loops | ||
@@ -89,0 +88,0 @@ parentNode.children = parentNode.children.filter((child) => child !== node); |
+9
-11
| { | ||
| "name": "svgo", | ||
| "version": "2.3.1", | ||
| "version": "2.4.0", | ||
| "description": "Nodejs-based tool for optimizing SVG vector graphics files", | ||
@@ -49,7 +49,8 @@ "keywords": [ | ||
| "plugins", | ||
| "dist" | ||
| "dist", | ||
| "!**/**.test.js" | ||
| ], | ||
| "scripts": { | ||
| "test": "c8 --reporter=html --reporter=text mocha \"test/*/_index.js\" \"**/*.test.js\" --ignore=\"node_modules/**\"", | ||
| "lint": "eslint --ignore-path .gitignore . && prettier --list-different \"**/*.js\" --ignore-path .gitignore", | ||
| "test": "jest --coverage", | ||
| "lint": "eslint --ignore-path .gitignore . && prettier --check \"**/*.js\" --ignore-path .gitignore", | ||
| "fix": "eslint --ignore-path .gitignore --fix . && prettier --write \"**/*.js\" --ignore-path .gitignore", | ||
@@ -86,7 +87,6 @@ "typecheck": "tsc", | ||
| "files": [ | ||
| "test/**/*.js", | ||
| "**/*.test.js" | ||
| ], | ||
| "env": { | ||
| "mocha": true | ||
| "jest": true | ||
| } | ||
@@ -98,3 +98,3 @@ } | ||
| "@trysound/sax": "0.1.1", | ||
| "chalk": "^4.1.0", | ||
| "colorette": "^1.2.2", | ||
| "commander": "^7.1.0", | ||
@@ -110,8 +110,6 @@ "css-select": "^4.1.3", | ||
| "@rollup/plugin-node-resolve": "^11.2.0", | ||
| "@types/mocha": "^8.2.2", | ||
| "c8": "^7.6.0", | ||
| "chai": "^4.3.4", | ||
| "@types/jest": "^27.0.0", | ||
| "del": "^6.0.0", | ||
| "eslint": "^7.22.0", | ||
| "mocha": "^8.3.2", | ||
| "jest": "^27.0.6", | ||
| "mock-stdin": "^1.0.0", | ||
@@ -118,0 +116,0 @@ "node-fetch": "^2.6.1", |
@@ -326,2 +326,7 @@ 'use strict'; | ||
| if (command === 'z' || command === 'Z') { | ||
| cursor[0] = start[0]; | ||
| cursor[1] = start[1]; | ||
| } | ||
| pathItem.instruction = command; | ||
@@ -328,0 +333,0 @@ pathItem.data = args; |
@@ -5,2 +5,4 @@ 'use strict'; | ||
| exports.name = 'addAttributesToSVGElement'; | ||
| exports.type = 'perItem'; | ||
@@ -7,0 +9,0 @@ |
| 'use strict'; | ||
| exports.name = 'addClassesToSVGElement'; | ||
| exports.type = 'full'; | ||
@@ -4,0 +6,0 @@ |
+36
-39
| 'use strict'; | ||
| exports.type = 'perItem'; | ||
| exports.name = 'cleanupAttrs'; | ||
| exports.type = 'visitor'; | ||
| exports.active = true; | ||
| exports.description = | ||
| 'cleanups attributes from newlines, trailing and repeating spaces'; | ||
| exports.params = { | ||
| newlines: true, | ||
| trim: true, | ||
| spaces: true, | ||
| }; | ||
| const regNewlinesNeedSpace = /(\S)\r?\n(\S)/g; | ||
| const regNewlines = /\r?\n/g; | ||
| const regSpaces = /\s{2,}/g; | ||
| var regNewlinesNeedSpace = /(\S)\r?\n(\S)/g, | ||
| regNewlines = /\r?\n/g, | ||
| regSpaces = /\s{2,}/g; | ||
| /** | ||
| * Cleanup attributes values from newlines, trailing and repeating spaces. | ||
| * | ||
| * @param {Object} item current iteration item | ||
| * @param {Object} params plugin params | ||
| * @return {Boolean} if false, item will be filtered out | ||
| * | ||
| * @author Kir Belevich | ||
| */ | ||
| exports.fn = function (item, params) { | ||
| if (item.type === 'element') { | ||
| for (const name of Object.keys(item.attributes)) { | ||
| if (params.newlines) { | ||
| // new line which requires a space instead of themselve | ||
| item.attributes[name] = item.attributes[name].replace( | ||
| regNewlinesNeedSpace, | ||
| (match, p1, p2) => p1 + ' ' + p2 | ||
| ); | ||
| // simple new line | ||
| item.attributes[name] = item.attributes[name].replace(regNewlines, ''); | ||
| } | ||
| if (params.trim) { | ||
| item.attributes[name] = item.attributes[name].trim(); | ||
| } | ||
| if (params.spaces) { | ||
| item.attributes[name] = item.attributes[name].replace(regSpaces, ' '); | ||
| } | ||
| } | ||
| } | ||
| exports.fn = (root, params) => { | ||
| const { newlines = true, trim = true, spaces = true } = params; | ||
| return { | ||
| element: { | ||
| enter: (node) => { | ||
| for (const name of Object.keys(node.attributes)) { | ||
| if (newlines) { | ||
| // new line which requires a space instead of themselve | ||
| node.attributes[name] = node.attributes[name].replace( | ||
| regNewlinesNeedSpace, | ||
| (match, p1, p2) => p1 + ' ' + p2 | ||
| ); | ||
| // simple new line | ||
| node.attributes[name] = node.attributes[name].replace( | ||
| regNewlines, | ||
| '' | ||
| ); | ||
| } | ||
| if (trim) { | ||
| node.attributes[name] = node.attributes[name].trim(); | ||
| } | ||
| if (spaces) { | ||
| node.attributes[name] = node.attributes[name].replace( | ||
| regSpaces, | ||
| ' ' | ||
| ); | ||
| } | ||
| } | ||
| }, | ||
| }, | ||
| }; | ||
| }; |
@@ -5,2 +5,4 @@ 'use strict'; | ||
| exports.name = 'cleanupEnableBackground'; | ||
| exports.type = 'full'; | ||
@@ -7,0 +9,0 @@ |
@@ -6,2 +6,4 @@ 'use strict'; | ||
| exports.name = 'cleanupIDs'; | ||
| exports.type = 'full'; | ||
@@ -8,0 +10,0 @@ |
@@ -5,2 +5,4 @@ 'use strict'; | ||
| exports.name = 'cleanupListOfValues'; | ||
| exports.type = 'perItem'; | ||
@@ -7,0 +9,0 @@ |
| 'use strict'; | ||
| exports.name = 'cleanupNumericValues'; | ||
| exports.type = 'perItem'; | ||
@@ -4,0 +6,0 @@ |
@@ -5,2 +5,4 @@ 'use strict'; | ||
| exports.name = 'collapseGroups'; | ||
| exports.type = 'perItemReverse'; | ||
@@ -7,0 +9,0 @@ |
| 'use strict'; | ||
| exports.name = 'convertColors'; | ||
| exports.type = 'perItem'; | ||
@@ -4,0 +6,0 @@ |
| 'use strict'; | ||
| exports.type = 'perItem'; | ||
| exports.name = 'convertEllipseToCircle'; | ||
| exports.type = 'visitor'; | ||
| exports.active = true; | ||
| exports.description = 'converts non-eccentric <ellipse>s to <circle>s'; | ||
@@ -14,24 +13,26 @@ | ||
| * | ||
| * @param {Object} item current iteration item | ||
| * @return {Boolean} if false, item will be filtered out | ||
| * | ||
| * @author Taylor Hunt | ||
| */ | ||
| exports.fn = function (item) { | ||
| if (item.isElem('ellipse')) { | ||
| const rx = item.attributes.rx || 0; | ||
| const ry = item.attributes.ry || 0; | ||
| if ( | ||
| rx === ry || | ||
| rx === 'auto' || | ||
| ry === 'auto' // SVG2 | ||
| ) { | ||
| var radius = rx !== 'auto' ? rx : ry; | ||
| item.renameElem('circle'); | ||
| delete item.attributes.rx; | ||
| delete item.attributes.ry; | ||
| item.attributes.r = radius; | ||
| } | ||
| } | ||
| exports.fn = () => { | ||
| return { | ||
| element: { | ||
| enter: (node) => { | ||
| if (node.name === 'ellipse') { | ||
| const rx = node.attributes.rx || 0; | ||
| const ry = node.attributes.ry || 0; | ||
| if ( | ||
| rx === ry || | ||
| rx === 'auto' || | ||
| ry === 'auto' // SVG2 | ||
| ) { | ||
| node.name = 'circle'; | ||
| const radius = rx === 'auto' ? ry : rx; | ||
| delete node.attributes.rx; | ||
| delete node.attributes.ry; | ||
| node.attributes.r = radius; | ||
| } | ||
| } | ||
| }, | ||
| }, | ||
| }; | ||
| }; |
| 'use strict'; | ||
| const { computeStyle } = require('../lib/style.js'); | ||
| const { collectStylesheet, computeStyle } = require('../lib/style.js'); | ||
| const { pathElems } = require('./_collections.js'); | ||
@@ -9,2 +9,3 @@ const { path2js, js2path } = require('./_path.js'); | ||
| exports.name = 'convertPathData'; | ||
| exports.type = 'visitor'; | ||
@@ -59,2 +60,3 @@ exports.active = true; | ||
| exports.fn = (root, params) => { | ||
| const stylesheet = collectStylesheet(root); | ||
| return { | ||
@@ -64,3 +66,3 @@ element: { | ||
| if (pathElems.includes(node.name) && node.attributes.d != null) { | ||
| const computedStyle = computeStyle(node); | ||
| const computedStyle = computeStyle(stylesheet, node); | ||
| precision = params.floatPrecision; | ||
@@ -67,0 +69,0 @@ error = |
+127
-121
| 'use strict'; | ||
| const { stringifyPathData } = require('../lib/path.js'); | ||
| const { detachNodeFromParent } = require('../lib/xast.js'); | ||
| exports.type = 'perItem'; | ||
| exports.name = 'convertShapeToPath'; | ||
| exports.type = 'visitor'; | ||
| exports.active = true; | ||
| exports.description = 'converts basic shapes to more compact path form'; | ||
| exports.params = { | ||
| convertArcs: false, | ||
| floatPrecision: null, | ||
| }; | ||
| const regNumber = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g; | ||
@@ -25,122 +20,133 @@ | ||
| * | ||
| * @param {Object} item current iteration item | ||
| * @param {Object} params plugin params | ||
| * @return {Boolean} if false, item will be filtered out | ||
| * | ||
| * @author Lev Solntsev | ||
| */ | ||
| exports.fn = function (item, params) { | ||
| const precision = params ? params.floatPrecision : null; | ||
| const convertArcs = params && params.convertArcs; | ||
| exports.fn = (root, params) => { | ||
| const { convertArcs = false, floatPrecision: precision = null } = params; | ||
| if ( | ||
| item.isElem('rect') && | ||
| item.attributes.width != null && | ||
| item.attributes.height != null && | ||
| item.attributes.rx == null && | ||
| item.attributes.ry == null | ||
| ) { | ||
| const x = Number(item.attributes.x || '0'); | ||
| const y = Number(item.attributes.y || '0'); | ||
| const width = Number(item.attributes.width); | ||
| const height = Number(item.attributes.height); | ||
| // Values like '100%' compute to NaN, thus running after | ||
| // cleanupNumericValues when 'px' units has already been removed. | ||
| // TODO: Calculate sizes from % and non-px units if possible. | ||
| if (isNaN(x - y + width - height)) return; | ||
| const pathData = [ | ||
| { command: 'M', args: [x, y] }, | ||
| { command: 'H', args: [x + width] }, | ||
| { command: 'V', args: [y + height] }, | ||
| { command: 'H', args: [x] }, | ||
| { command: 'z', args: [] }, | ||
| ]; | ||
| item.attributes.d = stringifyPathData({ pathData, precision }); | ||
| item.renameElem('path'); | ||
| delete item.attributes.x; | ||
| delete item.attributes.y; | ||
| delete item.attributes.width; | ||
| delete item.attributes.height; | ||
| } | ||
| return { | ||
| element: { | ||
| enter: (node, parentNode) => { | ||
| // convert rect to path | ||
| if ( | ||
| node.name === 'rect' && | ||
| node.attributes.width != null && | ||
| node.attributes.height != null && | ||
| node.attributes.rx == null && | ||
| node.attributes.ry == null | ||
| ) { | ||
| const x = Number(node.attributes.x || '0'); | ||
| const y = Number(node.attributes.y || '0'); | ||
| const width = Number(node.attributes.width); | ||
| const height = Number(node.attributes.height); | ||
| // Values like '100%' compute to NaN, thus running after | ||
| // cleanupNumericValues when 'px' units has already been removed. | ||
| // TODO: Calculate sizes from % and non-px units if possible. | ||
| if (Number.isNaN(x - y + width - height)) return; | ||
| const pathData = [ | ||
| { command: 'M', args: [x, y] }, | ||
| { command: 'H', args: [x + width] }, | ||
| { command: 'V', args: [y + height] }, | ||
| { command: 'H', args: [x] }, | ||
| { command: 'z', args: [] }, | ||
| ]; | ||
| node.name = 'path'; | ||
| node.attributes.d = stringifyPathData({ pathData, precision }); | ||
| delete node.attributes.x; | ||
| delete node.attributes.y; | ||
| delete node.attributes.width; | ||
| delete node.attributes.height; | ||
| } | ||
| if (item.isElem('line')) { | ||
| const x1 = Number(item.attributes.x1 || '0'); | ||
| const y1 = Number(item.attributes.y1 || '0'); | ||
| const x2 = Number(item.attributes.x2 || '0'); | ||
| const y2 = Number(item.attributes.y2 || '0'); | ||
| if (isNaN(x1 - y1 + x2 - y2)) return; | ||
| const pathData = [ | ||
| { command: 'M', args: [x1, y1] }, | ||
| { command: 'L', args: [x2, y2] }, | ||
| ]; | ||
| item.attributes.d = stringifyPathData({ pathData, precision }); | ||
| item.renameElem('path'); | ||
| delete item.attributes.x1; | ||
| delete item.attributes.y1; | ||
| delete item.attributes.x2; | ||
| delete item.attributes.y2; | ||
| } | ||
| // convert line to path | ||
| if (node.name === 'line') { | ||
| const x1 = Number(node.attributes.x1 || '0'); | ||
| const y1 = Number(node.attributes.y1 || '0'); | ||
| const x2 = Number(node.attributes.x2 || '0'); | ||
| const y2 = Number(node.attributes.y2 || '0'); | ||
| if (Number.isNaN(x1 - y1 + x2 - y2)) return; | ||
| const pathData = [ | ||
| { command: 'M', args: [x1, y1] }, | ||
| { command: 'L', args: [x2, y2] }, | ||
| ]; | ||
| node.name = 'path'; | ||
| node.attributes.d = stringifyPathData({ pathData, precision }); | ||
| delete node.attributes.x1; | ||
| delete node.attributes.y1; | ||
| delete node.attributes.x2; | ||
| delete node.attributes.y2; | ||
| } | ||
| if ( | ||
| (item.isElem('polyline') || item.isElem('polygon')) && | ||
| item.attributes.points != null | ||
| ) { | ||
| const coords = (item.attributes.points.match(regNumber) || []).map(Number); | ||
| if (coords.length < 4) return false; | ||
| const pathData = []; | ||
| for (let i = 0; i < coords.length; i += 2) { | ||
| pathData.push({ | ||
| command: i === 0 ? 'M' : 'L', | ||
| args: coords.slice(i, i + 2), | ||
| }); | ||
| } | ||
| if (item.isElem('polygon')) { | ||
| pathData.push({ command: 'z', args: [] }); | ||
| } | ||
| item.attributes.d = stringifyPathData({ pathData, precision }); | ||
| item.renameElem('path'); | ||
| delete item.attributes.points; | ||
| } | ||
| // convert polyline and polygon to path | ||
| if ( | ||
| (node.name === 'polyline' || node.name === 'polygon') && | ||
| node.attributes.points != null | ||
| ) { | ||
| const coords = (node.attributes.points.match(regNumber) || []).map( | ||
| Number | ||
| ); | ||
| if (coords.length < 4) { | ||
| detachNodeFromParent(node, parentNode); | ||
| return; | ||
| } | ||
| const pathData = []; | ||
| for (let i = 0; i < coords.length; i += 2) { | ||
| pathData.push({ | ||
| command: i === 0 ? 'M' : 'L', | ||
| args: coords.slice(i, i + 2), | ||
| }); | ||
| } | ||
| if (node.name === 'polygon') { | ||
| pathData.push({ command: 'z', args: [] }); | ||
| } | ||
| node.name = 'path'; | ||
| node.attributes.d = stringifyPathData({ pathData, precision }); | ||
| delete node.attributes.points; | ||
| } | ||
| if (item.isElem('circle') && convertArcs) { | ||
| const cx = Number(item.attributes.cx || '0'); | ||
| const cy = Number(item.attributes.cy || '0'); | ||
| const r = Number(item.attributes.r || '0'); | ||
| if (isNaN(cx - cy + r)) { | ||
| return; | ||
| } | ||
| const pathData = [ | ||
| { command: 'M', args: [cx, cy - r] }, | ||
| { command: 'A', args: [r, r, 0, 1, 0, cx, cy + r] }, | ||
| { command: 'A', args: [r, r, 0, 1, 0, cx, cy - r] }, | ||
| { command: 'z', args: [] }, | ||
| ]; | ||
| item.attributes.d = stringifyPathData({ pathData, precision }); | ||
| item.renameElem('path'); | ||
| delete item.attributes.cx; | ||
| delete item.attributes.cy; | ||
| delete item.attributes.r; | ||
| } | ||
| // optionally convert circle | ||
| if (node.name === 'circle' && convertArcs) { | ||
| const cx = Number(node.attributes.cx || '0'); | ||
| const cy = Number(node.attributes.cy || '0'); | ||
| const r = Number(node.attributes.r || '0'); | ||
| if (Number.isNaN(cx - cy + r)) { | ||
| return; | ||
| } | ||
| const pathData = [ | ||
| { command: 'M', args: [cx, cy - r] }, | ||
| { command: 'A', args: [r, r, 0, 1, 0, cx, cy + r] }, | ||
| { command: 'A', args: [r, r, 0, 1, 0, cx, cy - r] }, | ||
| { command: 'z', args: [] }, | ||
| ]; | ||
| node.name = 'path'; | ||
| node.attributes.d = stringifyPathData({ pathData, precision }); | ||
| delete node.attributes.cx; | ||
| delete node.attributes.cy; | ||
| delete node.attributes.r; | ||
| } | ||
| if (item.isElem('ellipse') && convertArcs) { | ||
| const ecx = Number(item.attributes.cx || '0'); | ||
| const ecy = Number(item.attributes.cy || '0'); | ||
| const rx = Number(item.attributes.rx || '0'); | ||
| const ry = Number(item.attributes.ry || '0'); | ||
| if (isNaN(ecx - ecy + rx - ry)) { | ||
| return; | ||
| } | ||
| const pathData = [ | ||
| { command: 'M', args: [ecx, ecy - ry] }, | ||
| { command: 'A', args: [rx, ry, 0, 1, 0, ecx, ecy + ry] }, | ||
| { command: 'A', args: [rx, ry, 0, 1, 0, ecx, ecy - ry] }, | ||
| { command: 'z', args: [] }, | ||
| ]; | ||
| item.attributes.d = stringifyPathData({ pathData, precision }); | ||
| item.renameElem('path'); | ||
| delete item.attributes.cx; | ||
| delete item.attributes.cy; | ||
| delete item.attributes.rx; | ||
| delete item.attributes.ry; | ||
| } | ||
| // optionally covert ellipse | ||
| if (node.name === 'ellipse' && convertArcs) { | ||
| const ecx = Number(node.attributes.cx || '0'); | ||
| const ecy = Number(node.attributes.cy || '0'); | ||
| const rx = Number(node.attributes.rx || '0'); | ||
| const ry = Number(node.attributes.ry || '0'); | ||
| if (Number.isNaN(ecx - ecy + rx - ry)) { | ||
| return; | ||
| } | ||
| const pathData = [ | ||
| { command: 'M', args: [ecx, ecy - ry] }, | ||
| { command: 'A', args: [rx, ry, 0, 1, 0, ecx, ecy + ry] }, | ||
| { command: 'A', args: [rx, ry, 0, 1, 0, ecx, ecy - ry] }, | ||
| { command: 'z', args: [] }, | ||
| ]; | ||
| node.name = 'path'; | ||
| node.attributes.d = stringifyPathData({ pathData, precision }); | ||
| delete node.attributes.cx; | ||
| delete node.attributes.cy; | ||
| delete node.attributes.rx; | ||
| delete node.attributes.ry; | ||
| } | ||
| }, | ||
| }, | ||
| }; | ||
| }; |
| 'use strict'; | ||
| exports.name = 'convertStyleToAttrs'; | ||
| exports.type = 'perItem'; | ||
@@ -4,0 +6,0 @@ |
| 'use strict'; | ||
| exports.name = 'convertTransform'; | ||
| exports.type = 'perItem'; | ||
@@ -4,0 +6,0 @@ |
@@ -7,2 +7,4 @@ 'use strict'; | ||
| exports.name = 'inlineStyles'; | ||
| exports.type = 'full'; | ||
@@ -9,0 +11,0 @@ |
| 'use strict'; | ||
| const { detachNodeFromParent } = require('../lib/xast.js'); | ||
| const { computeStyle } = require('../lib/style.js'); | ||
| const { collectStylesheet, computeStyle } = require('../lib/style.js'); | ||
| const { path2js, js2path, intersects } = require('./_path.js'); | ||
| exports.name = 'mergePaths'; | ||
| exports.type = 'visitor'; | ||
@@ -25,2 +26,3 @@ exports.active = true; | ||
| } = params; | ||
| const stylesheet = collectStylesheet(root); | ||
@@ -57,3 +59,3 @@ return { | ||
| // preserve paths with markers | ||
| const computedStyle = computeStyle(child); | ||
| const computedStyle = computeStyle(stylesheet, child); | ||
| if ( | ||
@@ -92,3 +94,3 @@ computedStyle['marker-start'] || | ||
| }); | ||
| detachNodeFromParent(child); | ||
| detachNodeFromParent(child, node); | ||
| continue; | ||
@@ -95,0 +97,0 @@ } |
@@ -6,2 +6,3 @@ 'use strict'; | ||
| exports.name = 'mergeStyles'; | ||
| exports.type = 'visitor'; | ||
@@ -21,3 +22,3 @@ exports.active = true; | ||
| const enterElement = (node) => { | ||
| const enterElement = (node, parentNode) => { | ||
| // collect style elements | ||
@@ -56,3 +57,3 @@ if (node.name !== 'style') { | ||
| if (css.trim().length === 0) { | ||
| detachNodeFromParent(node); | ||
| detachNodeFromParent(node, parentNode); | ||
| return; | ||
@@ -73,3 +74,3 @@ } | ||
| } else { | ||
| detachNodeFromParent(node); | ||
| detachNodeFromParent(node, parentNode); | ||
| firstStyleElement.children = [ | ||
@@ -76,0 +77,0 @@ new JSAPI( |
@@ -6,2 +6,4 @@ 'use strict'; | ||
| exports.name = 'minifyStyles'; | ||
| exports.type = 'full'; | ||
@@ -8,0 +10,0 @@ |
@@ -5,2 +5,4 @@ 'use strict'; | ||
| exports.name = 'moveElemsAttrsToGroup'; | ||
| exports.type = 'perItemReverse'; | ||
@@ -7,0 +9,0 @@ |
@@ -5,2 +5,4 @@ 'use strict'; | ||
| exports.name = 'moveGroupAttrsToElems'; | ||
| exports.type = 'perItem'; | ||
@@ -7,0 +9,0 @@ |
| 'use strict'; | ||
| // builtin presets | ||
| exports['preset-default'] = require('./preset-default.js'); | ||
| // builtin plugins | ||
| exports.addAttributesToSVGElement = require('./addAttributesToSVGElement.js'); | ||
@@ -4,0 +8,0 @@ exports.addClassesToSVGElement = require('./addClassesToSVGElement.js'); |
| 'use strict'; | ||
| exports.name = 'prefixIds'; | ||
| exports.type = 'perItem'; | ||
@@ -4,0 +6,0 @@ |
| 'use strict'; | ||
| exports.name = 'removeAttributesBySelector'; | ||
| exports.type = 'perItem'; | ||
@@ -4,0 +6,0 @@ |
@@ -5,2 +5,4 @@ 'use strict'; | ||
| exports.name = 'removeAttrs'; | ||
| exports.type = 'perItem'; | ||
@@ -7,0 +9,0 @@ |
| 'use strict'; | ||
| exports.type = 'perItem'; | ||
| const { detachNodeFromParent } = require('../lib/xast.js'); | ||
| exports.name = 'removeComments'; | ||
| exports.type = 'visitor'; | ||
| exports.active = true; | ||
| exports.description = 'removes comments'; | ||
@@ -16,11 +17,14 @@ | ||
| * | ||
| * @param {Object} item current iteration item | ||
| * @return {Boolean} if false, item will be filtered out | ||
| * | ||
| * @author Kir Belevich | ||
| */ | ||
| exports.fn = function (item) { | ||
| if (item.type === 'comment' && item.value.charAt(0) !== '!') { | ||
| return false; | ||
| } | ||
| exports.fn = () => { | ||
| return { | ||
| comment: { | ||
| enter: (node, parentNode) => { | ||
| if (node.value.charAt(0) !== '!') { | ||
| detachNodeFromParent(node, parentNode); | ||
| } | ||
| }, | ||
| }, | ||
| }; | ||
| }; |
+22
-20
| 'use strict'; | ||
| exports.type = 'perItem'; | ||
| const { detachNodeFromParent } = require('../lib/xast.js'); | ||
| exports.name = 'removeDesc'; | ||
| exports.type = 'visitor'; | ||
| exports.active = true; | ||
| exports.params = { | ||
| removeAny: true, | ||
| }; | ||
| exports.description = 'removes <desc>'; | ||
| var standardDescs = /^(Created with|Created using)/; | ||
| const standardDescs = /^(Created with|Created using)/; | ||
@@ -22,17 +19,22 @@ /** | ||
| * | ||
| * @param {Object} item current iteration item | ||
| * @return {Boolean} if false, item will be filtered out | ||
| * | ||
| * @author Daniel Wabyick | ||
| */ | ||
| exports.fn = function (item, params) { | ||
| return ( | ||
| !item.isElem('desc') || | ||
| !( | ||
| params.removeAny || | ||
| item.children.length === 0 || | ||
| (item.children[0].type === 'text' && | ||
| standardDescs.test(item.children[0].value)) | ||
| ) | ||
| ); | ||
| exports.fn = (root, params) => { | ||
| const { removeAny = true } = params; | ||
| return { | ||
| element: { | ||
| enter: (node, parentNode) => { | ||
| if (node.name === 'desc') { | ||
| if ( | ||
| removeAny || | ||
| node.children.length === 0 || | ||
| (node.children[0].type === 'text' && | ||
| standardDescs.test(node.children[0].value)) | ||
| ) { | ||
| detachNodeFromParent(node, parentNode); | ||
| } | ||
| } | ||
| }, | ||
| }, | ||
| }; | ||
| }; |
| 'use strict'; | ||
| exports.name = 'removeDimensions'; | ||
| exports.type = 'perItem'; | ||
@@ -4,0 +6,0 @@ |
| 'use strict'; | ||
| exports.type = 'perItem'; | ||
| const { detachNodeFromParent } = require('../lib/xast.js'); | ||
| exports.name = 'removeDoctype'; | ||
| exports.type = 'visitor'; | ||
| exports.active = true; | ||
| exports.description = 'removes doctype declaration'; | ||
@@ -29,11 +30,12 @@ | ||
| * | ||
| * @param {Object} item current iteration item | ||
| * @return {Boolean} if false, item will be filtered out | ||
| * | ||
| * @author Kir Belevich | ||
| */ | ||
| exports.fn = function (item) { | ||
| if (item.type === 'doctype') { | ||
| return false; | ||
| } | ||
| exports.fn = () => { | ||
| return { | ||
| doctype: { | ||
| enter: (node, parentNode) => { | ||
| detachNodeFromParent(node, parentNode); | ||
| }, | ||
| }, | ||
| }; | ||
| }; |
@@ -6,2 +6,4 @@ 'use strict'; | ||
| exports.name = 'removeEditorsNSData'; | ||
| exports.type = 'perItem'; | ||
@@ -8,0 +10,0 @@ |
| 'use strict'; | ||
| exports.name = 'removeElementsByAttr'; | ||
| exports.type = 'perItem'; | ||
@@ -4,0 +6,0 @@ |
@@ -5,2 +5,4 @@ 'use strict'; | ||
| exports.name = 'removeEmptyAttrs'; | ||
| exports.type = 'perItem'; | ||
@@ -7,0 +9,0 @@ |
@@ -5,2 +5,4 @@ 'use strict'; | ||
| exports.name = 'removeEmptyContainers'; | ||
| exports.type = 'perItemReverse'; | ||
@@ -7,0 +9,0 @@ |
| 'use strict'; | ||
| exports.type = 'perItem'; | ||
| const { detachNodeFromParent } = require('../lib/xast.js'); | ||
| exports.name = 'removeEmptyText'; | ||
| exports.type = 'visitor'; | ||
| exports.active = true; | ||
| exports.description = 'removes empty <text> elements'; | ||
| exports.params = { | ||
| text: true, | ||
| tspan: true, | ||
| tref: true, | ||
| }; | ||
| /** | ||
@@ -30,29 +25,28 @@ * Remove empty Text elements. | ||
| * | ||
| * @param {Object} item current iteration item | ||
| * @param {Object} params plugin params | ||
| * @return {Boolean} if false, item will be filtered out | ||
| * | ||
| * @author Kir Belevich | ||
| */ | ||
| exports.fn = function (item, params) { | ||
| if (item.type === 'element') { | ||
| // Remove empty text element | ||
| if (params.text && item.name === 'text' && item.children.length === 0) { | ||
| return false; | ||
| } | ||
| // Remove empty tspan element | ||
| if (params.tspan && item.name === 'tspan' && item.children.length === 0) { | ||
| return false; | ||
| } | ||
| // Remove tref with empty xlink:href attribute | ||
| if ( | ||
| params.tref && | ||
| item.name === 'tref' && | ||
| item.attributes['xlink:href'] == null | ||
| ) { | ||
| return false; | ||
| } | ||
| } | ||
| exports.fn = (root, params) => { | ||
| const { text = true, tspan = true, tref = true } = params; | ||
| return { | ||
| element: { | ||
| enter: (node, parentNode) => { | ||
| // Remove empty text element | ||
| if (text && node.name === 'text' && node.children.length === 0) { | ||
| detachNodeFromParent(node, parentNode); | ||
| } | ||
| // Remove empty tspan element | ||
| if (tspan && node.name === 'tspan' && node.children.length === 0) { | ||
| detachNodeFromParent(node, parentNode); | ||
| } | ||
| // Remove tref with empty xlink:href attribute | ||
| if ( | ||
| tref && | ||
| node.name === 'tref' && | ||
| node.attributes['xlink:href'] == null | ||
| ) { | ||
| detachNodeFromParent(node, parentNode); | ||
| } | ||
| }, | ||
| }, | ||
| }; | ||
| }; |
@@ -8,5 +8,6 @@ 'use strict'; | ||
| } = require('../lib/xast.js'); | ||
| const { computeStyle } = require('../lib/style.js'); | ||
| const { collectStylesheet, computeStyle } = require('../lib/style.js'); | ||
| const { parsePathData } = require('../lib/path.js'); | ||
| exports.name = 'removeHiddenElems'; | ||
| exports.type = 'visitor'; | ||
@@ -53,8 +54,10 @@ exports.active = true; | ||
| } = params; | ||
| const stylesheet = collectStylesheet(root); | ||
| return { | ||
| element: { | ||
| enter: (node) => { | ||
| enter: (node, parentNode) => { | ||
| // Removes hidden elements | ||
| // https://www.w3schools.com/cssref/pr_class_visibility.asp | ||
| const computedStyle = computeStyle(node); | ||
| const computedStyle = computeStyle(stylesheet, node); | ||
| if ( | ||
@@ -68,3 +71,3 @@ isHidden && | ||
| ) { | ||
| detachNodeFromParent(node); | ||
| detachNodeFromParent(node, parentNode); | ||
| return; | ||
@@ -86,3 +89,3 @@ } | ||
| ) { | ||
| detachNodeFromParent(node); | ||
| detachNodeFromParent(node, parentNode); | ||
| return; | ||
@@ -102,3 +105,3 @@ } | ||
| ) { | ||
| detachNodeFromParent(node); | ||
| detachNodeFromParent(node, parentNode); | ||
| return; | ||
@@ -119,3 +122,3 @@ } | ||
| ) { | ||
| detachNodeFromParent(node); | ||
| detachNodeFromParent(node, parentNode); | ||
| return; | ||
@@ -136,3 +139,3 @@ } | ||
| ) { | ||
| detachNodeFromParent(node); | ||
| detachNodeFromParent(node, parentNode); | ||
| return; | ||
@@ -153,3 +156,3 @@ } | ||
| ) { | ||
| detachNodeFromParent(node); | ||
| detachNodeFromParent(node, parentNode); | ||
| return; | ||
@@ -170,3 +173,3 @@ } | ||
| ) { | ||
| detachNodeFromParent(node); | ||
| detachNodeFromParent(node, parentNode); | ||
| return; | ||
@@ -188,3 +191,3 @@ } | ||
| ) { | ||
| detachNodeFromParent(node); | ||
| detachNodeFromParent(node, parentNode); | ||
| return; | ||
@@ -204,3 +207,3 @@ } | ||
| ) { | ||
| detachNodeFromParent(node); | ||
| detachNodeFromParent(node, parentNode); | ||
| return; | ||
@@ -220,3 +223,3 @@ } | ||
| ) { | ||
| detachNodeFromParent(node); | ||
| detachNodeFromParent(node, parentNode); | ||
| return; | ||
@@ -236,3 +239,3 @@ } | ||
| ) { | ||
| detachNodeFromParent(node); | ||
| detachNodeFromParent(node, parentNode); | ||
| return; | ||
@@ -252,3 +255,3 @@ } | ||
| ) { | ||
| detachNodeFromParent(node); | ||
| detachNodeFromParent(node, parentNode); | ||
| return; | ||
@@ -264,3 +267,3 @@ } | ||
| if (node.attributes.d == null) { | ||
| detachNodeFromParent(node); | ||
| detachNodeFromParent(node, parentNode); | ||
| return; | ||
@@ -270,3 +273,3 @@ } | ||
| if (pathData.length === 0) { | ||
| detachNodeFromParent(node); | ||
| detachNodeFromParent(node, parentNode); | ||
| return; | ||
@@ -280,3 +283,3 @@ } | ||
| ) { | ||
| detachNodeFromParent(node); | ||
| detachNodeFromParent(node, parentNode); | ||
| return; | ||
@@ -297,3 +300,3 @@ } | ||
| ) { | ||
| detachNodeFromParent(node); | ||
| detachNodeFromParent(node, parentNode); | ||
| return; | ||
@@ -312,3 +315,3 @@ } | ||
| ) { | ||
| detachNodeFromParent(node); | ||
| detachNodeFromParent(node, parentNode); | ||
| return; | ||
@@ -315,0 +318,0 @@ } |
| 'use strict'; | ||
| exports.type = 'perItem'; | ||
| const { detachNodeFromParent } = require('../lib/xast.js'); | ||
| exports.name = 'removeMetadata'; | ||
| exports.type = 'visitor'; | ||
| exports.active = true; | ||
| exports.description = 'removes <metadata>'; | ||
@@ -14,9 +15,14 @@ | ||
| * | ||
| * @param {Object} item current iteration item | ||
| * @return {Boolean} if false, item will be filtered out | ||
| * | ||
| * @author Kir Belevich | ||
| */ | ||
| exports.fn = function (item) { | ||
| return !item.isElem('metadata'); | ||
| exports.fn = () => { | ||
| return { | ||
| element: { | ||
| enter: (node, parentNode) => { | ||
| if (node.name === 'metadata') { | ||
| detachNodeFromParent(node, parentNode); | ||
| } | ||
| }, | ||
| }, | ||
| }; | ||
| }; |
| 'use strict'; | ||
| exports.name = 'removeNonInheritableGroupAttrs'; | ||
| exports.type = 'perItem'; | ||
@@ -4,0 +6,0 @@ |
| 'use strict'; | ||
| exports.name = 'removeOffCanvasPaths'; | ||
| exports.type = 'perItem'; | ||
@@ -4,0 +6,0 @@ |
| 'use strict'; | ||
| exports.type = 'perItem'; | ||
| const { detachNodeFromParent } = require('../lib/xast.js'); | ||
| exports.name = 'removeRasterImages'; | ||
| exports.type = 'visitor'; | ||
| exports.active = false; | ||
| exports.description = 'removes raster images (disabled by default)'; | ||
@@ -14,16 +15,18 @@ | ||
| * | ||
| * @param {Object} item current iteration item | ||
| * @return {Boolean} if false, item will be filtered out | ||
| * | ||
| * @author Kir Belevich | ||
| */ | ||
| exports.fn = function (item) { | ||
| if ( | ||
| item.type === 'element' && | ||
| item.name === 'image' && | ||
| item.attributes['xlink:href'] != null && | ||
| /(\.|image\/)(jpg|png|gif)/.test(item.attributes['xlink:href']) | ||
| ) { | ||
| return false; | ||
| } | ||
| exports.fn = () => { | ||
| return { | ||
| element: { | ||
| enter: (node, parentNode) => { | ||
| if ( | ||
| node.name === 'image' && | ||
| node.attributes['xlink:href'] != null && | ||
| /(\.|image\/)(jpg|png|gif)/.test(node.attributes['xlink:href']) | ||
| ) { | ||
| detachNodeFromParent(node, parentNode); | ||
| } | ||
| }, | ||
| }, | ||
| }; | ||
| }; |
| 'use strict'; | ||
| exports.type = 'perItem'; | ||
| const { detachNodeFromParent } = require('../lib/xast.js'); | ||
| exports.name = 'removeScriptElement'; | ||
| exports.type = 'visitor'; | ||
| exports.active = false; | ||
| exports.description = 'removes <script> elements (disabled by default)'; | ||
@@ -14,9 +15,15 @@ | ||
| * | ||
| * @param {Object} item current iteration item | ||
| * @return {Boolean} if false, item will be filtered out | ||
| * | ||
| * @author Patrick Klingemann | ||
| */ | ||
| exports.fn = function (item) { | ||
| return !item.isElem('script'); | ||
| exports.fn = () => { | ||
| return { | ||
| element: { | ||
| enter: (node, parentNode) => { | ||
| if (node.name === 'script') { | ||
| detachNodeFromParent(node, parentNode); | ||
| } | ||
| }, | ||
| }, | ||
| }; | ||
| }; |
| 'use strict'; | ||
| exports.type = 'perItem'; | ||
| const { detachNodeFromParent } = require('../lib/xast.js'); | ||
| exports.name = 'removeStyleElement'; | ||
| exports.type = 'visitor'; | ||
| exports.active = false; | ||
| exports.description = 'removes <style> element (disabled by default)'; | ||
@@ -14,9 +15,14 @@ | ||
| * | ||
| * @param {Object} item current iteration item | ||
| * @return {Boolean} if false, item will be filtered out | ||
| * | ||
| * @author Betsy Dupuis | ||
| */ | ||
| exports.fn = function (item) { | ||
| return !item.isElem('style'); | ||
| exports.fn = () => { | ||
| return { | ||
| element: { | ||
| enter: (node, parentNode) => { | ||
| if (node.name === 'style') { | ||
| detachNodeFromParent(node, parentNode); | ||
| } | ||
| }, | ||
| }, | ||
| }; | ||
| }; |
| 'use strict'; | ||
| exports.type = 'perItem'; | ||
| const { detachNodeFromParent } = require('../lib/xast.js'); | ||
| exports.name = 'removeTitle'; | ||
| exports.type = 'visitor'; | ||
| exports.active = true; | ||
| exports.description = 'removes <title>'; | ||
@@ -14,9 +15,14 @@ | ||
| * | ||
| * @param {Object} item current iteration item | ||
| * @return {Boolean} if false, item will be filtered out | ||
| * | ||
| * @author Igor Kalashnikov | ||
| */ | ||
| exports.fn = function (item) { | ||
| return !item.isElem('title'); | ||
| exports.fn = () => { | ||
| return { | ||
| element: { | ||
| enter: (node, parentNode) => { | ||
| if (node.name === 'title') { | ||
| detachNodeFromParent(node, parentNode); | ||
| } | ||
| }, | ||
| }, | ||
| }; | ||
| }; |
@@ -5,2 +5,4 @@ 'use strict'; | ||
| exports.name = 'removeUnknownsAndDefaults'; | ||
| exports.type = 'perItem'; | ||
@@ -7,0 +9,0 @@ |
@@ -6,2 +6,4 @@ 'use strict'; | ||
| exports.name = 'removeUnusedNS'; | ||
| exports.type = 'full'; | ||
@@ -8,0 +10,0 @@ |
@@ -5,2 +5,4 @@ 'use strict'; | ||
| exports.name = 'removeUselessDefs'; | ||
| exports.type = 'perItem'; | ||
@@ -7,0 +9,0 @@ |
| 'use strict'; | ||
| exports.name = 'removeUselessStrokeAndFill'; | ||
| exports.type = 'perItem'; | ||
@@ -4,0 +6,0 @@ |
@@ -5,2 +5,4 @@ 'use strict'; | ||
| exports.name = 'removeViewBox'; | ||
| exports.type = 'perItem'; | ||
@@ -7,0 +9,0 @@ |
| 'use strict'; | ||
| exports.name = 'removeXMLNS'; | ||
| exports.type = 'perItem'; | ||
@@ -4,0 +6,0 @@ |
| 'use strict'; | ||
| exports.type = 'perItem'; | ||
| const { detachNodeFromParent } = require('../lib/xast.js'); | ||
| exports.name = 'removeXMLProcInst'; | ||
| exports.type = 'visitor'; | ||
| exports.active = true; | ||
| exports.description = 'removes XML processing instructions'; | ||
@@ -15,12 +16,14 @@ | ||
| * | ||
| * @param {Object} item current iteration item | ||
| * @return {Boolean} if false, item will be filtered out | ||
| * | ||
| * @author Kir Belevich | ||
| */ | ||
| exports.fn = function (item) { | ||
| if (item.type === 'instruction' && item.name === 'xml') { | ||
| return false; | ||
| } | ||
| return true; | ||
| exports.fn = () => { | ||
| return { | ||
| instruction: { | ||
| enter: (node, parentNode) => { | ||
| if (node.name === 'xml') { | ||
| detachNodeFromParent(node, parentNode); | ||
| } | ||
| }, | ||
| }, | ||
| }; | ||
| }; |
@@ -6,2 +6,4 @@ 'use strict'; | ||
| exports.name = 'reusePaths'; | ||
| exports.type = 'full'; | ||
@@ -8,0 +10,0 @@ |
@@ -5,2 +5,4 @@ 'use strict'; | ||
| exports.name = 'sortAttrs'; | ||
| exports.type = 'perItem'; | ||
@@ -7,0 +9,0 @@ |
| 'use strict'; | ||
| exports.name = 'sortDefsChildren'; | ||
| exports.type = 'perItem'; | ||
@@ -4,0 +6,0 @@ |
+143
-156
@@ -5,3 +5,2 @@ <div align="center"> | ||
| ## SVGO [](https://npmjs.org/package/svgo) [](https://discord.gg/z8jX8NYxrE) | ||
@@ -57,4 +56,4 @@ | ||
| pretty: true, // boolean, false by default | ||
| } | ||
| } | ||
| }, | ||
| }; | ||
| ``` | ||
@@ -72,3 +71,3 @@ | ||
| { | ||
| name: 'builtinPluginName' | ||
| name: 'builtinPluginName', | ||
| }, | ||
@@ -79,87 +78,73 @@ // some plugins allow/require to pass options | ||
| params: { | ||
| optionName: 'optionValue' | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| optionName: 'optionValue', | ||
| }, | ||
| }, | ||
| ], | ||
| }; | ||
| ``` | ||
| The default list is fully overridden if the `plugins` field is specified. To extend the default | ||
| list use the `extendDefaultPlugins` utility: | ||
| The default preset of plugins is fully overridden if the `plugins` field is specified. | ||
| Use `preset-default` plugin to customize plugins options. | ||
| ```js | ||
| const { extendDefaultPlugins } = require('svgo'); | ||
| module.exports = { | ||
| plugins: extendDefaultPlugins([ | ||
| plugins: [ | ||
| { | ||
| name: 'builtinPluginName', | ||
| name: 'preset-default', | ||
| params: { | ||
| optionName: 'optionValue' | ||
| } | ||
| } | ||
| ]) | ||
| } | ||
| overrides: { | ||
| // customize options | ||
| builtinPluginName: { | ||
| optionName: 'optionValue', | ||
| }, | ||
| // or disable plugins | ||
| anotherBuiltinPlugin: false, | ||
| }, | ||
| }, | ||
| }, | ||
| ], | ||
| }; | ||
| ``` | ||
| To disable one of the default plugins use the `active` field: | ||
| Default preset includes the following list of plugins: | ||
| ```js | ||
| const { extendDefaultPlugins } = require('svgo'); | ||
| module.exports = { | ||
| plugins: extendDefaultPlugins([ | ||
| { | ||
| name: 'builtinPluginName', | ||
| active: false | ||
| } | ||
| ]) | ||
| } | ||
| ``` | ||
| - removeDoctype | ||
| - removeXMLProcInst | ||
| - removeComments | ||
| - removeMetadata | ||
| - removeEditorsNSData | ||
| - cleanupAttrs | ||
| - mergeStyles | ||
| - inlineStyles | ||
| - minifyStyles | ||
| - cleanupIDs | ||
| - removeUselessDefs | ||
| - cleanupNumericValues | ||
| - convertColors | ||
| - removeUnknownsAndDefaults | ||
| - removeNonInheritableGroupAttrs | ||
| - removeUselessStrokeAndFill | ||
| - removeViewBox | ||
| - cleanupEnableBackground | ||
| - removeHiddenElems | ||
| - removeEmptyText | ||
| - convertShapeToPath | ||
| - convertEllipseToCircle | ||
| - moveElemsAttrsToGroup | ||
| - moveGroupAttrsToElems | ||
| - collapseGroups | ||
| - convertPathData | ||
| - convertTransform | ||
| - removeEmptyAttrs | ||
| - removeEmptyContainers | ||
| - mergePaths | ||
| - removeUnusedNS | ||
| - sortDefsChildren | ||
| - removeTitle | ||
| - removeDesc | ||
| See the list of the default plugins: | ||
| ```js | ||
| module.exports = { | ||
| plugins: [ | ||
| 'removeDoctype', | ||
| 'removeXMLProcInst', | ||
| 'removeComments', | ||
| 'removeMetadata', | ||
| 'removeEditorsNSData', | ||
| 'cleanupAttrs', | ||
| 'mergeStyles', | ||
| 'inlineStyles', | ||
| 'minifyStyles', | ||
| 'cleanupIDs', | ||
| 'removeUselessDefs', | ||
| 'cleanupNumericValues', | ||
| 'convertColors', | ||
| 'removeUnknownsAndDefaults', | ||
| 'removeNonInheritableGroupAttrs', | ||
| 'removeUselessStrokeAndFill', | ||
| 'removeViewBox', | ||
| 'cleanupEnableBackground', | ||
| 'removeHiddenElems', | ||
| 'removeEmptyText', | ||
| 'convertShapeToPath', | ||
| 'convertEllipseToCircle', | ||
| 'moveElemsAttrsToGroup', | ||
| 'moveGroupAttrsToElems', | ||
| 'collapseGroups', | ||
| 'convertPathData', | ||
| 'convertTransform', | ||
| 'removeEmptyAttrs', | ||
| 'removeEmptyContainers', | ||
| 'mergePaths', | ||
| 'removeUnusedNS', | ||
| 'sortDefsChildren', | ||
| 'removeTitle', | ||
| 'removeDesc' | ||
| ] | ||
| } | ||
| ``` | ||
| It's also possible to specify a custom plugin: | ||
| ```js | ||
| const anotherCustomPlugin = require('./another-custom-plugin.js') | ||
| const anotherCustomPlugin = require('./another-custom-plugin.js'); | ||
| module.exports = { | ||
@@ -173,7 +158,7 @@ plugins: [ | ||
| }, | ||
| fn: (ast, params, info) => {} | ||
| fn: (ast, params, info) => {}, | ||
| }, | ||
| anotherCustomPlugin | ||
| ] | ||
| } | ||
| anotherCustomPlugin, | ||
| ], | ||
| }; | ||
| ``` | ||
@@ -195,5 +180,5 @@ | ||
| // all config fields are also available here | ||
| multipass: true | ||
| }) | ||
| const optimizedSvgString = result.data | ||
| multipass: true, | ||
| }); | ||
| const optimizedSvgString = result.data; | ||
| ``` | ||
@@ -207,6 +192,6 @@ | ||
| const { loadConfig } = require('svgo'); | ||
| const config = await loadConfig() | ||
| const config = await loadConfig(); | ||
| // you can also specify a relative or absolute path and customize the current working directory | ||
| const config = await loadConfig(configFile, cwd) | ||
| const config = await loadConfig(configFile, cwd); | ||
| ``` | ||
@@ -216,75 +201,77 @@ | ||
| | Plugin | Description | Default | | ||
| | ------ | ----------- | ------- | | ||
| | [cleanupAttrs](https://github.com/svg/svgo/blob/master/plugins/cleanupAttrs.js) | cleanup attributes from newlines, trailing, and repeating spaces | `enabled` | | ||
| | [mergeStyles](https://github.com/svg/svgo/blob/master/plugins/mergeStyles.js) | merge multiple style elements into one | `enabled` | | ||
| | [inlineStyles](https://github.com/svg/svgo/blob/master/plugins/inlineStyles.js) | move and merge styles from `<style>` elements to element `style` attributes | `enabled` | | ||
| | [removeDoctype](https://github.com/svg/svgo/blob/master/plugins/removeDoctype.js) | remove `doctype` declaration | `enabled` | | ||
| | [removeXMLProcInst](https://github.com/svg/svgo/blob/master/plugins/removeXMLProcInst.js) | remove XML processing instructions | `enabled` | | ||
| | [removeComments](https://github.com/svg/svgo/blob/master/plugins/removeComments.js) | remove comments | `enabled` | | ||
| | [removeMetadata](https://github.com/svg/svgo/blob/master/plugins/removeMetadata.js) | remove `<metadata>` | `enabled` | | ||
| | [removeTitle](https://github.com/svg/svgo/blob/master/plugins/removeTitle.js) | remove `<title>` | `enabled` | | ||
| | [removeDesc](https://github.com/svg/svgo/blob/master/plugins/removeDesc.js) | remove `<desc>` | `enabled` | | ||
| | [removeUselessDefs](https://github.com/svg/svgo/blob/master/plugins/removeUselessDefs.js) | remove elements of `<defs>` without `id` | `enabled` | | ||
| | [removeXMLNS](https://github.com/svg/svgo/blob/master/plugins/removeXMLNS.js) | removes the `xmlns` attribute (for inline SVG) | `disabled` | | ||
| | [removeEditorsNSData](https://github.com/svg/svgo/blob/master/plugins/removeEditorsNSData.js) | remove editors namespaces, elements, and attributes | `enabled` | | ||
| | [removeEmptyAttrs](https://github.com/svg/svgo/blob/master/plugins/removeEmptyAttrs.js) | remove empty attributes | `enabled` | | ||
| | [removeHiddenElems](https://github.com/svg/svgo/blob/master/plugins/removeHiddenElems.js) | remove hidden elements | `enabled` | | ||
| | [removeEmptyText](https://github.com/svg/svgo/blob/master/plugins/removeEmptyText.js) | remove empty Text elements | `enabled` | | ||
| | [removeEmptyContainers](https://github.com/svg/svgo/blob/master/plugins/removeEmptyContainers.js) | remove empty Container elements | `enabled` | | ||
| | [removeViewBox](https://github.com/svg/svgo/blob/master/plugins/removeViewBox.js) | remove `viewBox` attribute when possible | `enabled` | | ||
| | [cleanupEnableBackground](https://github.com/svg/svgo/blob/master/plugins/cleanupEnableBackground.js) | remove or cleanup `enable-background` attribute when possible | `enabled` | | ||
| | [minifyStyles](https://github.com/svg/svgo/blob/master/plugins/minifyStyles.js) | minify `<style>` elements content with [CSSO](https://github.com/css/csso) | `enabled` | | ||
| | [convertStyleToAttrs](https://github.com/svg/svgo/blob/master/plugins/convertStyleToAttrs.js) | convert styles into attributes | `disabled ` | | ||
| | [convertColors](https://github.com/svg/svgo/blob/master/plugins/convertColors.js) | convert colors (from `rgb()` to `#rrggbb`, from `#rrggbb` to `#rgb`) | `enabled` | | ||
| | [convertPathData](https://github.com/svg/svgo/blob/master/plugins/convertPathData.js) | convert Path data to relative or absolute (whichever is shorter), convert one segment to another, trim useless delimiters, smart rounding, and much more | `enabled` | | ||
| | [convertTransform](https://github.com/svg/svgo/blob/master/plugins/convertTransform.js) | collapse multiple transforms into one, convert matrices to the short aliases, and much more | `enabled` | | ||
| | [removeUnknownsAndDefaults](https://github.com/svg/svgo/blob/master/plugins/removeUnknownsAndDefaults.js) | remove unknown elements content and attributes, remove attributes with default values | `enabled` | | ||
| | [removeNonInheritableGroupAttrs](https://github.com/svg/svgo/blob/master/plugins/removeNonInheritableGroupAttrs.js) | remove non-inheritable group's "presentation" attributes | `enabled` | | ||
| | [removeUselessStrokeAndFill](https://github.com/svg/svgo/blob/master/plugins/removeUselessStrokeAndFill.js) | remove useless `stroke` and `fill` attributes | `enabled` | | ||
| | [removeUnusedNS](https://github.com/svg/svgo/blob/master/plugins/removeUnusedNS.js) | remove unused namespaces declaration | `enabled` | | ||
| | [prefixIds](https://github.com/svg/svgo/blob/master/plugins/prefixIds.js) | prefix IDs and classes with the SVG filename or an arbitrary string | `disabled` | | ||
| | [cleanupIDs](https://github.com/svg/svgo/blob/master/plugins/cleanupIDs.js) | remove unused and minify used IDs | `enabled` | | ||
| | [cleanupNumericValues](https://github.com/svg/svgo/blob/master/plugins/cleanupNumericValues.js) | round numeric values to the fixed precision, remove default `px` units | `enabled` | | ||
| | [cleanupListOfValues](https://github.com/svg/svgo/blob/master/plugins/cleanupListOfValues.js) | round numeric values in attributes that take a list of numbers (like `viewBox` or `enable-background`) | `disabled` | | ||
| | [moveElemsAttrsToGroup](https://github.com/svg/svgo/blob/master/plugins/moveElemsAttrsToGroup.js) | move elements' attributes to their enclosing group | `enabled` | | ||
| | [moveGroupAttrsToElems](https://github.com/svg/svgo/blob/master/plugins/moveGroupAttrsToElems.js) | move some group attributes to the contained elements | `enabled` | | ||
| | [collapseGroups](https://github.com/svg/svgo/blob/master/plugins/collapseGroups.js) | collapse useless groups | `enabled` | | ||
| | [removeRasterImages](https://github.com/svg/svgo/blob/master/plugins/removeRasterImages.js) | remove raster images | `disabled` | | ||
| | [mergePaths](https://github.com/svg/svgo/blob/master/plugins/mergePaths.js) | merge multiple Paths into one | `enabled` | | ||
| | [convertShapeToPath](https://github.com/svg/svgo/blob/master/plugins/convertShapeToPath.js) | convert some basic shapes to `<path>` | `enabled` | | ||
| | [convertEllipseToCircle](https://github.com/svg/svgo/blob/master/plugins/convertEllipseToCircle.js) | convert non-eccentric `<ellipse>` to `<circle>` | `enabled` | | ||
| | [sortAttrs](https://github.com/svg/svgo/blob/master/plugins/sortAttrs.js) | sort element attributes for epic readability | `disabled` | | ||
| | [sortDefsChildren](https://github.com/svg/svgo/blob/master/plugins/sortDefsChildren.js) | sort children of `<defs>` in order to improve compression | `enabled` | | ||
| | [removeDimensions](https://github.com/svg/svgo/blob/master/plugins/removeDimensions.js) | remove `width`/`height` and add `viewBox` if it's missing (opposite to removeViewBox, disable it first) | `disabled` | | ||
| | [removeAttrs](https://github.com/svg/svgo/blob/master/plugins/removeAttrs.js) | remove attributes by pattern | `disabled` | | ||
| | [removeAttributesBySelector](https://github.com/svg/svgo/blob/master/plugins/removeAttributesBySelector.js) | removes attributes of elements that match a CSS selector | `disabled` | | ||
| | [removeElementsByAttr](https://github.com/svg/svgo/blob/master/plugins/removeElementsByAttr.js) | remove arbitrary elements by `ID` or `className` | `disabled` | | ||
| | [addClassesToSVGElement](https://github.com/svg/svgo/blob/master/plugins/addClassesToSVGElement.js) | add classnames to an outer `<svg>` element | `disabled` | | ||
| | [addAttributesToSVGElement](https://github.com/svg/svgo/blob/master/plugins/addAttributesToSVGElement.js) | adds attributes to an outer `<svg>` element | `disabled` | | ||
| | [removeOffCanvasPaths](https://github.com/svg/svgo/blob/master/plugins/removeOffCanvasPaths.js) | removes elements that are drawn outside of the viewbox | `disabled` | | ||
| | [removeStyleElement](https://github.com/svg/svgo/blob/master/plugins/removeStyleElement.js) | remove `<style>` elements | `disabled` | | ||
| | [removeScriptElement](https://github.com/svg/svgo/blob/master/plugins/removeScriptElement.js) | remove `<script>` elements | `disabled` | | ||
| | [reusePaths](https://github.com/svg/svgo/blob/master/plugins/reusePaths.js) | Find duplicated <path> elements and replace them with <use> links | `disabled` | | ||
| | Plugin | Description | Default | | ||
| | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | | ||
| | [cleanupAttrs](https://github.com/svg/svgo/blob/master/plugins/cleanupAttrs.js) | cleanup attributes from newlines, trailing, and repeating spaces | `enabled` | | ||
| | [mergeStyles](https://github.com/svg/svgo/blob/master/plugins/mergeStyles.js) | merge multiple style elements into one | `enabled` | | ||
| | [inlineStyles](https://github.com/svg/svgo/blob/master/plugins/inlineStyles.js) | move and merge styles from `<style>` elements to element `style` attributes | `enabled` | | ||
| | [removeDoctype](https://github.com/svg/svgo/blob/master/plugins/removeDoctype.js) | remove `doctype` declaration | `enabled` | | ||
| | [removeXMLProcInst](https://github.com/svg/svgo/blob/master/plugins/removeXMLProcInst.js) | remove XML processing instructions | `enabled` | | ||
| | [removeComments](https://github.com/svg/svgo/blob/master/plugins/removeComments.js) | remove comments | `enabled` | | ||
| | [removeMetadata](https://github.com/svg/svgo/blob/master/plugins/removeMetadata.js) | remove `<metadata>` | `enabled` | | ||
| | [removeTitle](https://github.com/svg/svgo/blob/master/plugins/removeTitle.js) | remove `<title>` | `enabled` | | ||
| | [removeDesc](https://github.com/svg/svgo/blob/master/plugins/removeDesc.js) | remove `<desc>` | `enabled` | | ||
| | [removeUselessDefs](https://github.com/svg/svgo/blob/master/plugins/removeUselessDefs.js) | remove elements of `<defs>` without `id` | `enabled` | | ||
| | [removeXMLNS](https://github.com/svg/svgo/blob/master/plugins/removeXMLNS.js) | removes the `xmlns` attribute (for inline SVG) | `disabled` | | ||
| | [removeEditorsNSData](https://github.com/svg/svgo/blob/master/plugins/removeEditorsNSData.js) | remove editors namespaces, elements, and attributes | `enabled` | | ||
| | [removeEmptyAttrs](https://github.com/svg/svgo/blob/master/plugins/removeEmptyAttrs.js) | remove empty attributes | `enabled` | | ||
| | [removeHiddenElems](https://github.com/svg/svgo/blob/master/plugins/removeHiddenElems.js) | remove hidden elements | `enabled` | | ||
| | [removeEmptyText](https://github.com/svg/svgo/blob/master/plugins/removeEmptyText.js) | remove empty Text elements | `enabled` | | ||
| | [removeEmptyContainers](https://github.com/svg/svgo/blob/master/plugins/removeEmptyContainers.js) | remove empty Container elements | `enabled` | | ||
| | [removeViewBox](https://github.com/svg/svgo/blob/master/plugins/removeViewBox.js) | remove `viewBox` attribute when possible | `enabled` | | ||
| | [cleanupEnableBackground](https://github.com/svg/svgo/blob/master/plugins/cleanupEnableBackground.js) | remove or cleanup `enable-background` attribute when possible | `enabled` | | ||
| | [minifyStyles](https://github.com/svg/svgo/blob/master/plugins/minifyStyles.js) | minify `<style>` elements content with [CSSO](https://github.com/css/csso) | `enabled` | | ||
| | [convertStyleToAttrs](https://github.com/svg/svgo/blob/master/plugins/convertStyleToAttrs.js) | convert styles into attributes | `disabled` | | ||
| | [convertColors](https://github.com/svg/svgo/blob/master/plugins/convertColors.js) | convert colors (from `rgb()` to `#rrggbb`, from `#rrggbb` to `#rgb`) | `enabled` | | ||
| | [convertPathData](https://github.com/svg/svgo/blob/master/plugins/convertPathData.js) | convert Path data to relative or absolute (whichever is shorter), convert one segment to another, trim useless delimiters, smart rounding, and much more | `enabled` | | ||
| | [convertTransform](https://github.com/svg/svgo/blob/master/plugins/convertTransform.js) | collapse multiple transforms into one, convert matrices to the short aliases, and much more | `enabled` | | ||
| | [removeUnknownsAndDefaults](https://github.com/svg/svgo/blob/master/plugins/removeUnknownsAndDefaults.js) | remove unknown elements content and attributes, remove attributes with default values | `enabled` | | ||
| | [removeNonInheritableGroupAttrs](https://github.com/svg/svgo/blob/master/plugins/removeNonInheritableGroupAttrs.js) | remove non-inheritable group's "presentation" attributes | `enabled` | | ||
| | [removeUselessStrokeAndFill](https://github.com/svg/svgo/blob/master/plugins/removeUselessStrokeAndFill.js) | remove useless `stroke` and `fill` attributes | `enabled` | | ||
| | [removeUnusedNS](https://github.com/svg/svgo/blob/master/plugins/removeUnusedNS.js) | remove unused namespaces declaration | `enabled` | | ||
| | [prefixIds](https://github.com/svg/svgo/blob/master/plugins/prefixIds.js) | prefix IDs and classes with the SVG filename or an arbitrary string | `disabled` | | ||
| | [cleanupIDs](https://github.com/svg/svgo/blob/master/plugins/cleanupIDs.js) | remove unused and minify used IDs | `enabled` | | ||
| | [cleanupNumericValues](https://github.com/svg/svgo/blob/master/plugins/cleanupNumericValues.js) | round numeric values to the fixed precision, remove default `px` units | `enabled` | | ||
| | [cleanupListOfValues](https://github.com/svg/svgo/blob/master/plugins/cleanupListOfValues.js) | round numeric values in attributes that take a list of numbers (like `viewBox` or `enable-background`) | `disabled` | | ||
| | [moveElemsAttrsToGroup](https://github.com/svg/svgo/blob/master/plugins/moveElemsAttrsToGroup.js) | move elements' attributes to their enclosing group | `enabled` | | ||
| | [moveGroupAttrsToElems](https://github.com/svg/svgo/blob/master/plugins/moveGroupAttrsToElems.js) | move some group attributes to the contained elements | `enabled` | | ||
| | [collapseGroups](https://github.com/svg/svgo/blob/master/plugins/collapseGroups.js) | collapse useless groups | `enabled` | | ||
| | [removeRasterImages](https://github.com/svg/svgo/blob/master/plugins/removeRasterImages.js) | remove raster images | `disabled` | | ||
| | [mergePaths](https://github.com/svg/svgo/blob/master/plugins/mergePaths.js) | merge multiple Paths into one | `enabled` | | ||
| | [convertShapeToPath](https://github.com/svg/svgo/blob/master/plugins/convertShapeToPath.js) | convert some basic shapes to `<path>` | `enabled` | | ||
| | [convertEllipseToCircle](https://github.com/svg/svgo/blob/master/plugins/convertEllipseToCircle.js) | convert non-eccentric `<ellipse>` to `<circle>` | `enabled` | | ||
| | [sortAttrs](https://github.com/svg/svgo/blob/master/plugins/sortAttrs.js) | sort element attributes for epic readability | `disabled` | | ||
| | [sortDefsChildren](https://github.com/svg/svgo/blob/master/plugins/sortDefsChildren.js) | sort children of `<defs>` in order to improve compression | `enabled` | | ||
| | [removeDimensions](https://github.com/svg/svgo/blob/master/plugins/removeDimensions.js) | remove `width`/`height` and add `viewBox` if it's missing (opposite to removeViewBox, disable it first) | `disabled` | | ||
| | [removeAttrs](https://github.com/svg/svgo/blob/master/plugins/removeAttrs.js) | remove attributes by pattern | `disabled` | | ||
| | [removeAttributesBySelector](https://github.com/svg/svgo/blob/master/plugins/removeAttributesBySelector.js) | removes attributes of elements that match a CSS selector | `disabled` | | ||
| | [removeElementsByAttr](https://github.com/svg/svgo/blob/master/plugins/removeElementsByAttr.js) | remove arbitrary elements by `ID` or `className` | `disabled` | | ||
| | [addClassesToSVGElement](https://github.com/svg/svgo/blob/master/plugins/addClassesToSVGElement.js) | add classnames to an outer `<svg>` element | `disabled` | | ||
| | [addAttributesToSVGElement](https://github.com/svg/svgo/blob/master/plugins/addAttributesToSVGElement.js) | adds attributes to an outer `<svg>` element | `disabled` | | ||
| | [removeOffCanvasPaths](https://github.com/svg/svgo/blob/master/plugins/removeOffCanvasPaths.js) | removes elements that are drawn outside of the viewbox | `disabled` | | ||
| | [removeStyleElement](https://github.com/svg/svgo/blob/master/plugins/removeStyleElement.js) | remove `<style>` elements | `disabled` | | ||
| | [removeScriptElement](https://github.com/svg/svgo/blob/master/plugins/removeScriptElement.js) | remove `<script>` elements | `disabled` | | ||
| | [reusePaths](https://github.com/svg/svgo/blob/master/plugins/reusePaths.js) | Find duplicated <path> elements and replace them with <use> links | `disabled` | | ||
| ## Other Ways to Use SVGO | ||
| * as a web app – [SVGOMG](https://jakearchibald.github.io/svgomg/) | ||
| * as a GitHub Action – [SVGO Action](https://github.com/marketplace/actions/svgo-action) | ||
| * as a Node.js module – [examples](https://github.com/svg/svgo/tree/master/examples) | ||
| * as a Grunt task – [grunt-svgmin](https://github.com/sindresorhus/grunt-svgmin) | ||
| * as a Gulp task – [gulp-svgmin](https://github.com/ben-eb/gulp-svgmin) | ||
| * as a Mimosa module – [mimosa-minify-svg](https://github.com/dbashford/mimosa-minify-svg) | ||
| * as an OSX Folder Action – [svgo-osx-folder-action](https://github.com/svg/svgo-osx-folder-action) | ||
| * as a webpack loader – [image-webpack-loader](https://github.com/tcoopman/image-webpack-loader) | ||
| * as a Telegram Bot – [svgo_bot](https://github.com/maksugr/svgo_bot) | ||
| * as a PostCSS plugin – [postcss-svgo](https://github.com/ben-eb/postcss-svgo) | ||
| * as an Inkscape plugin – [inkscape-svgo](https://github.com/konsumer/inkscape-svgo) | ||
| * as a Sketch plugin - [svgo-compressor](https://github.com/BohemianCoding/svgo-compressor) | ||
| * as macOS app - [Image Shrinker](https://image-shrinker.com) | ||
| * as a Rollup plugin - [rollup-plugin-svgo](https://github.com/porsager/rollup-plugin-svgo) | ||
| * as a VS Code plugin - [vscode-svgo](https://github.com/1000ch/vscode-svgo) | ||
| * as a Atom plugin - [atom-svgo](https://github.com/1000ch/atom-svgo) | ||
| * as a Sublime plugin - [Sublime-svgo](https://github.com/1000ch/Sublime-svgo) | ||
| * as a Figma plugin - [Advanced SVG Export](https://www.figma.com/c/plugin/782713260363070260/Advanced-SVG-Export) | ||
| - as a web app – [SVGOMG](https://jakearchibald.github.io/svgomg/) | ||
| - as a GitHub Action – [SVGO Action](https://github.com/marketplace/actions/svgo-action) | ||
| - as a Grunt task – [grunt-svgmin](https://github.com/sindresorhus/grunt-svgmin) | ||
| - as a Gulp task – [gulp-svgmin](https://github.com/ben-eb/gulp-svgmin) | ||
| - as a Mimosa module – [mimosa-minify-svg](https://github.com/dbashford/mimosa-minify-svg) | ||
| - as an OSX Folder Action – [svgo-osx-folder-action](https://github.com/svg/svgo-osx-folder-action) | ||
| - as a webpack loader – [image-webpack-loader](https://github.com/tcoopman/image-webpack-loader) | ||
| - as a Telegram Bot – [svgo_bot](https://github.com/maksugr/svgo_bot) | ||
| - as a PostCSS plugin – [postcss-svgo](https://github.com/ben-eb/postcss-svgo) | ||
| - as an Inkscape plugin – [inkscape-svgo](https://github.com/konsumer/inkscape-svgo) | ||
| - as a Sketch plugin - [svgo-compressor](https://github.com/BohemianCoding/svgo-compressor) | ||
| - as a macOS app - [Image Shrinker](https://image-shrinker.com) | ||
| - as a Rollup plugin - [rollup-plugin-svgo](https://github.com/porsager/rollup-plugin-svgo) | ||
| - as a VS Code plugin - [vscode-svgo](https://github.com/1000ch/vscode-svgo) | ||
| - as a Atom plugin - [atom-svgo](https://github.com/1000ch/atom-svgo) | ||
| - as a Sublime plugin - [Sublime-svgo](https://github.com/1000ch/Sublime-svgo) | ||
| - as a Figma plugin - [Advanced SVG Export](https://www.figma.com/c/plugin/782713260363070260/Advanced-SVG-Export) | ||
| - as a Linux app - [Oh My SVG](https://github.com/sonnyp/OhMySVG) | ||
| - as a Browser extension - [SVG Gobbler](https://github.com/rossmoody/svg-gobbler) | ||
| - as an API - [Vector Express](https://github.com/smidyo/vectorexpress-api#convertor-svgo) | ||
@@ -294,4 +281,4 @@ ## Donators | ||
| | [<img src="https://sheetjs.com/sketch128.png" width="80">](https://sheetjs.com/) | [<img src="https://raw.githubusercontent.com/fontello/fontello/master/fontello-image.svg" width="80">](https://fontello.com/) | | ||
| |:-:|:-:| | ||
| | [SheetJS LLC](https://sheetjs.com/) | [Fontello](https://fontello.com/) | | ||
| | :------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------: | | ||
| | [SheetJS LLC](https://sheetjs.com/) | [Fontello](https://fontello.com/) | | ||
@@ -298,0 +285,0 @@ ## License and Copyright |
-183
| 'use strict'; | ||
| const { expect } = require('chai'); | ||
| const { parsePathData, stringifyPathData } = require('./path.js'); | ||
| describe('parse path data', () => { | ||
| it('should allow spaces between commands', () => { | ||
| expect(parsePathData('M0 10 L \n\r\t20 30')).to.deep.equal([ | ||
| { command: 'M', args: [0, 10] }, | ||
| { command: 'L', args: [20, 30] }, | ||
| ]); | ||
| }); | ||
| it('should allow spaces and commas between arguments', () => { | ||
| expect(parsePathData('M0 , 10 L 20 \n\r\t30,40,50')).to.deep.equal([ | ||
| { command: 'M', args: [0, 10] }, | ||
| { command: 'L', args: [20, 30] }, | ||
| { command: 'L', args: [40, 50] }, | ||
| ]); | ||
| }); | ||
| it('should forbid commas before commands', () => { | ||
| expect(parsePathData(', M0 10')).to.deep.equal([]); | ||
| }); | ||
| it('should forbid commas between commands', () => { | ||
| expect(parsePathData('M0,10 , L 20,30')).to.deep.equal([ | ||
| { command: 'M', args: [0, 10] }, | ||
| ]); | ||
| }); | ||
| it('should forbid commas between command name and argument', () => { | ||
| expect(parsePathData('M0,10 L,20,30')).to.deep.equal([ | ||
| { command: 'M', args: [0, 10] }, | ||
| ]); | ||
| }); | ||
| it('should forbid multipe commas in a row', () => { | ||
| expect(parsePathData('M0 , , 10')).to.deep.equal([]); | ||
| }); | ||
| it('should stop when unknown char appears', () => { | ||
| expect(parsePathData('M0 10 , L 20 #40')).to.deep.equal([ | ||
| { command: 'M', args: [0, 10] }, | ||
| ]); | ||
| }); | ||
| it('should stop when not enough arguments', () => { | ||
| expect(parsePathData('M0 10 L 20 L 30 40')).to.deep.equal([ | ||
| { command: 'M', args: [0, 10] }, | ||
| ]); | ||
| }); | ||
| it('should stop if moveto not the first command', () => { | ||
| expect(parsePathData('L 10 20')).to.deep.equal([]); | ||
| expect(parsePathData('10 20')).to.deep.equal([]); | ||
| }); | ||
| it('should stop on invalid scientific notation', () => { | ||
| expect(parsePathData('M 0 5e++1 L 0 0')).to.deep.equal([ | ||
| { command: 'M', args: [0, 5] }, | ||
| ]); | ||
| }); | ||
| it('should stop on invalid numbers', () => { | ||
| expect(parsePathData('M ...')).to.deep.equal([]); | ||
| }); | ||
| it('should handle arcs', () => { | ||
| expect( | ||
| parsePathData( | ||
| ` | ||
| M600,350 | ||
| l 50,-25 | ||
| a25,25 -30 0,1 50,-25 | ||
| 25,50 -30 0,1 50,-25 | ||
| 25,75 -30 01.2,-25 | ||
| a25,100 -30 0150,-25 | ||
| l 50,-25 | ||
| ` | ||
| ) | ||
| ).to.deep.equal([ | ||
| { command: 'M', args: [600, 350] }, | ||
| { command: 'l', args: [50, -25] }, | ||
| { command: 'a', args: [25, 25, -30, 0, 1, 50, -25] }, | ||
| { command: 'a', args: [25, 50, -30, 0, 1, 50, -25] }, | ||
| { command: 'a', args: [25, 75, -30, 0, 1, 0.2, -25] }, | ||
| { command: 'a', args: [25, 100, -30, 0, 1, 50, -25] }, | ||
| { command: 'l', args: [50, -25] }, | ||
| ]); | ||
| }); | ||
| }); | ||
| describe('stringify path data', () => { | ||
| it('should combine sequence of the same commands', () => { | ||
| expect( | ||
| stringifyPathData({ | ||
| pathData: [ | ||
| { command: 'M', args: [0, 0] }, | ||
| { command: 'h', args: [10] }, | ||
| { command: 'h', args: [20] }, | ||
| { command: 'h', args: [30] }, | ||
| { command: 'H', args: [40] }, | ||
| { command: 'H', args: [50] }, | ||
| ], | ||
| }) | ||
| ).to.equal('M0 0h10 20 30H40 50'); | ||
| }); | ||
| it('should not combine sequence of moveto', () => { | ||
| expect( | ||
| stringifyPathData({ | ||
| pathData: [ | ||
| { command: 'M', args: [0, 0] }, | ||
| { command: 'M', args: [10, 10] }, | ||
| { command: 'm', args: [20, 30] }, | ||
| { command: 'm', args: [40, 50] }, | ||
| ], | ||
| }) | ||
| ).to.equal('M0 0M10 10m20 30m40 50'); | ||
| }); | ||
| it('should combine moveto and sequence of lineto', () => { | ||
| expect( | ||
| stringifyPathData({ | ||
| pathData: [ | ||
| { command: 'M', args: [0, 0] }, | ||
| { command: 'l', args: [10, 10] }, | ||
| { command: 'M', args: [0, 0] }, | ||
| { command: 'l', args: [10, 10] }, | ||
| { command: 'M', args: [0, 0] }, | ||
| { command: 'L', args: [10, 10] }, | ||
| ], | ||
| }) | ||
| ).to.equal('m0 0 10 10M0 0l10 10M0 0 10 10'); | ||
| expect( | ||
| stringifyPathData({ | ||
| pathData: [ | ||
| { command: 'm', args: [0, 0] }, | ||
| { command: 'L', args: [10, 10] }, | ||
| ], | ||
| }) | ||
| ).to.equal('M0 0 10 10'); | ||
| }); | ||
| it('should avoid space before first, negative and decimals', () => { | ||
| expect( | ||
| stringifyPathData({ | ||
| pathData: [ | ||
| { command: 'M', args: [0, -1.2] }, | ||
| { command: 'L', args: [0.3, 4] }, | ||
| { command: 'L', args: [5, -0.6] }, | ||
| { command: 'L', args: [7, 0.8] }, | ||
| ], | ||
| }) | ||
| ).to.equal('M0-1.2.3 4 5-.6 7 .8'); | ||
| }); | ||
| it('should configure precision', () => { | ||
| const pathData = [ | ||
| { command: 'M', args: [0, -1.9876] }, | ||
| { command: 'L', args: [0.3, 3.14159265] }, | ||
| { command: 'L', args: [-0.3, -3.14159265] }, | ||
| { command: 'L', args: [100, 200] }, | ||
| ]; | ||
| expect( | ||
| stringifyPathData({ | ||
| pathData, | ||
| precision: 3, | ||
| }) | ||
| ).to.equal('M0-1.988.3 3.142-.3-3.142 100 200'); | ||
| expect( | ||
| stringifyPathData({ | ||
| pathData, | ||
| precision: 0, | ||
| }) | ||
| ).to.equal('M0-2 0 3 0-3 100 200'); | ||
| }); | ||
| it('allows to avoid spaces after arc flags', () => { | ||
| const pathData = [ | ||
| { command: 'M', args: [0, 0] }, | ||
| { command: 'A', args: [50, 50, 10, 1, 0, 0.2, 20] }, | ||
| { command: 'a', args: [50, 50, 10, 1, 0, 0.2, 20] }, | ||
| ]; | ||
| expect( | ||
| stringifyPathData({ | ||
| pathData, | ||
| disableSpaceAfterFlags: false, | ||
| }) | ||
| ).to.equal('M0 0A50 50 10 1 0 .2 20a50 50 10 1 0 .2 20'); | ||
| expect( | ||
| stringifyPathData({ | ||
| pathData, | ||
| disableSpaceAfterFlags: true, | ||
| }) | ||
| ).to.equal('M0 0A50 50 10 10.2 20a50 50 10 10.2 20'); | ||
| }); | ||
| }); |
| 'use strict'; | ||
| const { expect } = require('chai'); | ||
| const { computeStyle } = require('./style.js'); | ||
| const { querySelector } = require('./xast.js'); | ||
| const svg2js = require('./svgo/svg2js.js'); | ||
| describe('computeStyle', () => { | ||
| it('collects styles', () => { | ||
| const root = svg2js(` | ||
| <svg> | ||
| <rect id="class" class="a" /> | ||
| <rect id="two-classes" class="b a" /> | ||
| <rect id="attribute" fill="purple" /> | ||
| <rect id="inline-style" style="fill: grey;" /> | ||
| <g fill="yellow"> | ||
| <rect id="inheritance" /> | ||
| <g style="fill: blue;"> | ||
| <g> | ||
| <rect id="nested-inheritance" /> | ||
| </g> | ||
| </g> | ||
| </g> | ||
| <style> | ||
| .a { fill: red; } | ||
| </style> | ||
| <style> | ||
| <![CDATA[ | ||
| .b { fill: green; stroke: black; } | ||
| ]]> | ||
| </style> | ||
| </svg> | ||
| `); | ||
| expect(computeStyle(querySelector(root, '#class'))).to.deep.equal({ | ||
| fill: { type: 'static', inherited: false, value: 'red' }, | ||
| }); | ||
| expect(computeStyle(querySelector(root, '#two-classes'))).to.deep.equal({ | ||
| fill: { type: 'static', inherited: false, value: 'green' }, | ||
| stroke: { type: 'static', inherited: false, value: 'black' }, | ||
| }); | ||
| expect(computeStyle(querySelector(root, '#attribute'))).to.deep.equal({ | ||
| fill: { type: 'static', inherited: false, value: 'purple' }, | ||
| }); | ||
| expect(computeStyle(querySelector(root, '#inline-style'))).to.deep.equal({ | ||
| fill: { type: 'static', inherited: false, value: 'grey' }, | ||
| }); | ||
| expect(computeStyle(querySelector(root, '#inheritance'))).to.deep.equal({ | ||
| fill: { type: 'static', inherited: true, value: 'yellow' }, | ||
| }); | ||
| expect( | ||
| computeStyle(querySelector(root, '#nested-inheritance')) | ||
| ).to.deep.equal({ | ||
| fill: { type: 'static', inherited: true, value: 'blue' }, | ||
| }); | ||
| }); | ||
| it('prioritizes different kinds of styles', () => { | ||
| const root = svg2js(` | ||
| <svg> | ||
| <style> | ||
| g > .a { fill: red; } | ||
| .a { fill: green; } | ||
| .b { fill: blue; } | ||
| </style> | ||
| <g fill="yellow"> | ||
| <rect id="complex-selector" class="a" /> | ||
| <rect id="attribute-over-inheritance" fill="orange" /> | ||
| <rect id="style-rule-over-attribute" class="b" fill="grey" /> | ||
| <rect id="inline-style-over-style-rule" style="fill: purple;" class="b" /> | ||
| </g> | ||
| </svg> | ||
| `); | ||
| expect( | ||
| computeStyle(querySelector(root, '#complex-selector')) | ||
| ).to.deep.equal({ | ||
| fill: { type: 'static', inherited: false, value: 'red' }, | ||
| }); | ||
| expect( | ||
| computeStyle(querySelector(root, '#attribute-over-inheritance')) | ||
| ).to.deep.equal({ | ||
| fill: { type: 'static', inherited: false, value: 'orange' }, | ||
| }); | ||
| expect( | ||
| computeStyle(querySelector(root, '#style-rule-over-attribute')) | ||
| ).to.deep.equal({ | ||
| fill: { type: 'static', inherited: false, value: 'blue' }, | ||
| }); | ||
| expect( | ||
| computeStyle(querySelector(root, '#inline-style-over-style-rule')) | ||
| ).to.deep.equal({ | ||
| fill: { type: 'static', inherited: false, value: 'purple' }, | ||
| }); | ||
| }); | ||
| it('prioritizes important styles', () => { | ||
| const root = svg2js(` | ||
| <svg> | ||
| <style> | ||
| g > .a { fill: red; } | ||
| .b { fill: green !important; } | ||
| </style> | ||
| <rect id="complex-selector" class="a b" /> | ||
| <rect id="style-rule-over-inline-style" style="fill: orange;" class="b" /> | ||
| <rect id="inline-style-over-style-rule" style="fill: purple !important;" class="b" /> | ||
| </svg> | ||
| `); | ||
| expect( | ||
| computeStyle(querySelector(root, '#complex-selector')) | ||
| ).to.deep.equal({ | ||
| fill: { type: 'static', inherited: false, value: 'green' }, | ||
| }); | ||
| expect( | ||
| computeStyle(querySelector(root, '#style-rule-over-inline-style')) | ||
| ).to.deep.equal({ | ||
| fill: { type: 'static', inherited: false, value: 'green' }, | ||
| }); | ||
| expect( | ||
| computeStyle(querySelector(root, '#inline-style-over-style-rule')) | ||
| ).to.deep.equal({ | ||
| fill: { type: 'static', inherited: false, value: 'purple' }, | ||
| }); | ||
| }); | ||
| it('treats at-rules and pseudo-classes as dynamic styles', () => { | ||
| const root = svg2js(` | ||
| <svg> | ||
| <style> | ||
| @media screen { | ||
| .a { fill: red; } | ||
| } | ||
| .b:hover { fill: green; } | ||
| .c { fill: blue; } | ||
| .d { fill: purple; } | ||
| </style> | ||
| <rect id="media-query" class="a d" style="fill: orange;" /> | ||
| <rect id="hover" class="b" style="fill: yellow;" /> | ||
| <g class="a"> | ||
| <rect id="inherited" /> | ||
| <rect id="inherited-overriden" class="c" /> | ||
| </g> | ||
| <rect id="static" class="c" style="fill: black" /> | ||
| </svg> | ||
| `); | ||
| expect(computeStyle(querySelector(root, '#media-query'))).to.deep.equal({ | ||
| fill: { type: 'dynamic', inherited: false }, | ||
| }); | ||
| expect(computeStyle(querySelector(root, '#hover'))).to.deep.equal({ | ||
| fill: { type: 'dynamic', inherited: false }, | ||
| }); | ||
| expect(computeStyle(querySelector(root, '#inherited'))).to.deep.equal({ | ||
| fill: { type: 'dynamic', inherited: true }, | ||
| }); | ||
| expect( | ||
| computeStyle(querySelector(root, '#inherited-overriden')) | ||
| ).to.deep.equal({ | ||
| fill: { type: 'static', inherited: false, value: 'blue' }, | ||
| }); | ||
| expect(computeStyle(querySelector(root, '#static'))).to.deep.equal({ | ||
| fill: { type: 'static', inherited: false, value: 'black' }, | ||
| }); | ||
| }); | ||
| it('considers <style> media attribute', () => { | ||
| const root = svg2js(` | ||
| <svg> | ||
| <style media="print"> | ||
| @media screen { | ||
| .a { fill: red; } | ||
| } | ||
| .b { fill: green; } | ||
| </style> | ||
| <style media="all"> | ||
| .c { fill: blue; } | ||
| </style> | ||
| <rect id="media-query" class="a" /> | ||
| <rect id="kinda-static" class="b" /> | ||
| <rect id="static" class="c" /> | ||
| </svg> | ||
| `); | ||
| expect(computeStyle(querySelector(root, '#media-query'))).to.deep.equal({ | ||
| fill: { type: 'dynamic', inherited: false }, | ||
| }); | ||
| expect(computeStyle(querySelector(root, '#kinda-static'))).to.deep.equal({ | ||
| fill: { type: 'dynamic', inherited: false }, | ||
| }); | ||
| expect(computeStyle(querySelector(root, '#static'))).to.deep.equal({ | ||
| fill: { type: 'static', inherited: false, value: 'blue' }, | ||
| }); | ||
| }); | ||
| it('ignores <style> with invalid type', () => { | ||
| const root = svg2js(` | ||
| <svg> | ||
| <style type="text/css"> | ||
| .a { fill: red; } | ||
| </style> | ||
| <style type=""> | ||
| .b { fill: green; } | ||
| </style> | ||
| <style type="text/invalid"> | ||
| .c { fill: blue; } | ||
| </style> | ||
| <rect id="valid-type" class="a" /> | ||
| <rect id="empty-type" class="b" /> | ||
| <rect id="invalid-type" class="c" /> | ||
| </svg> | ||
| `); | ||
| expect(computeStyle(querySelector(root, '#valid-type'))).to.deep.equal({ | ||
| fill: { type: 'static', inherited: false, value: 'red' }, | ||
| }); | ||
| expect(computeStyle(querySelector(root, '#empty-type'))).to.deep.equal({ | ||
| fill: { type: 'static', inherited: false, value: 'green' }, | ||
| }); | ||
| expect(computeStyle(querySelector(root, '#invalid-type'))).to.deep.equal( | ||
| {} | ||
| ); | ||
| }); | ||
| it('ignores keyframes atrule', () => { | ||
| const root = svg2js(` | ||
| <svg> | ||
| <style> | ||
| .a { | ||
| animation: loading 4s linear infinite; | ||
| } | ||
| @keyframes loading { | ||
| 0% { | ||
| stroke-dashoffset: 440; | ||
| } | ||
| 50% { | ||
| stroke-dashoffset: 0; | ||
| } | ||
| 50.1% { | ||
| stroke-dashoffset: 880; | ||
| } | ||
| } | ||
| </style> | ||
| <rect id="element" class="a" /> | ||
| </svg> | ||
| `); | ||
| expect(computeStyle(querySelector(root, '#element'))).to.deep.equal({ | ||
| animation: { | ||
| type: 'static', | ||
| inherited: false, | ||
| value: 'loading 4s linear infinite', | ||
| }, | ||
| }); | ||
| }); | ||
| }); |
-108
| 'use strict'; | ||
| const { expect } = require('chai'); | ||
| const { visit, detachNodeFromParent } = require('./xast.js'); | ||
| const getAst = () => { | ||
| const ast = { | ||
| type: 'root', | ||
| children: [ | ||
| { | ||
| type: 'element', | ||
| name: 'g', | ||
| attributes: {}, | ||
| children: [ | ||
| { | ||
| type: 'element', | ||
| name: 'rect', | ||
| attributes: {}, | ||
| children: [], | ||
| }, | ||
| { | ||
| type: 'element', | ||
| name: 'circle', | ||
| attributes: {}, | ||
| children: [], | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| type: 'element', | ||
| name: 'ellipse', | ||
| attributes: {}, | ||
| children: [], | ||
| }, | ||
| ], | ||
| }; | ||
| ast.children[0].parentNode = ast; | ||
| ast.children[0].children[0].parentNode = ast.children[0]; | ||
| ast.children[0].children[1].parentNode = ast.children[0]; | ||
| ast.children[1].parentNode = ast; | ||
| return ast; | ||
| }; | ||
| describe('xast', () => { | ||
| it('enter into nodes', () => { | ||
| const root = getAst(); | ||
| const entered = []; | ||
| visit(root, { | ||
| root: { | ||
| enter: (node) => { | ||
| entered.push(node.type); | ||
| }, | ||
| }, | ||
| element: { | ||
| enter: (node) => { | ||
| entered.push(`${node.type}:${node.name}`); | ||
| }, | ||
| }, | ||
| }); | ||
| expect(entered).to.deep.equal([ | ||
| 'root', | ||
| 'element:g', | ||
| 'element:rect', | ||
| 'element:circle', | ||
| 'element:ellipse', | ||
| ]); | ||
| }); | ||
| it('exit from nodes', () => { | ||
| const root = getAst(); | ||
| const exited = []; | ||
| visit(root, { | ||
| root: { | ||
| exit: (node) => { | ||
| exited.push(node.type); | ||
| }, | ||
| }, | ||
| element: { | ||
| exit: (node) => { | ||
| exited.push(`${node.type}:${node.name}`); | ||
| }, | ||
| }, | ||
| }); | ||
| expect(exited).to.deep.equal([ | ||
| 'element:rect', | ||
| 'element:circle', | ||
| 'element:g', | ||
| 'element:ellipse', | ||
| 'root', | ||
| ]); | ||
| }); | ||
| it('skip entering children if node is detached', () => { | ||
| const root = getAst(); | ||
| const entered = []; | ||
| visit(root, { | ||
| element: { | ||
| enter: (node) => { | ||
| entered.push(node.name); | ||
| if (node.name === 'g') { | ||
| detachNodeFromParent(node); | ||
| } | ||
| }, | ||
| }, | ||
| }); | ||
| expect(entered).to.deep.equal(['g', 'ellipse']); | ||
| }); | ||
| }); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
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
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
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
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
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
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
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
921718
1.33%17
-10.53%77
-2.53%13780
-2.08%281
-4.42%+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed