@remotion/paths
Version:
Utilities for working with SVG paths
166 lines (165 loc) • 5.53 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getBoundingBox = exports.getBoundingBoxFromInstructions = void 0;
const remove_a_s_t_curves_1 = require("./helpers/remove-a-s-t-curves");
const normalize_path_1 = require("./normalize-path");
const parse_path_1 = require("./parse-path");
// Precision for consider cubic polynom as quadratic one
const CBEZIER_MINMAX_EPSILON = 0.00000001;
// https://github.com/kpym/SVGPathy/blob/acd1a50c626b36d81969f6e98e8602e128ba4302/lib/box.js#L89
function minmaxQ(A) {
const min = Math.min(A[0], A[2]);
const max = Math.max(A[0], A[2]);
if (A[1] > A[0] ? A[2] >= A[1] : A[2] <= A[1]) {
// if no extremum in ]0,1[
return [min, max];
}
// check if the extremum E is min or max
const E = (A[0] * A[2] - A[1] * A[1]) / (A[0] - 2 * A[1] + A[2]);
return E < min ? [E, max] : [min, E];
}
// https://github.com/kpym/SVGPathy/blob/acd1a50c626b36d81969f6e98e8602e128ba4302/lib/box.js#L127
function minmaxC(A) {
const K = A[0] - 3 * A[1] + 3 * A[2] - A[3];
// if the polynomial is (almost) quadratic and not cubic
if (Math.abs(K) < CBEZIER_MINMAX_EPSILON) {
if (A[0] === A[3] && A[0] === A[1]) {
// no curve, point targeting same location
return [A[0], A[3]];
}
return minmaxQ([
A[0],
-0.5 * A[0] + 1.5 * A[1],
A[0] - 3 * A[1] + 3 * A[2],
]);
}
// the reduced discriminant of the derivative
const T = -A[0] * A[2] +
A[0] * A[3] -
A[1] * A[2] -
A[1] * A[3] +
A[1] * A[1] +
A[2] * A[2];
// if the polynomial is monotone in [0,1]
if (T <= 0) {
return [Math.min(A[0], A[3]), Math.max(A[0], A[3])];
}
const S = Math.sqrt(T);
// potential extrema
let min = Math.min(A[0], A[3]);
let max = Math.max(A[0], A[3]);
const L = A[0] - 2 * A[1] + A[2];
// check local extrema
for (let R = (L + S) / K, i = 1; i <= 2; R = (L - S) / K, i++) {
if (R > 0 && R < 1) {
// if the extrema is for R in [0,1]
const Q = A[0] * (1 - R) * (1 - R) * (1 - R) +
A[1] * 3 * (1 - R) * (1 - R) * R +
A[2] * 3 * (1 - R) * R * R +
A[3] * R * R * R;
if (Q < min) {
min = Q;
}
if (Q > max) {
max = Q;
}
}
}
return [min, max];
}
const getBoundingBoxFromInstructions = (instructions) => {
let minX = Infinity;
let minY = Infinity;
let maxX = -Infinity;
let maxY = -Infinity;
let x = 0;
let y = 0;
let lastMoveX = 0;
let lastMoveY = 0;
for (const seg of instructions) {
switch (seg.type) {
case 'M': {
lastMoveX = seg.x;
lastMoveY = seg.y;
if (minX > seg.x) {
minX = seg.x;
}
if (minY > seg.y) {
minY = seg.y;
}
if (maxX < seg.x) {
maxX = seg.x;
}
if (maxY < seg.y) {
maxY = seg.y;
}
x = seg.x;
y = seg.y;
break;
}
case 'L': {
if (minX > seg.x) {
minX = seg.x;
}
if (minY > seg.y) {
minY = seg.y;
}
if (maxX < seg.x) {
maxX = seg.x;
}
if (maxY < seg.y) {
maxY = seg.y;
}
x = seg.x;
y = seg.y;
break;
}
case 'C': {
const cxMinMax = minmaxC([x, seg.cp1x, seg.cp2x, seg.x]);
if (minX > cxMinMax[0]) {
minX = cxMinMax[0];
}
if (maxX < cxMinMax[1]) {
maxX = cxMinMax[1];
}
const cyMinMax = minmaxC([y, seg.cp1y, seg.cp2y, seg.y]);
if (minY > cyMinMax[0]) {
minY = cyMinMax[0];
}
if (maxY < cyMinMax[1]) {
maxY = cyMinMax[1];
}
x = seg.x;
y = seg.y;
break;
}
case 'Z':
x = lastMoveX;
y = lastMoveY;
break;
default:
// @ts-expect-error
throw new Error(`Unknown instruction ${seg.type}`);
}
}
return {
x1: minX,
y1: minY,
x2: maxX,
y2: maxY,
viewBox: `${minX} ${minY} ${maxX - minX} ${maxY - minY}`,
width: maxX - minX,
height: maxY - minY,
};
};
exports.getBoundingBoxFromInstructions = getBoundingBoxFromInstructions;
/*
* @description Returns the bounding box of the given path, suitable for calculating the viewBox value that you need to pass to an SVG.
* @see [Documentation](https://www.remotion.dev/docs/paths/get-bounding-box)
*/
const getBoundingBox = (d) => {
const parsed = (0, parse_path_1.parsePath)(d);
const unarced = (0, remove_a_s_t_curves_1.removeATSHVQInstructions)((0, normalize_path_1.normalizeInstructions)(parsed));
return (0, exports.getBoundingBoxFromInstructions)(unarced);
};
exports.getBoundingBox = getBoundingBox;