UNPKG

@remotion/paths

Version:

Utilities for working with SVG paths

166 lines (165 loc) 5.53 kB
"use strict"; 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;