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.
104 lines
2.9 KiB
104 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; |
|
} |
|
}, |
|
}, |
|
}; |
|
};
|
|
|