@antv/path-util
Version:
A common util collection for antv projects
127 lines (120 loc) • 3.36 kB
text/typescript
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;
}