function sortIx( a, b ) { return b.parameter - a.parameter; } function splitPath( _ixs, other ) { other = other || false; var i, j, k, l, len, ixs, ix, path, crv, vals; var ixPoint, nuSeg; var paths = [], lastPathId = null; for (i = 0, l = _ixs.length; i < l; i++) { ix = ( other )? _ixs[i]._ixPair : _ixs[i]; if( ix.path.id !== lastPathId ){ paths.push( ix.path ); lastPathId = ix.path.id; } if( !ix.curve._ixParams ){ix.curve._ixParams = []; } ix.curve._ixParams.push( { parameter: ix.parameter, pair: ix._ixPair } ); } for (k = 0, len = paths.length; k < len; k++) { path = paths[k]; var lastNode = path.lastSegment, firstNode = path.firstSegment; var nextNode = null, left = null, right = null, parts = null, isLinear; var handleIn, handleOut; while( nextNode !== firstNode){ nextNode = ( nextNode )? nextNode.previous: lastNode; if( nextNode.curve._ixParams ){ ixs = nextNode.curve._ixParams; ixs.sort( sortIx ); crv = nextNode.curve; isLinear = crv.isLinear(); crv = vals = null; for (i = 0, l = ixs.length; i < l; i++) { ix = ixs[i]; crv = nextNode.curve; if( !vals ) vals = crv.getValues(); parts = Curve.subdivide( vals, ix.parameter ); left = parts[0]; right = parts[1]; handleIn = handleOut = null; if( !isLinear ){ crv.segment1.handleOut = new Point( left[2] - left[0], left[3] - left[1] ); crv.segment2.handleIn = new Point( right[4] - right[6], right[5] - right[7] ); handleIn = new Point( left[4] - ixPoint.x, left[5] - ixPoint.y ); handleOut = new Point( right[2] - ixPoint.x, right[3] - ixPoint.y ); } ixPoint = new Point( right[0], right[1] ); nuSeg = new Segment( ixPoint, handleIn, handleOut ); nuSeg._ixPair = ix.pair; nuSeg._ixPair._segment = nuSeg; path.insert( nextNode.index + 1, nuSeg ); // nextNode = nuSeg; for (j = i + 1; j < l; j++) { ixs[j].parameter = ixs[j].parameter / ix.parameter; } vals = left; } } } } } /** * To deal with a HTML canvas requirement where CompoundPaths' child contours * has to be of different winding direction for correctly filling holes. * But if some individual countours are disjoint, i.e. islands, we have to * reorient them so that * the holes have opposit winding direction ( already handled by paperjs ) * islands has to have same winding direction ( as the first child of the path ) * * Does NOT handle selfIntersecting CompoundPaths. * * @param {CompoundPath} path - Input CompoundPath, Note: This path could be modified if need be. * @return {boolean} the winding direction of the base contour( true if clockwise ) */ function reorientCompoundPath( path ){ if( !(path instanceof CompoundPath) ){ return path.clockwise; } var children = path.children, len = children.length, baseWinding; var bounds = new Array( len ); var tmparray = new Array( len ); baseWinding = children[0].clockwise; // Omit the first path for (i = 0; i < len; i++) { bounds[i] = children[i].bounds; tmparray[i] = 0; } for (i = 0; i < len; i++) { var p1 = children[i]; for (j = 0; j < len; j++) { var p2 = children[j]; if( i !== j && bounds[i].contains( bounds[j] ) ){ tmparray[j]++; } } } for (i = 1; i < len; i++) { if ( tmparray[i] % 2 === 0 ) { children[i].clockwise = baseWinding; } } return baseWinding; } function computeBoolean( _path1, _path2, operator ){ var path1Clockwise = reorientCompoundPath( _path1 ); var path2Clockwise = reorientCompoundPath( _path2 ); var ixs = _path1.getIntersections( _path2 ); var path1Id = _path1.id; var path2Id = _path2.id; splitPath( ixs ); splitPath( ixs, true ); var i, j, len, path, crv; var paths; if( _path1 instanceof CompoundPath ){ paths = _path1.children; } else { paths = [ _path1 ]; } if( _path2 instanceof CompoundPath ){ paths = paths.concat( _path2.children ); } else { paths.push( _path2 ); } // step 1: discard invalid links according to the boolean operator for (i = 0, len = paths.length; i < len; i++) { path = paths[i]; var thisId = ( path.parent instanceof CompoundPath )? path.parent.id : path.id; var thisWinding = path.clockwise; var lastNode = path.lastSegment, firstNode = path.firstSegment; var nextNode = null, midPoint, insidePath1, insidePath2; while( nextNode !== firstNode){ nextNode = ( nextNode )? nextNode.previous: lastNode; crv = nextNode.curve; midPoint = crv.getPoint( 0.5 ); if( thisId !== path1Id ){ contains = _path1.contains( midPoint ); insidePath1 = (thisWinding === path1Clockwise)? contains : contains && !testOnCurve( _path1, midPoint ); } if( thisId !== path2Id ){ contains = _path2.contains( midPoint ); insidePath2 = (thisWinding === path2Clockwise)? contains : contains && !testOnCurve( _path2, midPoint ); } if( !operator( thisId === path1Id, insidePath1, insidePath2 ) ){ crv._INVALID = true; } } } } function testOnCurve( path, point ){ var res = 0; var crv = path.getCurves(); var i = 0; var bounds = path._bounds; if( bounds && bounds.contains( point ) ){ for( i = 0; i < crv.length && !res; i++ ){ var crvi = crv[i]; if( crvi.bounds.contains( point ) && crvi.getParameterOf( point ) ){ res = 1; } } } return res; } function unite( path1, path2 ){ var unionOp = function( isPath1, isInsidePath1, isInsidePath2 ){ return ( isInsidePath1 || isInsidePath2 )? false : true; }; return computeBoolean( path1, path2, unionOp ); }