@remotion/shapes
Version:
Generate SVG shapes
162 lines (161 loc) • 6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeArrow = void 0;
const paths_1 = require("@remotion/paths");
const unitDir = (from, to) => {
const dx = to[0] - from[0];
const dy = to[1] - from[1];
const len = Math.sqrt(dx * dx + dy * dy);
return len === 0 ? [0, 0] : [dx / len, dy / len];
};
const buildArrowPath = (points, roundedIndices, cornerRadius) => {
const n = points.length;
if (cornerRadius === 0) {
return [
{ type: 'M', x: points[0][0], y: points[0][1] },
...points
.slice(1)
.map(([x, y]) => ({ type: 'L', x, y })),
{ type: 'Z' },
];
}
// Corner 0 is always rounded — start at its depart point
const d0 = unitDir(points[0], points[1]);
const startX = points[0][0] + d0[0] * cornerRadius;
const startY = points[0][1] + d0[1] * cornerRadius;
const instrs = [{ type: 'M', x: startX, y: startY }];
for (let i = 1; i < n; i++) {
const curr = points[i];
if (roundedIndices.has(i)) {
const prev = points[i - 1];
const next = points[(i + 1) % n];
const dIn = unitDir(prev, curr);
const dOut = unitDir(curr, next);
instrs.push({
type: 'L',
x: curr[0] - dIn[0] * cornerRadius,
y: curr[1] - dIn[1] * cornerRadius,
}, {
type: 'C',
cp1x: curr[0],
cp1y: curr[1],
cp2x: curr[0],
cp2y: curr[1],
x: curr[0] + dOut[0] * cornerRadius,
y: curr[1] + dOut[1] * cornerRadius,
});
}
else {
instrs.push({ type: 'L', x: curr[0], y: curr[1] });
}
}
// Close back to corner 0 (rounded)
const dIn0 = unitDir(points[n - 1], points[0]);
instrs.push({
type: 'L',
x: points[0][0] - dIn0[0] * cornerRadius,
y: points[0][1] - dIn0[1] * cornerRadius,
}, {
type: 'C',
cp1x: points[0][0],
cp1y: points[0][1],
cp2x: points[0][0],
cp2y: points[0][1],
x: startX,
y: startY,
}, { type: 'Z' });
return instrs;
};
/**
* @description Generates an SVG path for an arrow shape.
* @param {Number} length The total length of the arrow along its direction axis. Default 300.
* @param {Number} headWidth The width of the arrowhead at its widest point. Default 185.
* @param {Number} headLength The length of the arrowhead portion. Default 120.
* @param {Number} shaftWidth The width of the arrow shaft. Default 80.
* @param {string} direction The direction the arrow points. Default 'right'.
* @param {Number} cornerRadius Rounds the corner using an arc. Similar to CSS's border-radius.
* @see [Documentation](https://www.remotion.dev/docs/shapes/make-arrow)
*/
const makeArrow = ({ length = 300, headWidth = 185, headLength = 120, shaftWidth = 80, direction = 'right', cornerRadius = 0, }) => {
if (length <= 0 || headWidth <= 0 || headLength <= 0 || shaftWidth <= 0) {
throw new Error('All dimension parameters ("length", "headWidth", "headLength", "shaftWidth") must be positive numbers');
}
if (headWidth < shaftWidth) {
throw new Error(`"headWidth" must be greater than or equal to "shaftWidth", got headWidth=${headWidth} and shaftWidth=${shaftWidth}`);
}
if (headLength > length) {
throw new Error(`"headLength" must be less than or equal to "length", got headLength=${headLength} and length=${length}`);
}
const shaftTop = (headWidth - shaftWidth) / 2;
const shaftBottom = shaftTop + shaftWidth;
const shaftEnd = length - headLength;
// Points for a right-pointing arrow (clockwise from top-left of shaft)
const rightPoints = [
[0, shaftTop],
[shaftEnd, shaftTop],
[shaftEnd, 0],
[length, headWidth / 2],
[shaftEnd, headWidth],
[shaftEnd, shaftBottom],
[0, shaftBottom],
];
let points;
let width;
let height;
if (direction === 'right') {
points = rightPoints;
width = length;
height = headWidth;
}
else if (direction === 'left') {
// Mirror x: x -> length - x
points = rightPoints.map(([x, y]) => [length - x, y]);
width = length;
height = headWidth;
}
else if (direction === 'down') {
// Rotate 90° clockwise: swap axes so the tip points down
points = [
[shaftTop, 0],
[shaftBottom, 0],
[shaftBottom, shaftEnd],
[headWidth, shaftEnd],
[headWidth / 2, length],
[0, shaftEnd],
[shaftTop, shaftEnd],
];
width = headWidth;
height = length;
}
else {
// up: mirror of down (y -> length - y)
points = [
[shaftTop, length],
[shaftBottom, length],
[shaftBottom, headLength],
[headWidth, headLength],
[headWidth / 2, 0],
[0, headLength],
[shaftTop, headLength],
];
width = headWidth;
height = length;
}
// Round the back corners and all 3 arrowhead tips, but not the inner
// shaft-to-head junctions so the shaft edges stay straight.
// right/left: back = 0,6; head tips = 2,3,4; inner junctions (skip) = 1,5
// up/down: back = 0,1; head tips = 3,4,5; inner junctions (skip) = 2,6
const roundedIndices = direction === 'right' || direction === 'left'
? new Set([0, 2, 3, 4, 6])
: new Set([0, 1, 3, 4, 5]);
const instructions = buildArrowPath(points, roundedIndices, cornerRadius);
const path = (0, paths_1.serializeInstructions)(instructions);
return {
path,
instructions,
width,
height,
transformOrigin: `${width / 2} ${height / 2}`,
};
};
exports.makeArrow = makeArrow;