@remotion/paths
Version:
Utilities for working with SVG paths
209 lines (208 loc) • 6.34 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.fixZInstruction = exports.warpTransform = void 0;
exports.svgPathInterpolate = svgPathInterpolate;
const convert_q_to_c_instruction_1 = require("../helpers/convert-q-to-c-instruction");
const euclideanDistance = (points) => {
const startPoint = points[0];
const endPoint = points[points.length - 1];
let d2 = 0;
for (let i = 0; i < startPoint.length; i++) {
const d = endPoint[i] - startPoint[i];
d2 += d ** 2;
}
return Math.sqrt(d2);
};
function split(p, t = 0.5) {
const seg0 = [];
const seg1 = [];
const orders = [p];
while (orders.length < p.length) {
const q = orders[orders.length - 1];
const r = [];
for (let i = 1; i < q.length; i++) {
const q0 = q[i - 1];
const q1 = q[i];
const s = [];
const dim = Math.max(q0.length, q1.length);
for (let j = 0; j < dim; j++) {
const s0 = q0[j] || 0;
const s1 = q1[j] || 0;
s.push(s0 + (s1 - s0) * t);
}
r.push(s);
}
orders.push(r);
}
for (let i = 0; i < orders.length; i++) {
seg0.push(orders[i][0]);
seg1.push(orders[orders.length - 1 - i][i]);
}
return [seg0, seg1];
}
function interpolateUntil(points, threshold, deltaFunction = euclideanDistance) {
const stack = [points];
const segments = [];
while (stack.length > 0) {
const currentPoints = stack.pop();
if (deltaFunction(currentPoints) > threshold) {
const newPoints = split(currentPoints);
// Add new segments backwards so they end up in correct order
for (let i = newPoints.length - 1; i >= 0; i--) {
stack.push(newPoints[i]);
}
}
else {
segments.push(currentPoints);
}
}
return segments;
}
function createLineSegment(points) {
switch (points.length) {
case 2:
return {
type: 'L',
x: points[1][0],
y: points[1][1],
};
case 3:
return (0, convert_q_to_c_instruction_1.convertQToCInstruction)({
type: 'Q',
cpx: points[1][0],
cpy: points[1][1],
x: points[2][0],
y: points[2][1],
}, {
x: points[0][0],
y: points[0][1],
});
case 4:
return {
type: 'C',
cp1x: points[1][0],
cp1y: points[1][1],
cp2x: points[2][0],
cp2y: points[2][1],
x: points[3][0],
y: points[3][1],
};
default:
throw new Error('Expected 2, 3 or 4 points for a line segment, got ' + points.length);
}
}
function warpInterpolate(path, threshold, deltaFunction) {
let prexX = 0;
let prexY = 0;
return path
.map((segment) => {
const points = [[prexX, prexY]];
if (segment.type !== 'Z') {
prexX = segment.x;
prexY = segment.y;
}
if (segment.type === 'C') {
points.push([segment.cp1x, segment.cp1y]);
points.push([segment.cp2x, segment.cp2y]);
points.push([segment.x, segment.y]);
}
if (segment.type === 'L') {
points.push([segment.x, segment.y]);
}
if (segment.type === 'C' || segment.type === 'L') {
return interpolateUntil(points, threshold, deltaFunction).map((rawSegment) => createLineSegment(rawSegment));
}
return [segment];
})
.flat(1);
}
function svgPathInterpolate(path, threshold) {
let didWork = false;
const deltaFunction = (points) => {
const linearPoints = [
points[0].slice(0, 2),
points[points.length - 1].slice(0, 2),
];
const delta = euclideanDistance(linearPoints);
didWork = didWork || delta > threshold;
return delta;
};
return warpInterpolate(path, threshold, deltaFunction);
}
const warpTransform = (path, transformer) => {
return path
.map((segment) => {
if (segment.type === 'L') {
const { x, y } = transformer({ x: segment.x, y: segment.y });
return [
{
type: 'L',
x,
y,
},
];
}
if (segment.type === 'C') {
const { x, y } = transformer({ x: segment.x, y: segment.y });
const { x: cp1x, y: cp1y } = transformer({
x: segment.cp1x,
y: segment.cp1y,
});
const { x: cp2x, y: cp2y } = transformer({
x: segment.cp2x,
y: segment.cp2y,
});
return [
{
type: 'C',
x,
y,
cp1x,
cp1y,
cp2x,
cp2y,
},
];
}
if (segment.type === 'M') {
const { x, y } = transformer({ x: segment.x, y: segment.y });
return [
{
type: 'M',
x,
y,
},
];
}
return [segment];
})
.flat(1);
};
exports.warpTransform = warpTransform;
// Add a line from second to last point to last point and then keep Z so it can be transformed as well
const fixZInstruction = (instructions) => {
let prevX = 0;
let prevY = 0;
return instructions
.map((instruction) => {
if (instruction.type === 'Z') {
return [
{
type: 'L',
x: prevX,
y: prevY,
},
{
type: 'Z',
},
];
}
if (instruction.type === 'M') {
prevX = instruction.x;
prevY = instruction.y;
}
return [instruction];
})
.flat(1);
};
exports.fixZInstruction = fixZInstruction;