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 | };
|
---|