mirror of
https://github.com/svg/svgo.git
synced 2026-01-27 07:02:06 +03:00
The last usage of closestByName utility based on node.parentNode is removed here. One step closer to clean ast in v3.
329 lines
9.2 KiB
JavaScript
329 lines
9.2 KiB
JavaScript
'use strict';
|
|
|
|
const {
|
|
visit,
|
|
visitSkip,
|
|
querySelector,
|
|
detachNodeFromParent,
|
|
} = require('../lib/xast.js');
|
|
const { collectStylesheet, computeStyle } = require('../lib/style.js');
|
|
const { parsePathData } = require('../lib/path.js');
|
|
|
|
exports.name = 'removeHiddenElems';
|
|
exports.type = 'visitor';
|
|
exports.active = true;
|
|
exports.description =
|
|
'removes hidden elements (zero sized, with absent attributes)';
|
|
|
|
/**
|
|
* Remove hidden elements with disabled rendering:
|
|
* - display="none"
|
|
* - opacity="0"
|
|
* - circle with zero radius
|
|
* - ellipse with zero x-axis or y-axis radius
|
|
* - rectangle with zero width or height
|
|
* - pattern with zero width or height
|
|
* - image with zero width or height
|
|
* - path with empty data
|
|
* - polyline with empty points
|
|
* - polygon with empty points
|
|
*
|
|
* @author Kir Belevich
|
|
*
|
|
* @type {import('../lib/types').Plugin<{
|
|
* isHidden: boolean,
|
|
* displayNone: boolean,
|
|
* opacity0: boolean,
|
|
* circleR0: boolean,
|
|
* ellipseRX0: boolean,
|
|
* ellipseRY0: boolean,
|
|
* rectWidth0: boolean,
|
|
* rectHeight0: boolean,
|
|
* patternWidth0: boolean,
|
|
* patternHeight0: boolean,
|
|
* imageWidth0: boolean,
|
|
* imageHeight0: boolean,
|
|
* pathEmptyD: boolean,
|
|
* polylineEmptyPoints: boolean,
|
|
* polygonEmptyPoints: boolean,
|
|
* }>}
|
|
*/
|
|
exports.fn = (root, params) => {
|
|
const {
|
|
isHidden = true,
|
|
displayNone = true,
|
|
opacity0 = true,
|
|
circleR0 = true,
|
|
ellipseRX0 = true,
|
|
ellipseRY0 = true,
|
|
rectWidth0 = true,
|
|
rectHeight0 = true,
|
|
patternWidth0 = true,
|
|
patternHeight0 = true,
|
|
imageWidth0 = true,
|
|
imageHeight0 = true,
|
|
pathEmptyD = true,
|
|
polylineEmptyPoints = true,
|
|
polygonEmptyPoints = true,
|
|
} = params;
|
|
const stylesheet = collectStylesheet(root);
|
|
|
|
visit(root, {
|
|
element: {
|
|
enter: (node, parentNode) => {
|
|
// transparent element inside clipPath still affect clipped elements
|
|
if (node.name === 'clipPath') {
|
|
return visitSkip;
|
|
}
|
|
const computedStyle = computeStyle(stylesheet, node);
|
|
// opacity="0"
|
|
//
|
|
// https://www.w3.org/TR/SVG11/masking.html#ObjectAndGroupOpacityProperties
|
|
if (
|
|
opacity0 &&
|
|
computedStyle.opacity &&
|
|
computedStyle.opacity.type === 'static' &&
|
|
computedStyle.opacity.value === '0'
|
|
) {
|
|
detachNodeFromParent(node, parentNode);
|
|
return;
|
|
}
|
|
},
|
|
},
|
|
});
|
|
|
|
return {
|
|
element: {
|
|
enter: (node, parentNode) => {
|
|
// Removes hidden elements
|
|
// https://www.w3schools.com/cssref/pr_class_visibility.asp
|
|
const computedStyle = computeStyle(stylesheet, node);
|
|
if (
|
|
isHidden &&
|
|
computedStyle.visibility &&
|
|
computedStyle.visibility.type === 'static' &&
|
|
computedStyle.visibility.value === 'hidden' &&
|
|
// keep if any descendant enables visibility
|
|
querySelector(node, '[visibility=visible]') == null
|
|
) {
|
|
detachNodeFromParent(node, parentNode);
|
|
return;
|
|
}
|
|
|
|
// display="none"
|
|
//
|
|
// https://www.w3.org/TR/SVG11/painting.html#DisplayProperty
|
|
// "A value of display: none indicates that the given element
|
|
// and its children shall not be rendered directly"
|
|
if (
|
|
displayNone &&
|
|
computedStyle.display &&
|
|
computedStyle.display.type === 'static' &&
|
|
computedStyle.display.value === 'none' &&
|
|
// markers with display: none still rendered
|
|
node.name !== 'marker'
|
|
) {
|
|
detachNodeFromParent(node, parentNode);
|
|
return;
|
|
}
|
|
|
|
// Circles with zero radius
|
|
//
|
|
// https://www.w3.org/TR/SVG11/shapes.html#CircleElementRAttribute
|
|
// "A value of zero disables rendering of the element"
|
|
//
|
|
// <circle r="0">
|
|
if (
|
|
circleR0 &&
|
|
node.name === 'circle' &&
|
|
node.children.length === 0 &&
|
|
node.attributes.r === '0'
|
|
) {
|
|
detachNodeFromParent(node, parentNode);
|
|
return;
|
|
}
|
|
|
|
// Ellipse with zero x-axis radius
|
|
//
|
|
// https://www.w3.org/TR/SVG11/shapes.html#EllipseElementRXAttribute
|
|
// "A value of zero disables rendering of the element"
|
|
//
|
|
// <ellipse rx="0">
|
|
if (
|
|
ellipseRX0 &&
|
|
node.name === 'ellipse' &&
|
|
node.children.length === 0 &&
|
|
node.attributes.rx === '0'
|
|
) {
|
|
detachNodeFromParent(node, parentNode);
|
|
return;
|
|
}
|
|
|
|
// Ellipse with zero y-axis radius
|
|
//
|
|
// https://www.w3.org/TR/SVG11/shapes.html#EllipseElementRYAttribute
|
|
// "A value of zero disables rendering of the element"
|
|
//
|
|
// <ellipse ry="0">
|
|
if (
|
|
ellipseRY0 &&
|
|
node.name === 'ellipse' &&
|
|
node.children.length === 0 &&
|
|
node.attributes.ry === '0'
|
|
) {
|
|
detachNodeFromParent(node, parentNode);
|
|
return;
|
|
}
|
|
|
|
// Rectangle with zero width
|
|
//
|
|
// https://www.w3.org/TR/SVG11/shapes.html#RectElementWidthAttribute
|
|
// "A value of zero disables rendering of the element"
|
|
//
|
|
// <rect width="0">
|
|
if (
|
|
rectWidth0 &&
|
|
node.name === 'rect' &&
|
|
node.children.length === 0 &&
|
|
node.attributes.width === '0'
|
|
) {
|
|
detachNodeFromParent(node, parentNode);
|
|
return;
|
|
}
|
|
|
|
// Rectangle with zero height
|
|
//
|
|
// https://www.w3.org/TR/SVG11/shapes.html#RectElementHeightAttribute
|
|
// "A value of zero disables rendering of the element"
|
|
//
|
|
// <rect height="0">
|
|
if (
|
|
rectHeight0 &&
|
|
rectWidth0 &&
|
|
node.name === 'rect' &&
|
|
node.children.length === 0 &&
|
|
node.attributes.height === '0'
|
|
) {
|
|
detachNodeFromParent(node, parentNode);
|
|
return;
|
|
}
|
|
|
|
// Pattern with zero width
|
|
//
|
|
// https://www.w3.org/TR/SVG11/pservers.html#PatternElementWidthAttribute
|
|
// "A value of zero disables rendering of the element (i.e., no paint is applied)"
|
|
//
|
|
// <pattern width="0">
|
|
if (
|
|
patternWidth0 &&
|
|
node.name === 'pattern' &&
|
|
node.attributes.width === '0'
|
|
) {
|
|
detachNodeFromParent(node, parentNode);
|
|
return;
|
|
}
|
|
|
|
// Pattern with zero height
|
|
//
|
|
// https://www.w3.org/TR/SVG11/pservers.html#PatternElementHeightAttribute
|
|
// "A value of zero disables rendering of the element (i.e., no paint is applied)"
|
|
//
|
|
// <pattern height="0">
|
|
if (
|
|
patternHeight0 &&
|
|
node.name === 'pattern' &&
|
|
node.attributes.height === '0'
|
|
) {
|
|
detachNodeFromParent(node, parentNode);
|
|
return;
|
|
}
|
|
|
|
// Image with zero width
|
|
//
|
|
// https://www.w3.org/TR/SVG11/struct.html#ImageElementWidthAttribute
|
|
// "A value of zero disables rendering of the element"
|
|
//
|
|
// <image width="0">
|
|
if (
|
|
imageWidth0 &&
|
|
node.name === 'image' &&
|
|
node.attributes.width === '0'
|
|
) {
|
|
detachNodeFromParent(node, parentNode);
|
|
return;
|
|
}
|
|
|
|
// Image with zero height
|
|
//
|
|
// https://www.w3.org/TR/SVG11/struct.html#ImageElementHeightAttribute
|
|
// "A value of zero disables rendering of the element"
|
|
//
|
|
// <image height="0">
|
|
if (
|
|
imageHeight0 &&
|
|
node.name === 'image' &&
|
|
node.attributes.height === '0'
|
|
) {
|
|
detachNodeFromParent(node, parentNode);
|
|
return;
|
|
}
|
|
|
|
// Path with empty data
|
|
//
|
|
// https://www.w3.org/TR/SVG11/paths.html#DAttribute
|
|
//
|
|
// <path d=""/>
|
|
if (pathEmptyD && node.name === 'path') {
|
|
if (node.attributes.d == null) {
|
|
detachNodeFromParent(node, parentNode);
|
|
return;
|
|
}
|
|
const pathData = parsePathData(node.attributes.d);
|
|
if (pathData.length === 0) {
|
|
detachNodeFromParent(node, parentNode);
|
|
return;
|
|
}
|
|
// keep single point paths for markers
|
|
if (
|
|
pathData.length === 1 &&
|
|
computedStyle['marker-start'] == null &&
|
|
computedStyle['marker-end'] == null
|
|
) {
|
|
detachNodeFromParent(node, parentNode);
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Polyline with empty points
|
|
//
|
|
// https://www.w3.org/TR/SVG11/shapes.html#PolylineElementPointsAttribute
|
|
//
|
|
// <polyline points="">
|
|
if (
|
|
polylineEmptyPoints &&
|
|
node.name === 'polyline' &&
|
|
node.attributes.points == null
|
|
) {
|
|
detachNodeFromParent(node, parentNode);
|
|
return;
|
|
}
|
|
|
|
// Polygon with empty points
|
|
//
|
|
// https://www.w3.org/TR/SVG11/shapes.html#PolygonElementPointsAttribute
|
|
//
|
|
// <polygon points="">
|
|
if (
|
|
polygonEmptyPoints &&
|
|
node.name === 'polygon' &&
|
|
node.attributes.points == null
|
|
) {
|
|
detachNodeFromParent(node, parentNode);
|
|
return;
|
|
}
|
|
},
|
|
},
|
|
};
|
|
};
|