mirror of
https://github.com/svg/svgo.git
synced 2026-01-27 07:02:06 +03:00
Detects if a redundant linearGradient or radialGradient is used with only a single stop, which effectively means a solid color. If this is found, just remove the gradient and replace references to it with the color of the first and only stop defined.
169 lines
4.6 KiB
JavaScript
169 lines
4.6 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* @typedef {import('../lib/types').XastElement} XastElement
|
|
* @typedef {import('../lib/types').XastParent} XastParent
|
|
*/
|
|
|
|
const { attrsGroupsDefaults, colorsProps } = require('./_collections');
|
|
const {
|
|
detachNodeFromParent,
|
|
querySelectorAll,
|
|
querySelector,
|
|
} = require('../lib/xast');
|
|
const { computeStyle, collectStylesheet } = require('../lib/style');
|
|
|
|
exports.name = 'convertOneStopGradients';
|
|
exports.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('./plugins-types').Plugin<'convertOneStopGradients'>}
|
|
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient
|
|
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/radialGradient
|
|
*/
|
|
exports.fn = (root) => {
|
|
const stylesheet = collectStylesheet(root);
|
|
|
|
/**
|
|
* Parent defs that had gradients elements removed from them.
|
|
*
|
|
* @type {Set<XastElement>}
|
|
*/
|
|
const effectedDefs = new Set();
|
|
|
|
/**
|
|
* @type {Map<XastElement, XastParent>}
|
|
*/
|
|
const allDefs = new Map();
|
|
|
|
/**
|
|
* @type {Map<XastElement, 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'];
|
|
let 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);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
},
|
|
};
|
|
};
|