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.
128 lines
3.6 KiB
128 lines
3.6 KiB
'use strict'; |
|
|
|
const { visit } = require('../lib/xast.js'); |
|
const { inheritableAttrs, pathElems } = require('./_collections.js'); |
|
|
|
exports.name = 'moveElemsAttrsToGroup'; |
|
exports.description = 'Move common attributes of group children to the group'; |
|
|
|
/** |
|
* Move common attributes of group children to the group |
|
* |
|
* @example |
|
* <g attr1="val1"> |
|
* <g attr2="val2"> |
|
* text |
|
* </g> |
|
* <circle attr2="val2" attr3="val3"/> |
|
* </g> |
|
* ⬇ |
|
* <g attr1="val1" attr2="val2"> |
|
* <g> |
|
* text |
|
* </g> |
|
* <circle attr3="val3"/> |
|
* </g> |
|
* |
|
* @author Kir Belevich |
|
* |
|
* @type {import('./plugins-types').Plugin<'moveElemsAttrsToGroup'>} |
|
*/ |
|
exports.fn = (root) => { |
|
// find if any style element is present |
|
let deoptimizedWithStyles = false; |
|
visit(root, { |
|
element: { |
|
enter: (node) => { |
|
if (node.name === 'style') { |
|
deoptimizedWithStyles = true; |
|
} |
|
}, |
|
}, |
|
}); |
|
|
|
return { |
|
element: { |
|
exit: (node) => { |
|
// process only groups with more than 1 children |
|
if (node.name !== 'g' || node.children.length <= 1) { |
|
return; |
|
} |
|
|
|
// deoptimize the plugin when style elements are present |
|
// selectors may rely on id, classes or tag names |
|
if (deoptimizedWithStyles) { |
|
return; |
|
} |
|
|
|
/** |
|
* find common attributes in group children |
|
* @type {Map<string, string>} |
|
*/ |
|
const commonAttributes = new Map(); |
|
let initial = true; |
|
let everyChildIsPath = true; |
|
for (const child of node.children) { |
|
if (child.type === 'element') { |
|
if (pathElems.includes(child.name) === false) { |
|
everyChildIsPath = false; |
|
} |
|
if (initial) { |
|
initial = false; |
|
// collect all inheritable attributes from first child element |
|
for (const [name, value] of Object.entries(child.attributes)) { |
|
// consider only inheritable attributes |
|
if (inheritableAttrs.includes(name)) { |
|
commonAttributes.set(name, value); |
|
} |
|
} |
|
} else { |
|
// exclude uncommon attributes from initial list |
|
for (const [name, value] of commonAttributes) { |
|
if (child.attributes[name] !== value) { |
|
commonAttributes.delete(name); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
// preserve transform on children when group has clip-path or mask |
|
if ( |
|
node.attributes['clip-path'] != null || |
|
node.attributes.mask != null |
|
) { |
|
commonAttributes.delete('transform'); |
|
} |
|
|
|
// preserve transform when all children are paths |
|
// so the transform could be applied to path data by other plugins |
|
if (everyChildIsPath) { |
|
commonAttributes.delete('transform'); |
|
} |
|
|
|
// add common children attributes to group |
|
for (const [name, value] of commonAttributes) { |
|
if (name === 'transform') { |
|
if (node.attributes.transform != null) { |
|
node.attributes.transform = `${node.attributes.transform} ${value}`; |
|
} else { |
|
node.attributes.transform = value; |
|
} |
|
} else { |
|
node.attributes[name] = value; |
|
} |
|
} |
|
|
|
// delete common attributes from children |
|
for (const child of node.children) { |
|
if (child.type === 'element') { |
|
for (const [name] of commonAttributes) { |
|
delete child.attributes[name]; |
|
} |
|
} |
|
} |
|
}, |
|
}, |
|
}; |
|
};
|
|
|