149 lines
4.1 KiB
JavaScript
149 lines
4.1 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* @typedef {import("../lib/types").PathDataItem} PathDataItem
|
|
* @typedef {import('../lib/types').XastChild} XastChild
|
|
* @typedef {import('../lib/types').XastElement} XastElement
|
|
*/
|
|
|
|
const { collectStylesheet, computeStyle } = require('../lib/style.js');
|
|
const { path2js, js2path, intersects } = require('./_path.js');
|
|
|
|
exports.name = 'mergePaths';
|
|
exports.description = 'merges multiple paths in one if possible';
|
|
|
|
/**
|
|
* Merge multiple Paths into one.
|
|
*
|
|
* @author Kir Belevich, Lev Solntsev
|
|
*
|
|
* @type {import('./plugins-types').Plugin<'mergePaths'>}
|
|
*/
|
|
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) => {
|
|
if (node.children.length <= 1) {
|
|
return;
|
|
}
|
|
|
|
/** @type {XastChild[]} */
|
|
const elementsToRemove = [];
|
|
let prevChild = node.children[0];
|
|
let prevPathData = null;
|
|
|
|
/**
|
|
* @param {XastElement} child
|
|
* @param {PathDataItem[]} pathData
|
|
*/
|
|
const updatePreviousPath = (child, pathData) => {
|
|
js2path(child, pathData, {
|
|
floatPrecision,
|
|
noSpaceAfterFlags,
|
|
});
|
|
prevPathData = null;
|
|
};
|
|
|
|
for (let i = 1; i < node.children.length; i++) {
|
|
const child = node.children[i];
|
|
|
|
if (
|
|
prevChild.type !== 'element' ||
|
|
prevChild.name !== 'path' ||
|
|
prevChild.children.length !== 0 ||
|
|
prevChild.attributes.d == null
|
|
) {
|
|
if (prevPathData && prevChild.type === 'element') {
|
|
updatePreviousPath(prevChild, prevPathData);
|
|
}
|
|
prevChild = child;
|
|
continue;
|
|
}
|
|
|
|
if (
|
|
child.type !== 'element' ||
|
|
child.name !== 'path' ||
|
|
child.children.length !== 0 ||
|
|
child.attributes.d == null
|
|
) {
|
|
if (prevPathData) {
|
|
updatePreviousPath(prevChild, prevPathData);
|
|
}
|
|
prevChild = child;
|
|
continue;
|
|
}
|
|
|
|
const computedStyle = computeStyle(stylesheet, child);
|
|
if (
|
|
computedStyle['marker-start'] ||
|
|
computedStyle['marker-mid'] ||
|
|
computedStyle['marker-end']
|
|
) {
|
|
if (prevPathData) {
|
|
updatePreviousPath(prevChild, prevPathData);
|
|
}
|
|
prevChild = child;
|
|
continue;
|
|
}
|
|
const childAttrs = Object.keys(child.attributes);
|
|
if (childAttrs.length !== Object.keys(prevChild.attributes).length) {
|
|
if (prevPathData) {
|
|
updatePreviousPath(prevChild, prevPathData);
|
|
}
|
|
prevChild = child;
|
|
continue;
|
|
}
|
|
|
|
const areAttrsEqual = childAttrs.some((attr) => {
|
|
return (
|
|
attr !== 'd' &&
|
|
prevChild.type === 'element' &&
|
|
prevChild.attributes[attr] !== child.attributes[attr]
|
|
);
|
|
});
|
|
|
|
if (areAttrsEqual) {
|
|
if (prevPathData) {
|
|
updatePreviousPath(prevChild, prevPathData);
|
|
}
|
|
prevChild = child;
|
|
continue;
|
|
}
|
|
|
|
const hasPrevPath = prevPathData != null;
|
|
const currentPathData = path2js(child);
|
|
prevPathData = prevPathData ?? path2js(prevChild);
|
|
|
|
if (force || !intersects(prevPathData, currentPathData)) {
|
|
prevPathData.push(...currentPathData);
|
|
elementsToRemove.push(child);
|
|
continue;
|
|
}
|
|
|
|
if (hasPrevPath) {
|
|
updatePreviousPath(prevChild, prevPathData);
|
|
}
|
|
|
|
prevChild = child;
|
|
prevPathData = null;
|
|
}
|
|
|
|
if (prevPathData && prevChild.type === 'element') {
|
|
updatePreviousPath(prevChild, prevPathData);
|
|
}
|
|
|
|
node.children = node.children.filter(
|
|
(child) => !elementsToRemove.includes(child),
|
|
);
|
|
},
|
|
},
|
|
};
|
|
};
|