'use strict'; const csstree = require('css-tree'); const { visit } = require('../lib/xast.js'); exports.name = 'cleanupEnableBackground'; exports.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 * * ⬇ * * @author Kir Belevich * @type {import('./plugins-types').Plugin<'cleanupEnableBackground'>} */ exports.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} */ let enableBackgroundDeclaration = null; if (node.attributes.style != null) { newStyle = csstree.parse(node.attributes.style, { context: 'declarationList', }); if (newStyle.type === 'DeclarationList') { /** @type {csstree.ListItem[]} */ 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-ignore enableBackgroundDeclaration.data.value, ); const styleCleaned = cleanupValue( styleValue, node.name, node.attributes.width, node.attributes.height, ); if (styleCleaned) { // @ts-ignore 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 a 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; };