1 | 'use strict';
|
---|
2 |
|
---|
3 | const { visit, visitSkip, detachNodeFromParent } = require('../lib/xast.js');
|
---|
4 | const { collectStylesheet, computeStyle } = require('../lib/style.js');
|
---|
5 | const { elemsGroups } = require('./_collections.js');
|
---|
6 |
|
---|
7 | exports.type = 'visitor';
|
---|
8 | exports.name = 'removeUselessStrokeAndFill';
|
---|
9 | exports.active = true;
|
---|
10 | exports.description = 'removes useless stroke and fill attributes';
|
---|
11 |
|
---|
12 | /**
|
---|
13 | * Remove useless stroke and fill attrs.
|
---|
14 | *
|
---|
15 | * @author Kir Belevich
|
---|
16 | *
|
---|
17 | * @type {import('../lib/types').Plugin<{
|
---|
18 | * stroke?: boolean,
|
---|
19 | * fill?: boolean,
|
---|
20 | * removeNone?: boolean
|
---|
21 | * }>}
|
---|
22 | */
|
---|
23 | exports.fn = (root, params) => {
|
---|
24 | const {
|
---|
25 | stroke: removeStroke = true,
|
---|
26 | fill: removeFill = true,
|
---|
27 | removeNone = false,
|
---|
28 | } = params;
|
---|
29 |
|
---|
30 | // style and script elements deoptimise this plugin
|
---|
31 | let hasStyleOrScript = false;
|
---|
32 | visit(root, {
|
---|
33 | element: {
|
---|
34 | enter: (node) => {
|
---|
35 | if (node.name === 'style' || node.name === 'script') {
|
---|
36 | hasStyleOrScript = true;
|
---|
37 | }
|
---|
38 | },
|
---|
39 | },
|
---|
40 | });
|
---|
41 | if (hasStyleOrScript) {
|
---|
42 | return null;
|
---|
43 | }
|
---|
44 |
|
---|
45 | const stylesheet = collectStylesheet(root);
|
---|
46 |
|
---|
47 | return {
|
---|
48 | element: {
|
---|
49 | enter: (node, parentNode) => {
|
---|
50 | // id attribute deoptimise the whole subtree
|
---|
51 | if (node.attributes.id != null) {
|
---|
52 | return visitSkip;
|
---|
53 | }
|
---|
54 | if (elemsGroups.shape.includes(node.name) == false) {
|
---|
55 | return;
|
---|
56 | }
|
---|
57 | const computedStyle = computeStyle(stylesheet, node);
|
---|
58 | const stroke = computedStyle.stroke;
|
---|
59 | const strokeOpacity = computedStyle['stroke-opacity'];
|
---|
60 | const strokeWidth = computedStyle['stroke-width'];
|
---|
61 | const markerEnd = computedStyle['marker-end'];
|
---|
62 | const fill = computedStyle.fill;
|
---|
63 | const fillOpacity = computedStyle['fill-opacity'];
|
---|
64 | const computedParentStyle =
|
---|
65 | parentNode.type === 'element'
|
---|
66 | ? computeStyle(stylesheet, parentNode)
|
---|
67 | : null;
|
---|
68 | const parentStroke =
|
---|
69 | computedParentStyle == null ? null : computedParentStyle.stroke;
|
---|
70 |
|
---|
71 | // remove stroke*
|
---|
72 | if (removeStroke) {
|
---|
73 | if (
|
---|
74 | stroke == null ||
|
---|
75 | (stroke.type === 'static' && stroke.value == 'none') ||
|
---|
76 | (strokeOpacity != null &&
|
---|
77 | strokeOpacity.type === 'static' &&
|
---|
78 | strokeOpacity.value === '0') ||
|
---|
79 | (strokeWidth != null &&
|
---|
80 | strokeWidth.type === 'static' &&
|
---|
81 | strokeWidth.value === '0')
|
---|
82 | ) {
|
---|
83 | // stroke-width may affect the size of marker-end
|
---|
84 | // marker is not visible when stroke-width is 0
|
---|
85 | if (
|
---|
86 | (strokeWidth != null &&
|
---|
87 | strokeWidth.type === 'static' &&
|
---|
88 | strokeWidth.value === '0') ||
|
---|
89 | markerEnd == null
|
---|
90 | ) {
|
---|
91 | for (const name of Object.keys(node.attributes)) {
|
---|
92 | if (name.startsWith('stroke')) {
|
---|
93 | delete node.attributes[name];
|
---|
94 | }
|
---|
95 | }
|
---|
96 | // set explicit none to not inherit from parent
|
---|
97 | if (
|
---|
98 | parentStroke != null &&
|
---|
99 | parentStroke.type === 'static' &&
|
---|
100 | parentStroke.value !== 'none'
|
---|
101 | ) {
|
---|
102 | node.attributes.stroke = 'none';
|
---|
103 | }
|
---|
104 | }
|
---|
105 | }
|
---|
106 | }
|
---|
107 |
|
---|
108 | // remove fill*
|
---|
109 | if (removeFill) {
|
---|
110 | if (
|
---|
111 | (fill != null && fill.type === 'static' && fill.value === 'none') ||
|
---|
112 | (fillOpacity != null &&
|
---|
113 | fillOpacity.type === 'static' &&
|
---|
114 | fillOpacity.value === '0')
|
---|
115 | ) {
|
---|
116 | for (const name of Object.keys(node.attributes)) {
|
---|
117 | if (name.startsWith('fill-')) {
|
---|
118 | delete node.attributes[name];
|
---|
119 | }
|
---|
120 | }
|
---|
121 | if (
|
---|
122 | fill == null ||
|
---|
123 | (fill.type === 'static' && fill.value !== 'none')
|
---|
124 | ) {
|
---|
125 | node.attributes.fill = 'none';
|
---|
126 | }
|
---|
127 | }
|
---|
128 | }
|
---|
129 |
|
---|
130 | if (removeNone) {
|
---|
131 | if (
|
---|
132 | (stroke == null || node.attributes.stroke === 'none') &&
|
---|
133 | ((fill != null &&
|
---|
134 | fill.type === 'static' &&
|
---|
135 | fill.value === 'none') ||
|
---|
136 | node.attributes.fill === 'none')
|
---|
137 | ) {
|
---|
138 | detachNodeFromParent(node, parentNode);
|
---|
139 | }
|
---|
140 | }
|
---|
141 | },
|
---|
142 | },
|
---|
143 | };
|
---|
144 | };
|
---|