| 'use strict'; |
| |
| const { visit, visitSkip, detachNodeFromParent } = require('../lib/xast.js'); |
| const { collectStylesheet, computeStyle } = require('../lib/style.js'); |
| const { elemsGroups } = require('./_collections.js'); |
| |
| exports.type = 'visitor'; |
| exports.name = 'removeUselessStrokeAndFill'; |
| exports.active = true; |
| exports.description = 'removes useless stroke and fill attributes'; |
| |
| /** |
| * Remove useless stroke and fill attrs. |
| * |
| * @author Kir Belevich |
| * |
| * @type {import('../lib/types').Plugin<{ |
| * stroke?: boolean, |
| * fill?: boolean, |
| * removeNone?: boolean |
| * }>} |
| */ |
| exports.fn = (root, params) => { |
| const { |
| stroke: removeStroke = true, |
| fill: removeFill = true, |
| removeNone = false, |
| } = params; |
| |
| // style and script elements deoptimise this plugin |
| let hasStyleOrScript = false; |
| visit(root, { |
| element: { |
| enter: (node) => { |
| if (node.name === 'style' || node.name === 'script') { |
| hasStyleOrScript = true; |
| } |
| }, |
| }, |
| }); |
| if (hasStyleOrScript) { |
| return null; |
| } |
| |
| const stylesheet = collectStylesheet(root); |
| |
| return { |
| element: { |
| enter: (node, parentNode) => { |
| // id attribute deoptimise the whole subtree |
| if (node.attributes.id != null) { |
| return visitSkip; |
| } |
| if (elemsGroups.shape.includes(node.name) == false) { |
| return; |
| } |
| const computedStyle = computeStyle(stylesheet, node); |
| const stroke = computedStyle.stroke; |
| const strokeOpacity = computedStyle['stroke-opacity']; |
| const strokeWidth = computedStyle['stroke-width']; |
| const markerEnd = computedStyle['marker-end']; |
| const fill = computedStyle.fill; |
| const fillOpacity = computedStyle['fill-opacity']; |
| const computedParentStyle = |
| parentNode.type === 'element' |
| ? computeStyle(stylesheet, parentNode) |
| : null; |
| const parentStroke = |
| computedParentStyle == null ? null : computedParentStyle.stroke; |
| |
| // remove stroke* |
| if (removeStroke) { |
| if ( |
| stroke == null || |
| (stroke.type === 'static' && stroke.value == 'none') || |
| (strokeOpacity != null && |
| strokeOpacity.type === 'static' && |
| strokeOpacity.value === '0') || |
| (strokeWidth != null && |
| strokeWidth.type === 'static' && |
| strokeWidth.value === '0') |
| ) { |
| // stroke-width may affect the size of marker-end |
| // marker is not visible when stroke-width is 0 |
| if ( |
| (strokeWidth != null && |
| strokeWidth.type === 'static' && |
| strokeWidth.value === '0') || |
| markerEnd == null |
| ) { |
| for (const name of Object.keys(node.attributes)) { |
| if (name.startsWith('stroke')) { |
| delete node.attributes[name]; |
| } |
| } |
| // set explicit none to not inherit from parent |
| if ( |
| parentStroke != null && |
| parentStroke.type === 'static' && |
| parentStroke.value !== 'none' |
| ) { |
| node.attributes.stroke = 'none'; |
| } |
| } |
| } |
| } |
| |
| // remove fill* |
| if (removeFill) { |
| if ( |
| (fill != null && fill.type === 'static' && fill.value === 'none') || |
| (fillOpacity != null && |
| fillOpacity.type === 'static' && |
| fillOpacity.value === '0') |
| ) { |
| for (const name of Object.keys(node.attributes)) { |
| if (name.startsWith('fill-')) { |
| delete node.attributes[name]; |
| } |
| } |
| if ( |
| fill == null || |
| (fill.type === 'static' && fill.value !== 'none') |
| ) { |
| node.attributes.fill = 'none'; |
| } |
| } |
| } |
| |
| if (removeNone) { |
| if ( |
| (stroke == null || node.attributes.stroke === 'none') && |
| ((fill != null && |
| fill.type === 'static' && |
| fill.value === 'none') || |
| node.attributes.fill === 'none') |
| ) { |
| detachNodeFromParent(node, parentNode); |
| } |
| } |
| }, |
| }, |
| }; |
| }; |