[6a3a178] | 1 | 'use strict';
|
---|
| 2 |
|
---|
| 3 | const {
|
---|
| 4 | querySelector,
|
---|
| 5 | closestByName,
|
---|
| 6 | detachNodeFromParent,
|
---|
| 7 | } = require('../lib/xast.js');
|
---|
| 8 | const { collectStylesheet, computeStyle } = require('../lib/style.js');
|
---|
| 9 | const { parsePathData } = require('../lib/path.js');
|
---|
| 10 |
|
---|
| 11 | exports.name = 'removeHiddenElems';
|
---|
| 12 | exports.type = 'visitor';
|
---|
| 13 | exports.active = true;
|
---|
| 14 | exports.description =
|
---|
| 15 | 'removes hidden elements (zero sized, with absent attributes)';
|
---|
| 16 |
|
---|
| 17 | /**
|
---|
| 18 | * Remove hidden elements with disabled rendering:
|
---|
| 19 | * - display="none"
|
---|
| 20 | * - opacity="0"
|
---|
| 21 | * - circle with zero radius
|
---|
| 22 | * - ellipse with zero x-axis or y-axis radius
|
---|
| 23 | * - rectangle with zero width or height
|
---|
| 24 | * - pattern with zero width or height
|
---|
| 25 | * - image with zero width or height
|
---|
| 26 | * - path with empty data
|
---|
| 27 | * - polyline with empty points
|
---|
| 28 | * - polygon with empty points
|
---|
| 29 | *
|
---|
| 30 | * @author Kir Belevich
|
---|
| 31 | *
|
---|
| 32 | * @type {import('../lib/types').Plugin<{
|
---|
| 33 | * isHidden: boolean,
|
---|
| 34 | * displayNone: boolean,
|
---|
| 35 | * opacity0: boolean,
|
---|
| 36 | * circleR0: boolean,
|
---|
| 37 | * ellipseRX0: boolean,
|
---|
| 38 | * ellipseRY0: boolean,
|
---|
| 39 | * rectWidth0: boolean,
|
---|
| 40 | * rectHeight0: boolean,
|
---|
| 41 | * patternWidth0: boolean,
|
---|
| 42 | * patternHeight0: boolean,
|
---|
| 43 | * imageWidth0: boolean,
|
---|
| 44 | * imageHeight0: boolean,
|
---|
| 45 | * pathEmptyD: boolean,
|
---|
| 46 | * polylineEmptyPoints: boolean,
|
---|
| 47 | * polygonEmptyPoints: boolean,
|
---|
| 48 | * }>}
|
---|
| 49 | */
|
---|
| 50 | exports.fn = (root, params) => {
|
---|
| 51 | const {
|
---|
| 52 | isHidden = true,
|
---|
| 53 | displayNone = true,
|
---|
| 54 | opacity0 = true,
|
---|
| 55 | circleR0 = true,
|
---|
| 56 | ellipseRX0 = true,
|
---|
| 57 | ellipseRY0 = true,
|
---|
| 58 | rectWidth0 = true,
|
---|
| 59 | rectHeight0 = true,
|
---|
| 60 | patternWidth0 = true,
|
---|
| 61 | patternHeight0 = true,
|
---|
| 62 | imageWidth0 = true,
|
---|
| 63 | imageHeight0 = true,
|
---|
| 64 | pathEmptyD = true,
|
---|
| 65 | polylineEmptyPoints = true,
|
---|
| 66 | polygonEmptyPoints = true,
|
---|
| 67 | } = params;
|
---|
| 68 | const stylesheet = collectStylesheet(root);
|
---|
| 69 |
|
---|
| 70 | return {
|
---|
| 71 | element: {
|
---|
| 72 | enter: (node, parentNode) => {
|
---|
| 73 | // Removes hidden elements
|
---|
| 74 | // https://www.w3schools.com/cssref/pr_class_visibility.asp
|
---|
| 75 | const computedStyle = computeStyle(stylesheet, node);
|
---|
| 76 | if (
|
---|
| 77 | isHidden &&
|
---|
| 78 | computedStyle.visibility &&
|
---|
| 79 | computedStyle.visibility.type === 'static' &&
|
---|
| 80 | computedStyle.visibility.value === 'hidden' &&
|
---|
| 81 | // keep if any descendant enables visibility
|
---|
| 82 | querySelector(node, '[visibility=visible]') == null
|
---|
| 83 | ) {
|
---|
| 84 | detachNodeFromParent(node, parentNode);
|
---|
| 85 | return;
|
---|
| 86 | }
|
---|
| 87 |
|
---|
| 88 | // display="none"
|
---|
| 89 | //
|
---|
| 90 | // https://www.w3.org/TR/SVG11/painting.html#DisplayProperty
|
---|
| 91 | // "A value of display: none indicates that the given element
|
---|
| 92 | // and its children shall not be rendered directly"
|
---|
| 93 | if (
|
---|
| 94 | displayNone &&
|
---|
| 95 | computedStyle.display &&
|
---|
| 96 | computedStyle.display.type === 'static' &&
|
---|
| 97 | computedStyle.display.value === 'none' &&
|
---|
| 98 | // markers with display: none still rendered
|
---|
| 99 | node.name !== 'marker'
|
---|
| 100 | ) {
|
---|
| 101 | detachNodeFromParent(node, parentNode);
|
---|
| 102 | return;
|
---|
| 103 | }
|
---|
| 104 |
|
---|
| 105 | // opacity="0"
|
---|
| 106 | //
|
---|
| 107 | // https://www.w3.org/TR/SVG11/masking.html#ObjectAndGroupOpacityProperties
|
---|
| 108 | if (
|
---|
| 109 | opacity0 &&
|
---|
| 110 | computedStyle.opacity &&
|
---|
| 111 | computedStyle.opacity.type === 'static' &&
|
---|
| 112 | computedStyle.opacity.value === '0' &&
|
---|
| 113 | // transparent element inside clipPath still affect clipped elements
|
---|
| 114 | closestByName(node, 'clipPath') == null
|
---|
| 115 | ) {
|
---|
| 116 | detachNodeFromParent(node, parentNode);
|
---|
| 117 | return;
|
---|
| 118 | }
|
---|
| 119 |
|
---|
| 120 | // Circles with zero radius
|
---|
| 121 | //
|
---|
| 122 | // https://www.w3.org/TR/SVG11/shapes.html#CircleElementRAttribute
|
---|
| 123 | // "A value of zero disables rendering of the element"
|
---|
| 124 | //
|
---|
| 125 | // <circle r="0">
|
---|
| 126 | if (
|
---|
| 127 | circleR0 &&
|
---|
| 128 | node.name === 'circle' &&
|
---|
| 129 | node.children.length === 0 &&
|
---|
| 130 | node.attributes.r === '0'
|
---|
| 131 | ) {
|
---|
| 132 | detachNodeFromParent(node, parentNode);
|
---|
| 133 | return;
|
---|
| 134 | }
|
---|
| 135 |
|
---|
| 136 | // Ellipse with zero x-axis radius
|
---|
| 137 | //
|
---|
| 138 | // https://www.w3.org/TR/SVG11/shapes.html#EllipseElementRXAttribute
|
---|
| 139 | // "A value of zero disables rendering of the element"
|
---|
| 140 | //
|
---|
| 141 | // <ellipse rx="0">
|
---|
| 142 | if (
|
---|
| 143 | ellipseRX0 &&
|
---|
| 144 | node.name === 'ellipse' &&
|
---|
| 145 | node.children.length === 0 &&
|
---|
| 146 | node.attributes.rx === '0'
|
---|
| 147 | ) {
|
---|
| 148 | detachNodeFromParent(node, parentNode);
|
---|
| 149 | return;
|
---|
| 150 | }
|
---|
| 151 |
|
---|
| 152 | // Ellipse with zero y-axis radius
|
---|
| 153 | //
|
---|
| 154 | // https://www.w3.org/TR/SVG11/shapes.html#EllipseElementRYAttribute
|
---|
| 155 | // "A value of zero disables rendering of the element"
|
---|
| 156 | //
|
---|
| 157 | // <ellipse ry="0">
|
---|
| 158 | if (
|
---|
| 159 | ellipseRY0 &&
|
---|
| 160 | node.name === 'ellipse' &&
|
---|
| 161 | node.children.length === 0 &&
|
---|
| 162 | node.attributes.ry === '0'
|
---|
| 163 | ) {
|
---|
| 164 | detachNodeFromParent(node, parentNode);
|
---|
| 165 | return;
|
---|
| 166 | }
|
---|
| 167 |
|
---|
| 168 | // Rectangle with zero width
|
---|
| 169 | //
|
---|
| 170 | // https://www.w3.org/TR/SVG11/shapes.html#RectElementWidthAttribute
|
---|
| 171 | // "A value of zero disables rendering of the element"
|
---|
| 172 | //
|
---|
| 173 | // <rect width="0">
|
---|
| 174 | if (
|
---|
| 175 | rectWidth0 &&
|
---|
| 176 | node.name === 'rect' &&
|
---|
| 177 | node.children.length === 0 &&
|
---|
| 178 | node.attributes.width === '0'
|
---|
| 179 | ) {
|
---|
| 180 | detachNodeFromParent(node, parentNode);
|
---|
| 181 | return;
|
---|
| 182 | }
|
---|
| 183 |
|
---|
| 184 | // Rectangle with zero height
|
---|
| 185 | //
|
---|
| 186 | // https://www.w3.org/TR/SVG11/shapes.html#RectElementHeightAttribute
|
---|
| 187 | // "A value of zero disables rendering of the element"
|
---|
| 188 | //
|
---|
| 189 | // <rect height="0">
|
---|
| 190 | if (
|
---|
| 191 | rectHeight0 &&
|
---|
| 192 | rectWidth0 &&
|
---|
| 193 | node.name === 'rect' &&
|
---|
| 194 | node.children.length === 0 &&
|
---|
| 195 | node.attributes.height === '0'
|
---|
| 196 | ) {
|
---|
| 197 | detachNodeFromParent(node, parentNode);
|
---|
| 198 | return;
|
---|
| 199 | }
|
---|
| 200 |
|
---|
| 201 | // Pattern with zero width
|
---|
| 202 | //
|
---|
| 203 | // https://www.w3.org/TR/SVG11/pservers.html#PatternElementWidthAttribute
|
---|
| 204 | // "A value of zero disables rendering of the element (i.e., no paint is applied)"
|
---|
| 205 | //
|
---|
| 206 | // <pattern width="0">
|
---|
| 207 | if (
|
---|
| 208 | patternWidth0 &&
|
---|
| 209 | node.name === 'pattern' &&
|
---|
| 210 | node.attributes.width === '0'
|
---|
| 211 | ) {
|
---|
| 212 | detachNodeFromParent(node, parentNode);
|
---|
| 213 | return;
|
---|
| 214 | }
|
---|
| 215 |
|
---|
| 216 | // Pattern with zero height
|
---|
| 217 | //
|
---|
| 218 | // https://www.w3.org/TR/SVG11/pservers.html#PatternElementHeightAttribute
|
---|
| 219 | // "A value of zero disables rendering of the element (i.e., no paint is applied)"
|
---|
| 220 | //
|
---|
| 221 | // <pattern height="0">
|
---|
| 222 | if (
|
---|
| 223 | patternHeight0 &&
|
---|
| 224 | node.name === 'pattern' &&
|
---|
| 225 | node.attributes.height === '0'
|
---|
| 226 | ) {
|
---|
| 227 | detachNodeFromParent(node, parentNode);
|
---|
| 228 | return;
|
---|
| 229 | }
|
---|
| 230 |
|
---|
| 231 | // Image with zero width
|
---|
| 232 | //
|
---|
| 233 | // https://www.w3.org/TR/SVG11/struct.html#ImageElementWidthAttribute
|
---|
| 234 | // "A value of zero disables rendering of the element"
|
---|
| 235 | //
|
---|
| 236 | // <image width="0">
|
---|
| 237 | if (
|
---|
| 238 | imageWidth0 &&
|
---|
| 239 | node.name === 'image' &&
|
---|
| 240 | node.attributes.width === '0'
|
---|
| 241 | ) {
|
---|
| 242 | detachNodeFromParent(node, parentNode);
|
---|
| 243 | return;
|
---|
| 244 | }
|
---|
| 245 |
|
---|
| 246 | // Image with zero height
|
---|
| 247 | //
|
---|
| 248 | // https://www.w3.org/TR/SVG11/struct.html#ImageElementHeightAttribute
|
---|
| 249 | // "A value of zero disables rendering of the element"
|
---|
| 250 | //
|
---|
| 251 | // <image height="0">
|
---|
| 252 | if (
|
---|
| 253 | imageHeight0 &&
|
---|
| 254 | node.name === 'image' &&
|
---|
| 255 | node.attributes.height === '0'
|
---|
| 256 | ) {
|
---|
| 257 | detachNodeFromParent(node, parentNode);
|
---|
| 258 | return;
|
---|
| 259 | }
|
---|
| 260 |
|
---|
| 261 | // Path with empty data
|
---|
| 262 | //
|
---|
| 263 | // https://www.w3.org/TR/SVG11/paths.html#DAttribute
|
---|
| 264 | //
|
---|
| 265 | // <path d=""/>
|
---|
| 266 | if (pathEmptyD && node.name === 'path') {
|
---|
| 267 | if (node.attributes.d == null) {
|
---|
| 268 | detachNodeFromParent(node, parentNode);
|
---|
| 269 | return;
|
---|
| 270 | }
|
---|
| 271 | const pathData = parsePathData(node.attributes.d);
|
---|
| 272 | if (pathData.length === 0) {
|
---|
| 273 | detachNodeFromParent(node, parentNode);
|
---|
| 274 | return;
|
---|
| 275 | }
|
---|
| 276 | // keep single point paths for markers
|
---|
| 277 | if (
|
---|
| 278 | pathData.length === 1 &&
|
---|
| 279 | computedStyle['marker-start'] == null &&
|
---|
| 280 | computedStyle['marker-end'] == null
|
---|
| 281 | ) {
|
---|
| 282 | detachNodeFromParent(node, parentNode);
|
---|
| 283 | return;
|
---|
| 284 | }
|
---|
| 285 | return;
|
---|
| 286 | }
|
---|
| 287 |
|
---|
| 288 | // Polyline with empty points
|
---|
| 289 | //
|
---|
| 290 | // https://www.w3.org/TR/SVG11/shapes.html#PolylineElementPointsAttribute
|
---|
| 291 | //
|
---|
| 292 | // <polyline points="">
|
---|
| 293 | if (
|
---|
| 294 | polylineEmptyPoints &&
|
---|
| 295 | node.name === 'polyline' &&
|
---|
| 296 | node.attributes.points == null
|
---|
| 297 | ) {
|
---|
| 298 | detachNodeFromParent(node, parentNode);
|
---|
| 299 | return;
|
---|
| 300 | }
|
---|
| 301 |
|
---|
| 302 | // Polygon with empty points
|
---|
| 303 | //
|
---|
| 304 | // https://www.w3.org/TR/SVG11/shapes.html#PolygonElementPointsAttribute
|
---|
| 305 | //
|
---|
| 306 | // <polygon points="">
|
---|
| 307 | if (
|
---|
| 308 | polygonEmptyPoints &&
|
---|
| 309 | node.name === 'polygon' &&
|
---|
| 310 | node.attributes.points == null
|
---|
| 311 | ) {
|
---|
| 312 | detachNodeFromParent(node, parentNode);
|
---|
| 313 | return;
|
---|
| 314 | }
|
---|
| 315 | },
|
---|
| 316 | },
|
---|
| 317 | };
|
---|
| 318 | };
|
---|