1
0
mirror of https://github.com/svg/svgo.git synced 2025-07-31 07:44:22 +03:00

Convert mergePaths to visitor

This commit is contained in:
Bogdan Chadkin
2021-03-29 03:08:15 +03:00
parent 368a67b70f
commit 06110b4fc0
4 changed files with 91 additions and 60 deletions

View File

@ -1,80 +1,99 @@
'use strict'; 'use strict';
const { detachNodeFromParent } = require('../lib/xast.js');
const { computeStyle } = require('../lib/style.js'); const { computeStyle } = require('../lib/style.js');
const { path2js, js2path, intersects } = require('./_path.js'); const { path2js, js2path, intersects } = require('./_path.js');
exports.type = 'perItem'; exports.type = 'visitor';
exports.active = true; exports.active = true;
exports.description = 'merges multiple paths in one if possible'; exports.description = 'merges multiple paths in one if possible';
exports.params = {
collapseRepeated: true,
force: false,
leadingZero: true,
negativeExtraSpace: true,
noSpaceAfterFlags: false, // a20 60 45 0 1 30 20 → a20 60 45 0130 20
};
/** /**
* Merge multiple Paths into one. * Merge multiple Paths into one.
* *
* @param {Object} item current iteration item * @param {Object} root
* @return {Boolean} if false, item will be filtered out * @param {Object} params
* *
* @author Kir Belevich, Lev Solntsev * @author Kir Belevich, Lev Solntsev
*/ */
exports.fn = function (item, params) { exports.fn = (root, params) => {
if (item.type !== 'element' || item.children.length === 0) return; const {
force = false,
floatPrecision,
noSpaceAfterFlags = false, // a20 60 45 0 1 30 20 → a20 60 45 0130 20
} = params;
let prevContentItem = null; return {
let prevContentItemKeys = null; element: {
enter: (node) => {
let prevChild = null;
item.children = item.children.filter(function (contentItem) { for (const child of node.children) {
if ( // skip if previous element is not path or contains animation elements
prevContentItem && if (
prevContentItem.isElem('path') && prevChild == null ||
prevContentItem.children.length === 0 && prevChild.type !== 'element' ||
prevContentItem.attributes.d != null && prevChild.name !== 'path' ||
contentItem.isElem('path') && prevChild.children.length !== 0 ||
contentItem.children.length === 0 && prevChild.attributes.d == null
contentItem.attributes.d != null ) {
) { prevChild = child;
const computedStyle = computeStyle(contentItem); continue;
// keep path to not break markers }
if (
computedStyle['marker-start'] ||
computedStyle['marker-mid'] ||
computedStyle['marker-end']
) {
return true;
}
if (!prevContentItemKeys) {
prevContentItemKeys = Object.keys(prevContentItem.attributes);
}
const contentItemAttrs = Object.keys(contentItem.attributes); // skip if element is not path or contains animation elements
const equalData = if (
prevContentItemKeys.length == contentItemAttrs.length && child.type !== 'element' ||
contentItemAttrs.every(function (key) { child.name !== 'path' ||
return ( child.children.length !== 0 ||
key == 'd' || child.attributes.d == null
(prevContentItem.attributes[key] != null && ) {
prevContentItem.attributes[key] == contentItem.attributes[key]) prevChild = child;
); continue;
}); }
const prevPathJS = path2js(prevContentItem);
const curPathJS = path2js(contentItem);
if (equalData && (params.force || !intersects(prevPathJS, curPathJS))) { // preserve paths with markers
js2path(prevContentItem, prevPathJS.concat(curPathJS), params); const computedStyle = computeStyle(child);
return false; if (
} computedStyle['marker-start'] ||
} computedStyle['marker-mid'] ||
computedStyle['marker-end']
) {
prevChild = child;
continue;
}
prevContentItem = contentItem; const prevChildAttrs = Object.keys(prevChild.attributes);
prevContentItemKeys = null; const childAttrs = Object.keys(child.attributes);
return true; 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);
continue;
}
prevChild = child;
}
},
},
};
}; };

View File

@ -1,3 +1,7 @@
Merge sequences of paths without attributes
===
<svg xmlns="http://www.w3.org/2000/svg"> <svg xmlns="http://www.w3.org/2000/svg">
<path d="M 0,0 z"/> <path d="M 0,0 z"/>
<path d="M 10,10 z"/> <path d="M 10,10 z"/>

Before

Width:  |  Height:  |  Size: 494 B

After

Width:  |  Height:  |  Size: 544 B

View File

@ -1,3 +1,7 @@
Merge sequences of paths with the same attributes
===
<svg xmlns="http://www.w3.org/2000/svg"> <svg xmlns="http://www.w3.org/2000/svg">
<path d="M 0,0 z" fill="#fff" stroke="#333"/> <path d="M 0,0 z" fill="#fff" stroke="#333"/>
<path d="M 10,10 z" fill="#fff" stroke="#333"/> <path d="M 10,10 z" fill="#fff" stroke="#333"/>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,3 +1,7 @@
Merge only intersected paths
===
<svg xmlns="http://www.w3.org/2000/svg"> <svg xmlns="http://www.w3.org/2000/svg">
<path d="M30 0L0 40H60z"/> <path d="M30 0L0 40H60z"/>
<path d="M0 10H60L30 50z"/> <path d="M0 10H60L30 50z"/>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB