136 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			136 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| /**
 | |
|  * @typedef {import('../lib/types').XastNode} XastNode
 | |
|  */
 | |
| 
 | |
| const { inheritableAttrs, elemsGroups } = require('./_collections.js');
 | |
| 
 | |
| exports.type = 'visitor';
 | |
| exports.name = 'collapseGroups';
 | |
| exports.active = true;
 | |
| exports.description = 'collapses useless groups';
 | |
| 
 | |
| /**
 | |
|  * @type {(node: XastNode, name: string) => boolean}
 | |
|  */
 | |
| const hasAnimatedAttr = (node, name) => {
 | |
|   if (node.type === 'element') {
 | |
|     if (
 | |
|       elemsGroups.animation.includes(node.name) &&
 | |
|       node.attributes.attributeName === name
 | |
|     ) {
 | |
|       return true;
 | |
|     }
 | |
|     for (const child of node.children) {
 | |
|       if (hasAnimatedAttr(child, name)) {
 | |
|         return true;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return false;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Collapse useless groups.
 | |
|  *
 | |
|  * @example
 | |
|  * <g>
 | |
|  *     <g attr1="val1">
 | |
|  *         <path d="..."/>
 | |
|  *     </g>
 | |
|  * </g>
 | |
|  *         ⬇
 | |
|  * <g>
 | |
|  *     <g>
 | |
|  *         <path attr1="val1" d="..."/>
 | |
|  *     </g>
 | |
|  * </g>
 | |
|  *         ⬇
 | |
|  * <path attr1="val1" d="..."/>
 | |
|  *
 | |
|  * @author Kir Belevich
 | |
|  *
 | |
|  * @type {import('../lib/types').Plugin<void>}
 | |
|  */
 | |
| exports.fn = () => {
 | |
|   return {
 | |
|     element: {
 | |
|       exit: (node, parentNode) => {
 | |
|         if (parentNode.type === 'root' || parentNode.name === 'switch') {
 | |
|           return;
 | |
|         }
 | |
|         // non-empty groups
 | |
|         if (node.name !== 'g' || node.children.length === 0) {
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|         // move group attibutes to the single child element
 | |
|         if (
 | |
|           Object.keys(node.attributes).length !== 0 &&
 | |
|           node.children.length === 1
 | |
|         ) {
 | |
|           const firstChild = node.children[0];
 | |
|           // TODO untangle this mess
 | |
|           if (
 | |
|             firstChild.type === 'element' &&
 | |
|             firstChild.attributes.id == null &&
 | |
|             node.attributes.filter == null &&
 | |
|             (node.attributes.class == null ||
 | |
|               firstChild.attributes.class == null) &&
 | |
|             ((node.attributes['clip-path'] == null &&
 | |
|               node.attributes.mask == null) ||
 | |
|               (firstChild.name === 'g' &&
 | |
|                 node.attributes.transform == null &&
 | |
|                 firstChild.attributes.transform == null))
 | |
|           ) {
 | |
|             for (const [name, value] of Object.entries(node.attributes)) {
 | |
|               // avoid copying to not conflict with animated attribute
 | |
|               if (hasAnimatedAttr(firstChild, name)) {
 | |
|                 return;
 | |
|               }
 | |
|               if (firstChild.attributes[name] == null) {
 | |
|                 firstChild.attributes[name] = value;
 | |
|               } else if (name === 'transform') {
 | |
|                 firstChild.attributes[name] =
 | |
|                   value + ' ' + firstChild.attributes[name];
 | |
|               } else if (firstChild.attributes[name] === 'inherit') {
 | |
|                 firstChild.attributes[name] = value;
 | |
|               } else if (
 | |
|                 inheritableAttrs.includes(name) === false &&
 | |
|                 firstChild.attributes[name] !== value
 | |
|               ) {
 | |
|                 return;
 | |
|               }
 | |
|               delete node.attributes[name];
 | |
|             }
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         // collapse groups without attributes
 | |
|         if (Object.keys(node.attributes).length === 0) {
 | |
|           // animation elements "add" attributes to group
 | |
|           // group should be preserved
 | |
|           for (const child of node.children) {
 | |
|             if (
 | |
|               child.type === 'element' &&
 | |
|               elemsGroups.animation.includes(child.name)
 | |
|             ) {
 | |
|               return;
 | |
|             }
 | |
|           }
 | |
|           // replace current node with all its children
 | |
|           const index = parentNode.children.indexOf(node);
 | |
|           parentNode.children.splice(index, 1, ...node.children);
 | |
|           // TODO remove in v3
 | |
|           for (const child of node.children) {
 | |
|             // @ts-ignore parentNode is forbidden for public usage
 | |
|             // and will be moved in v3
 | |
|             child.parentNode = parentNode;
 | |
|           }
 | |
|         }
 | |
|       },
 | |
|     },
 | |
|   };
 | |
| };
 |