UNPKG

svgo

Version:

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

158 lines (130 loc) 4.63 kB
import { attrsGroupsDefaults, colorsProps } from './_collections.js'; import { detachNodeFromParent, querySelector, querySelectorAll, } from '../lib/xast.js'; import { collectStylesheet, computeStyle } from '../lib/style.js'; export const name = 'convertOneStopGradients'; export const description = 'converts one-stop (single color) gradients to a plain color'; /** * Converts one-stop (single color) gradients to a plain color. * * @author Seth Falco <seth@falco.fun> * @type {import('../lib/types.js').Plugin} * @see https://developer.mozilla.org/docs/Web/SVG/Element/linearGradient * @see https://developer.mozilla.org/docs/Web/SVG/Element/radialGradient */ export const fn = (root) => { const stylesheet = collectStylesheet(root); /** * Parent defs that had gradients elements removed from them. * * @type {Set<import('../lib/types.js').XastElement>} */ const effectedDefs = new Set(); /** @type {Map<import('../lib/types.js').XastElement, import('../lib/types.js').XastParent>} */ const allDefs = new Map(); /** @type {Map<import('../lib/types.js').XastElement, import('../lib/types.js').XastParent>} */ const gradientsToDetach = new Map(); /** Number of references to the xlink:href attribute. */ let xlinkHrefCount = 0; return { element: { enter: (node, parentNode) => { if (node.attributes['xlink:href'] != null) { xlinkHrefCount++; } if (node.name === 'defs') { allDefs.set(node, parentNode); return; } if (node.name !== 'linearGradient' && node.name !== 'radialGradient') { return; } const stops = node.children.filter((child) => { return child.type === 'element' && child.name === 'stop'; }); const href = node.attributes['xlink:href'] || node.attributes['href']; const effectiveNode = stops.length === 0 && href != null && href.startsWith('#') ? querySelector(root, href) : node; if (effectiveNode == null || effectiveNode.type !== 'element') { gradientsToDetach.set(node, parentNode); return; } const effectiveStops = effectiveNode.children.filter((child) => { return child.type === 'element' && child.name === 'stop'; }); if ( effectiveStops.length !== 1 || effectiveStops[0].type !== 'element' ) { return; } if (parentNode.type === 'element' && parentNode.name === 'defs') { effectedDefs.add(parentNode); } gradientsToDetach.set(node, parentNode); let color; const style = computeStyle(stylesheet, effectiveStops[0])['stop-color']; if (style != null && style.type === 'static') { color = style.value; } const selectorVal = `url(#${node.attributes.id})`; const selector = [...colorsProps] .map((attr) => `[${attr}="${selectorVal}"]`) .join(','); const elements = querySelectorAll(root, selector); for (const element of elements) { if (element.type !== 'element') { continue; } for (const attr of colorsProps) { if (element.attributes[attr] !== selectorVal) { continue; } if (color != null) { element.attributes[attr] = color; } else { delete element.attributes[attr]; } } } const styledElements = querySelectorAll( root, `[style*=${selectorVal}]`, ); for (const element of styledElements) { if (element.type !== 'element') { continue; } element.attributes.style = element.attributes.style.replace( selectorVal, color || attrsGroupsDefaults.presentation['stop-color'], ); } }, exit: (node) => { if (node.name === 'svg') { for (const [gradient, parent] of gradientsToDetach.entries()) { if (gradient.attributes['xlink:href'] != null) { xlinkHrefCount--; } detachNodeFromParent(gradient, parent); } if (xlinkHrefCount === 0) { delete node.attributes['xmlns:xlink']; } for (const [defs, parent] of allDefs.entries()) { if (effectedDefs.has(defs) && defs.children.length === 0) { detachNodeFromParent(defs, parent); } } } }, }, }; };