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.
105 lines
2.9 KiB
105 lines
2.9 KiB
'use strict';
|
|
|
|
const { detachNodeFromParent } = require('../lib/xast.js');
|
|
const { collectStylesheet, computeStyle } = require('../lib/style.js');
|
|
const { path2js, js2path, intersects } = require('./_path.js');
|
|
|
|
exports.type = 'visitor';
|
|
exports.name = 'mergePaths';
|
|
exports.active = true;
|
|
exports.description = 'merges multiple paths in one if possible';
|
|
|
|
/**
|
|
* Merge multiple Paths into one.
|
|
*
|
|
* @author Kir Belevich, Lev Solntsev
|
|
*
|
|
* @type {import('../lib/types').Plugin<{
|
|
* force?: boolean,
|
|
* floatPrecision?: number,
|
|
* noSpaceAfterFlags?: boolean
|
|
* }>}
|
|
*/
|
|
exports.fn = (root, params) => {
|
|
const {
|
|
force = false,
|
|
floatPrecision,
|
|
noSpaceAfterFlags = false, // a20 60 45 0 1 30 20 → a20 60 45 0130 20
|
|
} = params;
|
|
const stylesheet = collectStylesheet(root);
|
|
|
|
return {
|
|
element: {
|
|
enter: (node) => {
|
|
let prevChild = null;
|
|
|
|
for (const child of node.children) {
|
|
// skip if previous element is not path or contains animation elements
|
|
if (
|
|
prevChild == null ||
|
|
prevChild.type !== 'element' ||
|
|
prevChild.name !== 'path' ||
|
|
prevChild.children.length !== 0 ||
|
|
prevChild.attributes.d == null
|
|
) {
|
|
prevChild = child;
|
|
continue;
|
|
}
|
|
|
|
// skip if element is not path or contains animation elements
|
|
if (
|
|
child.type !== 'element' ||
|
|
child.name !== 'path' ||
|
|
child.children.length !== 0 ||
|
|
child.attributes.d == null
|
|
) {
|
|
prevChild = child;
|
|
continue;
|
|
}
|
|
|
|
// preserve paths with markers
|
|
const computedStyle = computeStyle(stylesheet, child);
|
|
if (
|
|
computedStyle['marker-start'] ||
|
|
computedStyle['marker-mid'] ||
|
|
computedStyle['marker-end']
|
|
) {
|
|
prevChild = child;
|
|
continue;
|
|
}
|
|
|
|
const prevChildAttrs = Object.keys(prevChild.attributes);
|
|
const childAttrs = Object.keys(child.attributes);
|
|
let attributesAreEqual = prevChildAttrs.length === childAttrs.length;
|
|
for (const name of childAttrs) {
|
|
if (name !== 'd') {
|
|
if (
|
|
prevChild.attributes[name] == null ||
|
|
prevChild.attributes[name] !== child.attributes[name]
|
|
) {
|
|
attributesAreEqual = false;
|
|
}
|
|
}
|
|
}
|
|
const prevPathJS = path2js(prevChild);
|
|
const curPathJS = path2js(child);
|
|
|
|
if (
|
|
attributesAreEqual &&
|
|
(force || !intersects(prevPathJS, curPathJS))
|
|
) {
|
|
js2path(prevChild, prevPathJS.concat(curPathJS), {
|
|
floatPrecision,
|
|
noSpaceAfterFlags,
|
|
});
|
|
detachNodeFromParent(child, node);
|
|
continue;
|
|
}
|
|
|
|
prevChild = child;
|
|
}
|
|
},
|
|
},
|
|
};
|
|
};
|