| '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; |
| } |
| }, |
| }, |
| }; |
| }; |