UNPKG

svgo

Version:

SVGO is a Node.js library and command-line application for optimizing vector images.

165 lines (142 loc) 4.89 kB
import * as csstree from 'css-tree'; import { visit } from '../lib/util/visit.js'; export const name = 'cleanupEnableBackground'; export const description = 'remove or cleanup enable-background attribute when possible'; const regEnableBackground = /^new\s0\s0\s([-+]?\d*\.?\d+([eE][-+]?\d+)?)\s([-+]?\d*\.?\d+([eE][-+]?\d+)?)$/; /** * Remove or cleanup enable-background attr which coincides with a width/height * box. * * @see https://www.w3.org/TR/SVG11/filters.html#EnableBackgroundProperty * @example * <svg width="100" height="50" enable-background="new 0 0 100 50"> * ⬇ * <svg width="100" height="50"> * @author Kir Belevich * @type {import('../lib/types.js').Plugin} */ export const fn = (root) => { let hasFilter = false; visit(root, { element: { enter: (node) => { if (node.name === 'filter') { hasFilter = true; } }, }, }); return { element: { enter: (node) => { /** @type {?csstree.CssNode} */ let newStyle = null; /** @type {?csstree.ListItem<csstree.CssNode>} */ let enableBackgroundDeclaration = null; if (node.attributes.style != null) { newStyle = csstree.parse(node.attributes.style, { context: 'declarationList', }); if (newStyle.type === 'DeclarationList') { /** @type {csstree.ListItem<csstree.CssNode>[]} */ const enableBackgroundDeclarations = []; csstree.walk(newStyle, (node, nodeItem) => { if ( node.type === 'Declaration' && node.property === 'enable-background' ) { enableBackgroundDeclarations.push(nodeItem); enableBackgroundDeclaration = nodeItem; } }); for (let i = 0; i < enableBackgroundDeclarations.length - 1; i++) { newStyle.children.remove(enableBackgroundDeclarations[i]); } } } if (!hasFilter) { delete node.attributes['enable-background']; if (newStyle?.type === 'DeclarationList') { if (enableBackgroundDeclaration) { newStyle.children.remove(enableBackgroundDeclaration); } if (newStyle.children.isEmpty) { delete node.attributes.style; } else { node.attributes.style = csstree.generate(newStyle); } } return; } const hasDimensions = node.attributes.width != null && node.attributes.height != null; if ( (node.name === 'svg' || node.name === 'mask' || node.name === 'pattern') && hasDimensions ) { const attrValue = node.attributes['enable-background']; const attrCleaned = cleanupValue( attrValue, node.name, node.attributes.width, node.attributes.height, ); if (attrCleaned) { node.attributes['enable-background'] = attrCleaned; } else { delete node.attributes['enable-background']; } if ( newStyle?.type === 'DeclarationList' && enableBackgroundDeclaration ) { const styleValue = csstree.generate( // @ts-expect-error enableBackgroundDeclaration.data.value, ); const styleCleaned = cleanupValue( styleValue, node.name, node.attributes.width, node.attributes.height, ); if (styleCleaned) { // @ts-expect-error enableBackgroundDeclaration.data.value = { type: 'Raw', value: styleCleaned, }; } else { newStyle.children.remove(enableBackgroundDeclaration); } } } if (newStyle?.type === 'DeclarationList') { if (newStyle.children.isEmpty) { delete node.attributes.style; } else { node.attributes.style = csstree.generate(newStyle); } } }, }, }; }; /** * @param {string} value Value of an enable-background attribute or style declaration. * @param {string} nodeName Name of the node the value was assigned to. * @param {string} width Width of the node the value was assigned to. * @param {string} height Height of the node the value was assigned to. * @returns {string | undefined} Cleaned up value, or undefined if it's redundant. */ const cleanupValue = (value, nodeName, width, height) => { const match = regEnableBackground.exec(value); if (match != null && width === match[1] && height === match[3]) { return nodeName === 'svg' ? undefined : 'new'; } return value; };