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