import { IAttr , IDocument , IParentNode , ITagNode } from 'svg-vdom' ; import { IRuleOption , TDynamicObj } from '../../typings' ; import { ITag } from '../../typings/node' ; import { IStyleObj } from '../../typings/style' ; import { parseColor } from '../color/parse' ; import { regularAttr } from '../const/regular-attr' ; import { parseAlpha } from '../math/parse-alpha' ; import { hasProp } from '../utils/has-prop' ; import { keysToKey } from '../utils/keys-to-key' ; import { valueIsEqual } from '../xml/attr-is-equal' ; import { getAttr } from '../xml/get-attr' ; import { parseStyleTree } from '../xml/parse-style-tree' ; interface IPathChildrenItem { attr : IAttr ; index : number ; node : ITag ; } interface IPathChildren { [ propName ] : IPathChildrenItem ; } const canbeCombine = ( node1 , node2 , combineFill , combineOpacity ) : boolean => { if ( node1 . children . length || node2 . children . length ) { return false ; } const hasMarker = getAttr ( node1 , 'marker-start' , 'none' ) !== 'none' || getAttr ( node1 , 'marker-mid' , 'none' ) !== 'none' || getAttr ( node1 , 'marker-end' , 'none' ) !== 'none' ; if ( hasMarker ) { return false ; } if ( ! attrDiff ( node1 , node2 ) ) { return false ; } const styles1 = node1 . styles as IStyleObj ; const styles2 = node2 . styles as IStyleObj ; if ( ! styleDiff ( styles1 , styles2 ) ) { return false ; } const noOpacity = ! hasProp ( styles1 , 'opacity' ) || parseAlpha ( styles1 . opacity . value ) === 1 ; const noStrokeOpacity = parseColor ( hasProp ( styles1 , 'stroke' ) ? styles1 . stroke . value : '' ) . a === 1 && ( ! hasProp ( styles1 , 'stroke-opacity' ) || parseAlpha ( styles1 [ 'stroke-opacity' ] . value ) === 1 ) ; const noFillOpacity = parseColor ( hasProp ( styles1 , 'fill' ) ? styles1 . fill . value : '' ) . a === 1 && ( ! hasProp ( styles1 , 'fill-opacity' ) || parseAlpha ( styles1 [ 'fill-opacity' ] . value ) === 1 ) ; const noFill = ( hasProp ( styles1 , 'fill' ) && styles1 . fill . value === 'none' ) || combineFill ; const noEvenOdd = ! hasProp ( styles1 , 'fill-rule' ) || styles1 [ 'fill-rule' ] . value !== 'evenodd' ; const noStroke = ( ! hasProp ( styles1 , 'stroke' ) || styles1 . stroke . value === 'none' ) ; const withoutOpacity = combineOpacity || ( noOpacity && noFillOpacity && noStrokeOpacity ) ; return withoutOpacity && ( noFill || noStroke ) && noEvenOdd ; } ; const attrDiff = ( node1 , node2 ) => { const obj1 = { } ; const obj2 = { } ; let same = true ; node1 . attributes . forEach ( ( { value , fullname } ) => { if ( fullname !== 'd' && fullname !== 'style' ) { obj1 [ fullname ] = value ; } } ) ; node2 . attributes . forEach ( ( { value , fullname } ) => { if ( fullname !== 'd' && fullname !== 'style' ) { obj2 [ fullname ] = value ; const attrDefine = regularAttr [ fullname ] ; if ( ! hasProp ( obj1 , fullname ) || ! valueIsEqual ( attrDefine , value , obj1 [ fullname ] ) ) { same = false ; } } } ) ; return same && keysToKey ( obj1 ) === keysToKey ( obj2 ) ; } ; const styleDiff = ( styles1 , styles2 ) => { let same = true ; Object . keys ( styles1 ) . forEach ( key => { const attrDefine = regularAttr [ key ] ; if ( ! hasProp ( styles2 , key ) || ! valueIsEqual ( attrDefine , styles1 [ key ] . value , styles2 [ key ] . value ) ) { same = false ; } } ) ; return same && keysToKey ( styles1 ) === keysToKey ( styles2 ) ; } ; export const combinePath = async ( dom , { option : { disregardFill , disregardOpacity , } } ) : Promise < void > => new Promise ( resolve => { parseStyleTree ( dom ) ; const pathTags = dom . querySelectorAll ( 'path' ) as ITagNode [ ] ; for ( let i = pathTags . length - 1 ; i > 0 ; i -- ) { const pathItem = pathTags [ i ] ; const prevItem = pathTags [ i - 1 ] ; const parent = pathItem . parentNode as IParentNode ; if ( prevItem . parentNode === parent ) { const siblings = parent . children ; const index = siblings . indexOf ( pathItem ) ; const pIndex = siblings . indexOf ( prevItem ) ; if ( pIndex !== index - 1 ) { break ; } if ( canbeCombine ( pathItem , prevItem , disregardFill as boolean , disregardOpacity as boolean ) ) { const prevD = prevItem . getAttribute ( 'd' ) || '' ; const pathD = pathItem . getAttribute ( 'd' ) || '' ; prevItem . setAttribute ( 'd' , `${ prevD }${ pathD . replace ( /^m/ , 'M' ) }` ) ; pathItem . remove ( ) ; } } } resolve ( ) ; } ) ;