@remotion/paths 4.0.528 → 4.0.529

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3297,6 +3297,101 @@ var interpolatePath = (value, firstPath, secondPath) => {
3297
3297
  const commandInterpolator = interpolateInstructions(aCommands, bCommands);
3298
3298
  return serializeInstructions(commandInterpolator(value));
3299
3299
  };
3300
+ // src/interpolate-paths.ts
3301
+ import {
3302
+ interpolate
3303
+ } from "remotion/no-react";
3304
+ var normalizeInterpolatedPath = (path) => {
3305
+ return path.replace(/-?\d*\.?\d+(?:e[+-]?\d+)?/gi, (value) => {
3306
+ const number = Number(value);
3307
+ return Number.isInteger(number) ? value : String(Number(number.toPrecision(15)));
3308
+ });
3309
+ };
3310
+ var interpolatePaths = (input, inputRange, outputRange, options) => {
3311
+ if (!Array.isArray(outputRange)) {
3312
+ throw new TypeError("outputRange must be an array of SVG path strings");
3313
+ }
3314
+ for (const path of outputRange) {
3315
+ if (typeof path !== "string") {
3316
+ throw new TypeError("outputRange must contain only SVG path strings");
3317
+ }
3318
+ }
3319
+ for (const side of ["extrapolateLeft", "extrapolateRight"]) {
3320
+ const extrapolation = options?.[side];
3321
+ if (extrapolation !== undefined && extrapolation !== "extend" && extrapolation !== "clamp" && extrapolation !== "wrap") {
3322
+ throw new TypeError(`${side} must be "extend", "clamp", or "wrap" for interpolatePaths()`);
3323
+ }
3324
+ }
3325
+ const weights = interpolate(input, inputRange, outputRange.map((_, pathIndex) => outputRange.map((__, index) => index === pathIndex ? 1 : 0)), options);
3326
+ const exactPathIndex = weights.findIndex((weight, index) => weight === 1 && weights.every((otherWeight, otherIndex) => otherIndex === index ? true : otherWeight === 0));
3327
+ if (exactPathIndex !== -1) {
3328
+ return outputRange[exactPathIndex];
3329
+ }
3330
+ const contributingPaths = outputRange.map((path, index) => ({ path, weight: weights[index] })).filter(({ weight }) => weight !== 0);
3331
+ const paths = contributingPaths.map(({ path }) => {
3332
+ const commands = reduceInstructions(parsePath(path));
3333
+ if (commands.length === 0) {
3334
+ throw new TypeError(`SVG Path "${path}" is not valid`);
3335
+ }
3336
+ return commands;
3337
+ });
3338
+ const closed = paths.every((commands) => commands[commands.length - 1].type === "Z");
3339
+ const openPaths = paths.map((commands) => commands[commands.length - 1].type === "Z" ? commands.slice(0, -1) : commands);
3340
+ const reference = openPaths.reduce((longest, commands) => commands.length > longest.length ? commands : longest);
3341
+ const extendedPaths = openPaths.map((commands) => commands.length < reference.length ? extendInstruction(commands, reference) : commands);
3342
+ const result = reference.map((_, instructionIndex) => {
3343
+ const commands = extendedPaths.map((path) => path[instructionIndex]);
3344
+ if (commands.some((command) => command.type === "Z")) {
3345
+ if (commands.every((command) => command.type === "Z")) {
3346
+ return { type: "Z" };
3347
+ }
3348
+ throw new TypeError("Cannot interpolate SVG paths with different subpath structures");
3349
+ }
3350
+ const target = commands.find((command) => command.type === "C") ?? commands[0];
3351
+ const normalized = commands.map((command, pathIndex) => {
3352
+ if (command.type === "M" !== (target.type === "M")) {
3353
+ throw new TypeError("Cannot interpolate SVG paths with different subpath structures");
3354
+ }
3355
+ return convertToSameInstructionType(command, target, getEndPosition(extendedPaths[pathIndex].slice(0, instructionIndex)));
3356
+ });
3357
+ const coordinates = normalized.map((command) => {
3358
+ if (command.type === "Z") {
3359
+ return [];
3360
+ }
3361
+ return command.type === "C" ? [
3362
+ command.x,
3363
+ command.y,
3364
+ command.cp1x,
3365
+ command.cp1y,
3366
+ command.cp2x,
3367
+ command.cp2y
3368
+ ] : [command.x, command.y];
3369
+ });
3370
+ const values = coordinates[0].map((base, coordinateIndex) => {
3371
+ const interpolated = coordinates.reduce((value, path, pathIndex) => value + (path[coordinateIndex] - base) * contributingPaths[pathIndex].weight, base);
3372
+ if (Number.isFinite(interpolated)) {
3373
+ return interpolated;
3374
+ }
3375
+ return coordinates.reduce((sum, path, pathIndex) => sum + path[coordinateIndex] * contributingPaths[pathIndex].weight, 0);
3376
+ });
3377
+ if (target.type === "C") {
3378
+ return {
3379
+ type: "C",
3380
+ x: values[0],
3381
+ y: values[1],
3382
+ cp1x: values[2],
3383
+ cp1y: values[3],
3384
+ cp2x: values[4],
3385
+ cp2y: values[5]
3386
+ };
3387
+ }
3388
+ return { type: target.type, x: values[0], y: values[1] };
3389
+ });
3390
+ if (closed) {
3391
+ result.push({ type: "Z" });
3392
+ }
3393
+ return normalizeInterpolatedPath(serializeInstructions(result));
3394
+ };
3300
3395
  // src/reset-path.ts
