You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
135 lines
3.8 KiB
135 lines
3.8 KiB
'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; |
|
} |
|
} |
|
}, |
|
}, |
|
}; |
|
};
|
|
|