summaryrefslogtreecommitdiff
path: root/node_modules/svgo/plugins/removeHiddenElems.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/svgo/plugins/removeHiddenElems.js')
-rw-r--r--node_modules/svgo/plugins/removeHiddenElems.js172
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);
+ }
+ }
},
},
};