3301
3396
  var resetPath = (d) => {
3302
3397
  const box = getBoundingBox(d);
@@ -3757,6 +3852,7 @@ export {
3757
3852
  reduceInstructions,
3758
3853
  parsePath,
3759
3854
  normalizePath,
3855
+ interpolatePaths,
3760
3856
  interpolatePath,
3761
3857
  getTangentAtLength,
3762
3858
  getSubpaths,
package/dist/index.d.ts CHANGED
@@ -10,6 +10,7 @@ export { getSubpaths } from './get-subpaths';
10
10
  export { getTangentAtLength } from './get-tangent-at-length';
11
11
  export { AbsoluteInstruction, BoundingBox, Instruction, Part, ReducedInstruction, } from './helpers/types';
12
12
  export { interpolatePath } from './interpolate-path/interpolate-path';
13
+ export { interpolatePaths, type InterpolatePathsOptions, } from './interpolate-paths';
13
14
  export { normalizePath } from './normalize-path';
14
15
  export { parsePath } from './parse-path';
15
16
  export { reduceInstructions } from './reduce-instructions';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PathInternals = exports.warpPath = exports.translatePath = exports.serializeInstructions = exports.scalePath = exports.reversePath = exports.resetPath = exports.reduceInstructions = exports.parsePath = exports.normalizePath = exports.interpolatePath = exports.getTangentAtLength = exports.getSubpaths = exports.getPointAtLength = exports.getLength = exports.getInstructionIndexAtLength = exports.getBoundingBox = exports.extendViewBox = exports.evolvePath = exports.cutPath = exports.centerPath = void 0;
3
+ exports.PathInternals = exports.warpPath = exports.translatePath = exports.serializeInstructions = exports.scalePath = exports.reversePath = exports.resetPath = exports.reduceInstructions = exports.parsePath = exports.normalizePath = exports.interpolatePaths = exports.interpolatePath = exports.getTangentAtLength = exports.getSubpaths = exports.getPointAtLength = exports.getLength = exports.getInstructionIndexAtLength = exports.getBoundingBox = exports.extendViewBox = exports.evolvePath = exports.cutPath = exports.centerPath = void 0;
4
4
  const cut_path_1 = require("./cut-path");
5
5
  const debug_path_1 = require("./debug-path");
6
6
  const get_bounding_box_1 = require("./get-bounding-box");
@@ -26,6 +26,8 @@ const get_tangent_at_length_1 = require("./get-tangent-at-length");
26
26
  Object.defineProperty(exports, "getTangentAtLength", { enumerable: true, get: function () { return get_tangent_at_length_1.getTangentAtLength; } });
27
27
  const interpolate_path_1 = require("./interpolate-path/interpolate-path");
28
28
  Object.defineProperty(exports, "interpolatePath", { enumerable: true, get: function () { return interpolate_path_1.interpolatePath; } });
29
+ const interpolate_paths_1 = require("./interpolate-paths");
30
+ Object.defineProperty(exports, "interpolatePaths", { enumerable: true, get: function () { return interpolate_paths_1.interpolatePaths; } });
29
31
  const normalize_path_1 = require("./normalize-path");
30
32
  Object.defineProperty(exports, "normalizePath", { enumerable: true, get: function () { return normalize_path_1.normalizePath; } });
31
33
  const parse_path_1 = require("./parse-path");
@@ -0,0 +1,6 @@
1
+ import { type ExtrapolateType, type InterpolateOptions } from 'remotion/no-react';
2
+ export type InterpolatePathsOptions = Pick<InterpolateOptions, 'easing' | 'posterize'> & {
3
+ extrapolateLeft?: Exclude<ExtrapolateType, 'identity'>;
4
+ extrapolateRight?: Exclude<ExtrapolateType, 'identity'>;
5
+ };
6
+ export declare const interpolatePaths: (input: number, inputRange: readonly number[], outputRange: readonly string[], options?: InterpolatePathsOptions | undefined) => string;
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.interpolatePaths = void 0;
4
+ const no_react_1 = require("remotion/no-react");
5
+ const get_end_position_1 = require("./get-end-position");
6
+ const convert_to_same_instruction_type_1 = require("./interpolate-path/convert-to-same-instruction-type");
7
+ const extend_command_1 = require("./interpolate-path/extend-command");
8
+ const parse_path_1 = require("./parse-path");
9
+ const reduce_instructions_1 = require("./reduce-instructions");
10
+ const serialize_instructions_1 = require("./serialize-instructions");
11
+ const normalizeInterpolatedPath = (path) => {
12
+ // Remove floating-point artifacts without rounding small coordinates to zero.
13
+ return path.replace(/-?\d*\.?\d+(?:e[+-]?\d+)?/gi, (value) => {
14
+ const number = Number(value);
15
+ return Number.isInteger(number)
16
+ ? value
17
+ : String(Number(number.toPrecision(15)));
18
+ });
19
+ };
20
+ /*
21
+ * @description Interpolates between multiple SVG paths, with keyframes, easing, and extrapolation.
22
+ * @see [Documentation](https://www.remotion.dev/docs/paths/interpolate-paths)
23
+ */
24
+ const interpolatePaths = (input, inputRange, outputRange, options) => {
25
+ if (!Array.isArray(outputRange)) {
26
+ throw new TypeError('outputRange must be an array of SVG path strings');
27
+ }
28
+ for (const path of outputRange) {
29
+ if (typeof path !== 'string') {
30
+ throw new TypeError('outputRange must contain only SVG path strings');
31
+ }
32
+ }
33
+ for (const side of ['extrapolateLeft', 'extrapolateRight']) {
34
+ const extrapolation = options === null || options === void 0 ? void 0 : options[side];
35
+ if (extrapolation !== undefined &&
36
+ extrapolation !== 'extend' &&
37
+ extrapolation !== 'clamp' &&
38
+ extrapolation !== 'wrap') {
39
+ throw new TypeError(`${side} must be "extend", "clamp", or "wrap" for interpolatePaths()`);
40
+ }
41
+ }
42
+ // Interpolating a basis vector for each path preserves segment selection,
43
+ // easing overshoot, and the contributions of earlier spring segments.
44
+ const weights = (0, no_react_1.interpolate)(input, inputRange, outputRange.map((_, pathIndex) => outputRange.map((__, index) => (index === pathIndex ? 1 : 0))), options);
45
+ const exactPathIndex = weights.findIndex((weight, index) => weight === 1 &&
46
+ weights.every((otherWeight, otherIndex) => otherIndex === index ? true : otherWeight === 0));
47
+ if (exactPathIndex !== -1) {
48
+ return outputRange[exactPathIndex];
49
+ }
50
+ const contributingPaths = outputRange
51
+ .map((path, index) => ({ path, weight: weights[index] }))
52
+ .filter(({ weight }) => weight !== 0);
53
+ const paths = contributingPaths.map(({ path }) => {
54
+ const commands = (0, reduce_instructions_1.reduceInstructions)((0, parse_path_1.parsePath)(path));
55
+ if (commands.length === 0) {
56
+ throw new TypeError(`SVG Path "${path}" is not valid`);
57
+ }
58
+ return commands;
59
+ });
60
+ const closed = paths.every((commands) => commands[commands.length - 1].type === 'Z');
61
+ const openPaths = paths.map((commands) => commands[commands.length - 1].type === 'Z'
62
+ ? commands.slice(0, -1)
63
+ : commands);
64
+ const reference = openPaths.reduce((longest, commands) => commands.length > longest.length ? commands : longest);
65
+ const extendedPaths = openPaths.map((commands) => commands.length < reference.length
66
+ ? (0, extend_command_1.extendInstruction)(commands, reference)
67
+ : commands);
68
+ const result = reference.map((_, instructionIndex) => {
69
+ var _a;
70
+ const commands = extendedPaths.map((path) => path[instructionIndex]);
71
+ if (commands.some((command) => command.type === 'Z')) {
72
+ if (commands.every((command) => command.type === 'Z')) {
73
+ return { type: 'Z' };
74
+ }
75
+ throw new TypeError('Cannot interpolate SVG paths with different subpath structures');
76
+ }
77
+ // A common cubic command preserves every curve while allowing paths with
78
+ // different instruction types to share the same coordinate interpolation.
79
+ const target = (_a = commands.find((command) => command.type === 'C')) !== null && _a !== void 0 ? _a : commands[0];
80
+ const normalized = commands.map((command, pathIndex) => {
81
+ if ((command.type === 'M') !== (target.type === 'M')) {
82
+ throw new TypeError('Cannot interpolate SVG paths with different subpath structures');
83
+ }
84
+ return (0, convert_to_same_instruction_type_1.convertToSameInstructionType)(command, target, (0, get_end_position_1.getEndPosition)(extendedPaths[pathIndex].slice(0, instructionIndex)));
85
+ });
86
+ const coordinates = normalized.map((command) => {
87
+ if (command.type === 'Z') {
88
+ return [];
89
+ }
90
+ return command.type === 'C'
91
+ ? [
92
+ command.x,
93
+ command.y,
94
+ command.cp1x,
95
+ command.cp1y,
96
+ command.cp2x,
97
+ command.cp2y,
98
+ ]
99
+ : [command.x, command.y];
100
+ });
101
+ const values = coordinates[0].map((base, coordinateIndex) => {
102
+ const interpolated = coordinates.reduce((value, path, pathIndex) => value +
103
+ (path[coordinateIndex] - base) * contributingPaths[pathIndex].weight, base);
104
+ if (Number.isFinite(interpolated)) {
105
+ return interpolated;
106
+ }
107
+ // Opposite large coordinates can overflow when subtracting the base.
108
+ return coordinates.reduce((sum, path, pathIndex) => sum + path[coordinateIndex] * contributingPaths[pathIndex].weight, 0);
109
+ });
110
+ if (target.type === 'C') {
111
+ return {
112
+ type: 'C',
113
+ x: values[0],
114
+ y: values[1],
115
+ cp1x: values[2],
116
+ cp1y: values[3],
117
+ cp2x: values[4],
118
+ cp2y: values[5],
119
+ };
120
+ }
121
+ return { type: target.type, x: values[0], y: values[1] };
122
+ });
123
+ if (closed) {
124
+ result.push({ type: 'Z' });
125
+ }
126
+ return normalizeInterpolatedPath((0, serialize_instructions_1.serializeInstructions)(result));
127
+ };
128
+ exports.interpolatePaths = interpolatePaths;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "url": "https://github.com/remotion-dev/remotion/tree/main/packages/paths"
4
4
  },
5
5
  "name": "@remotion/paths",
6
- "version": "4.0.528",
6
+ "version": "4.0.529",
7
7
  "description": "Utilities for working with SVG paths",
8
8
  "main": "dist/index.js",
9
9
  "types": "dist/index.d.ts",
@@ -26,10 +26,13 @@
26
26
  "utilities"
27
27
  ],
28
28
  "devDependencies": {
29
- "@remotion/eslint-config-internal": "4.0.528",
29
+ "@remotion/eslint-config-internal": "4.0.529",
30
30
  "eslint": "9.19.0",
31
31
  "@typescript/native-preview": "7.0.0-dev.20260217.1"
32
32
  },
33
+ "dependencies": {
34
+ "remotion": "4.0.529"
35
+ },
33
36
  "exports": {
34
37
  "./package.json": "./package.json",
35
38
  ".": {