function decasteljau(points, t) { const left = []; const right = []; function recurse(points, t) { if (points.length === 1) { left.push(points[0]); right.push(points[0]); } else { const middlePoints = []; for (let i = 0; i < points.length - 1; i++) { if (i === 0) { left.push(points[0]); } if (i === points.length - 2) { right.push(points[i + 1]); } middlePoints[i] = [ (1 - t) * points[i][0] + t * points[i + 1][0], (1 - t) * points[i][1] + t * points[i + 1][1] ]; } recurse(middlePoints, t); } } if (points.length) { recurse(points, t); } return { left, right: right.reverse() }; } function splitCurve(start, end, count) { const points = [ [ start[1], start[2] ] ]; count = count || 2; const segments = []; if (end[0] === 'A') { points.push(end[6]); points.push(end[7]); } else if (end[0] === 'C') { points.push([ end[1], end[2] ]); points.push([ end[3], end[4] ]); points.push([ end[5], end[6] ]); } else if (end[0] === 'S' || end[0] === 'Q') { points.push([ end[1], end[2] ]); points.push([ end[3], end[4] ]); } else { points.push([ end[1], end[2] ]); } let leftSegments = points; const t = 1 / count; for (let i = 0; i < count - 1; i++) { const rt = t / (1 - t * i); const split = decasteljau(leftSegments, rt); segments.push(split.left); leftSegments = split.right; } segments.push(leftSegments); const result = segments.map((segment) => { let cmd = []; if (segment.length === 4) { cmd.push('C'); cmd = cmd.concat(segment[2]); } if (segment.length >= 3) { if (segment.length === 3) { cmd.push('Q'); } cmd = cmd.concat(segment[1]); } if (segment.length === 2) { cmd.push('L'); } cmd = cmd.concat(segment[segment.length - 1]); return cmd; }); return result; } function splitSegment(start, end, count) { if (count === 1) { return [ [].concat(start) ]; } let segments = []; if (end[0] === 'L' || end[0] === 'C' || end[0] === 'Q') { segments = segments.concat(splitCurve(start, end, count)); } else { const temp = [].concat(start); if (temp[0] === 'M') { temp[0] = 'L'; } for (let i = 0; i <= count - 1; i++) { segments.push(temp); } } return segments; } export default function fillPath(source, target) { if (source.length === 1) { return source; } const sourceLen = source.length - 1; const targetLen = target.length - 1; const ratio = sourceLen / targetLen; const segmentsToFill = []; if (source.length === 1 && source[0][0] === 'M') { for (let i = 0; i < targetLen - sourceLen; i++) { source.push(source[0]); } return source; } for (let i = 0; i < targetLen; i++) { const index = Math.floor(ratio * i); segmentsToFill[index] = (segmentsToFill[index] || 0) + 1; } const filled = segmentsToFill.reduce((filled, count, i) => { if (i === sourceLen) { return filled.concat(source[sourceLen]); } return filled.concat(splitSegment(source[i], source[i + 1], count)); }, []); filled.unshift(source[0]); if (target[targetLen] === 'Z' || target[targetLen] === 'z') { filled.push('Z'); } return filled; }