1
0
mirror of https://github.com/svg/svgo.git synced 2025-08-07 15:22:54 +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) {
// skip if previous element is not path or contains animation elements
if ( if (
prevContentItem && prevChild == null ||
prevContentItem.isElem('path') && prevChild.type !== 'element' ||
prevContentItem.children.length === 0 && prevChild.name !== 'path' ||
prevContentItem.attributes.d != null && prevChild.children.length !== 0 ||
contentItem.isElem('path') && prevChild.attributes.d == null
contentItem.children.length === 0 &&
contentItem.attributes.d != null
) { ) {
const computedStyle = computeStyle(contentItem); prevChild = child;
// keep path to not break markers 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(child);
if ( if (
computedStyle['marker-start'] || computedStyle['marker-start'] ||
computedStyle['marker-mid'] || computedStyle['marker-mid'] ||
computedStyle['marker-end'] computedStyle['marker-end']
) { ) {
return true; prevChild = child;
} continue;
if (!prevContentItemKeys) {
prevContentItemKeys = Object.keys(prevContentItem.attributes);
} }
const contentItemAttrs = Object.keys(contentItem.attributes); const prevChildAttrs = Object.keys(prevChild.attributes);
const equalData = const childAttrs = Object.keys(child.attributes);
prevContentItemKeys.length == contentItemAttrs.length && let attributesAreEqual = prevChildAttrs.length === childAttrs.length;
contentItemAttrs.every(function (key) { for (const name of childAttrs) {
return ( if (name !== 'd') {
key == 'd' || if (
(prevContentItem.attributes[key] != null && prevChild.attributes[name] == null ||
prevContentItem.attributes[key] == contentItem.attributes[key]) 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,
}); });
const prevPathJS = path2js(prevContentItem); detachNodeFromParent(child);
const curPathJS = path2js(contentItem); continue;
if (equalData && (params.force || !intersects(prevPathJS, curPathJS))) {
js2path(prevContentItem, prevPathJS.concat(curPathJS), params);
return false;
}
} }
prevContentItem = contentItem; prevChild = child;
prevContentItemKeys = null; }
return true; },
}); },
};
}; };

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