-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: convert one stop gradients plugin (#1790)
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.
- Loading branch information
Showing
8 changed files
with
238 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
'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); | ||
} | ||
} | ||
} | ||
}, | ||
}, | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.