145 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			145 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| '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);
 | |
|           }
 | |
|         }
 | |
|       },
 | |
|     },
 | |
|   };
 | |
| };
 |