diff options
Diffstat (limited to 'node_modules/svgo/plugins/removeHiddenElems.js')
| -rw-r--r-- | node_modules/svgo/plugins/removeHiddenElems.js | 172 |
1 files changed, 151 insertions, 21 deletions
diff --git a/node_modules/svgo/plugins/removeHiddenElems.js b/node_modules/svgo/plugins/removeHiddenElems.js index 5c1a0135..6ed2750b 100644 --- a/node_modules/svgo/plugins/removeHiddenElems.js +++ b/node_modules/svgo/plugins/removeHiddenElems.js @@ -1,5 +1,12 @@ 'use strict'; +/** + * @typedef {import('../lib/types').XastChild} XastChild + * @typedef {import('../lib/types').XastElement} XastElement + * @typedef {import('../lib/types').XastParent} XastParent + */ + +const { elemsGroups } = require('./_collections.js'); const { visit, visitSkip, @@ -8,6 +15,9 @@ const { } = require('../lib/xast.js'); const { collectStylesheet, computeStyle } = require('../lib/style.js'); const { parsePathData } = require('../lib/path.js'); +const { hasScripts, findReferences } = require('../lib/svgo/tools.js'); + +const nonRendering = elemsGroups.nonRendering; exports.name = 'removeHiddenElems'; exports.description = @@ -50,11 +60,67 @@ exports.fn = (root, params) => { } = params; const stylesheet = collectStylesheet(root); + /** + * Skip non-rendered nodes initially, and only detach if they have no ID, or + * their ID is not referenced by another node. + * + * @type {Map<XastElement, XastParent>} + */ + const nonRenderedNodes = new Map(); + + /** + * IDs for removed hidden definitions. + * + * @type {Set<string>} + */ + const removedDefIds = new Set(); + + /** + * @type {Map<XastElement, XastParent>} + */ + const allDefs = new Map(); + + /** @type {Set<string>} */ + const allReferences = new Set(); + + /** + * @type {Map<string, Array<{ node: XastElement, parentNode: XastParent }>>} + */ + const referencesById = new Map(); + + /** + * If styles are present, we can't be sure if a definition is unused or not + */ + let deoptimized = false; + + /** + * @param {XastChild} node + * @param {XastParent} parentNode + */ + function removeElement(node, parentNode) { + if ( + node.type === 'element' && + node.attributes.id != null && + parentNode.type === 'element' && + parentNode.name === 'defs' + ) { + removedDefIds.add(node.attributes.id); + } + + detachNodeFromParent(node, parentNode); + } + visit(root, { element: { enter: (node, parentNode) => { - // transparent element inside clipPath still affect clipped elements - if (node.name === 'clipPath') { + // transparent non-rendering elements still apply where referenced + if (nonRendering.includes(node.name)) { + if (node.attributes.id == null) { + detachNodeFromParent(node, parentNode); + return visitSkip; + } + + nonRenderedNodes.set(node, parentNode); return visitSkip; } const computedStyle = computeStyle(stylesheet, node); @@ -67,8 +133,7 @@ exports.fn = (root, params) => { computedStyle.opacity.type === 'static' && computedStyle.opacity.value === '0' ) { - detachNodeFromParent(node, parentNode); - return; + removeElement(node, parentNode); } }, }, @@ -77,6 +142,33 @@ exports.fn = (root, params) => { return { element: { enter: (node, parentNode) => { + if ( + (node.name === 'style' && node.children.length !== 0) || + hasScripts(node) + ) { + deoptimized = true; + return; + } + + if (node.name === 'defs') { + allDefs.set(node, parentNode); + } + + if (node.name === 'use') { + for (const attr of Object.keys(node.attributes)) { + if (attr !== 'href' && !attr.endsWith(':href')) continue; + const value = node.attributes[attr]; + const id = value.slice(1); + + let refs = referencesById.get(id); + if (!refs) { + refs = []; + referencesById.set(id, refs); + } + refs.push({ node, parentNode }); + } + } + // Removes hidden elements // https://www.w3schools.com/cssref/pr_class_visibility.asp const computedStyle = computeStyle(stylesheet, node); @@ -88,7 +180,7 @@ exports.fn = (root, params) => { // keep if any descendant enables visibility querySelector(node, '[visibility=visible]') == null ) { - detachNodeFromParent(node, parentNode); + removeElement(node, parentNode); return; } @@ -105,7 +197,7 @@ exports.fn = (root, params) => { // markers with display: none still rendered node.name !== 'marker' ) { - detachNodeFromParent(node, parentNode); + removeElement(node, parentNode); return; } @@ -121,7 +213,7 @@ exports.fn = (root, params) => { node.children.length === 0 && node.attributes.r === '0' ) { - detachNodeFromParent(node, parentNode); + removeElement(node, parentNode); return; } @@ -137,7 +229,7 @@ exports.fn = (root, params) => { node.children.length === 0 && node.attributes.rx === '0' ) { - detachNodeFromParent(node, parentNode); + removeElement(node, parentNode); return; } @@ -153,7 +245,7 @@ exports.fn = (root, params) => { node.children.length === 0 && node.attributes.ry === '0' ) { - detachNodeFromParent(node, parentNode); + removeElement(node, parentNode); return; } @@ -169,7 +261,7 @@ exports.fn = (root, params) => { node.children.length === 0 && node.attributes.width === '0' ) { - detachNodeFromParent(node, parentNode); + removeElement(node, parentNode); return; } @@ -186,7 +278,7 @@ exports.fn = (root, params) => { node.children.length === 0 && node.attributes.height === '0' ) { - detachNodeFromParent(node, parentNode); + removeElement(node, parentNode); return; } @@ -201,7 +293,7 @@ exports.fn = (root, params) => { node.name === 'pattern' && node.attributes.width === '0' ) { - detachNodeFromParent(node, parentNode); + removeElement(node, parentNode); return; } @@ -216,7 +308,7 @@ exports.fn = (root, params) => { node.name === 'pattern' && node.attributes.height === '0' ) { - detachNodeFromParent(node, parentNode); + removeElement(node, parentNode); return; } @@ -231,7 +323,7 @@ exports.fn = (root, params) => { node.name === 'image' && node.attributes.width === '0' ) { - detachNodeFromParent(node, parentNode); + removeElement(node, parentNode); return; } @@ -246,7 +338,7 @@ exports.fn = (root, params) => { node.name === 'image' && node.attributes.height === '0' ) { - detachNodeFromParent(node, parentNode); + removeElement(node, parentNode); return; } @@ -257,12 +349,12 @@ exports.fn = (root, params) => { // <path d=""/> if (pathEmptyD && node.name === 'path') { if (node.attributes.d == null) { - detachNodeFromParent(node, parentNode); + removeElement(node, parentNode); return; } const pathData = parsePathData(node.attributes.d); if (pathData.length === 0) { - detachNodeFromParent(node, parentNode); + removeElement(node, parentNode); return; } // keep single point paths for markers @@ -271,10 +363,9 @@ exports.fn = (root, params) => { computedStyle['marker-start'] == null && computedStyle['marker-end'] == null ) { - detachNodeFromParent(node, parentNode); + removeElement(node, parentNode); return; } - return; } // Polyline with empty points @@ -287,7 +378,7 @@ exports.fn = (root, params) => { node.name === 'polyline' && node.attributes.points == null ) { - detachNodeFromParent(node, parentNode); + removeElement(node, parentNode); return; } @@ -301,9 +392,48 @@ exports.fn = (root, params) => { node.name === 'polygon' && node.attributes.points == null ) { - detachNodeFromParent(node, parentNode); + removeElement(node, parentNode); return; } + + for (const [name, value] of Object.entries(node.attributes)) { + const ids = findReferences(name, value); + + for (const id of ids) { + allReferences.add(id); + } + } + }, + }, + root: { + exit: () => { + for (const id of removedDefIds) { + const refs = referencesById.get(id); + if (refs) { + for (const { node, parentNode } of refs) { + detachNodeFromParent(node, parentNode); + } + } + } + + if (!deoptimized) { + for (const [ + nonRenderedNode, + nonRenderedParent, + ] of nonRenderedNodes.entries()) { + const id = nonRenderedNode.attributes.id; + + if (!allReferences.has(id)) { + detachNodeFromParent(nonRenderedNode, nonRenderedParent); + } + } + } + + for (const [node, parentNode] of allDefs.entries()) { + if (node.children.length === 0) { + detachNodeFromParent(node, parentNode); + } + } }, }, }; |
