@remotion/paths 4.0.527 → 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.
@@ -1144,9 +1144,31 @@ var makeInstructions = (arr, instruction, cb) => {
1144
1144
  };
1145
1145
  var segmentRegExp = /([astvzqmhlc])([^astvzqmhlc]*)/gi;
1146
1146
  var numberRegExp = /-?[0-9]*\.?[0-9]+(?:e[-+]?\d+)?/gi;
1147
+ var arcNumberRegExp = /[\s,]*([-+]?(?:[0-9]+\.?[0-9]*|\.[0-9]+)(?:e[-+]?\d+)?)/iy;
1148
+ var arcFlagRegExp = /[\s,]*([01])/y;
1147
1149
  var parseValues = (args, instructionType) => {
1148
- const numbers = args.match(numberRegExp);
1149
- if (!numbers) {
1150
+ let numbers;
1151
+ if (instructionType === "A" || instructionType === "a") {
1152
+ numbers = [];
1153
+ let match;
1154
+ let lastIndex = 0;
1155
+ do {
1156
+ const regExp = numbers.length % 7 === 3 || numbers.length % 7 === 4 ? arcFlagRegExp : arcNumberRegExp;
1157
+ regExp.lastIndex = lastIndex;
1158
+ match = regExp.exec(args);
1159
+ if (match) {
1160
+ numbers.push(match[1]);
1161
+ lastIndex = regExp.lastIndex;
1162
+ }
1163
+ } while (match);
1164
+ const rest = args.slice(lastIndex);
1165
+ if (!/^[\s,]*$/.test(rest)) {
1166
+ throw new Error(`Malformed path data: ${instructionType} has an invalid value at "${rest.trim()}"`);
1167
+ }
1168
+ } else {
1169
+ numbers = args.match(numberRegExp);
1170
+ }
1171
+ if (!numbers || numbers.length === 0) {
1150
1172
  if (instructionType === "Z" || instructionType === "z") {
1151
1173
  return [];
1152
1174
  }
@@ -1788,8 +1810,8 @@ var normalizeInstructions = (instructions) => {
1788
1810
  moveX = instruction.x;
1789
1811
  moveY = instruction.y;
1790
1812
  } else if (instruction.type === "m") {
1791
- moveX += instruction.dx;
1792
- moveY += instruction.dy;
1813
+ moveX = x + instruction.dx;
1814
+ moveY = y + instruction.dy;
1793
1815
  }
1794
1816
  if (instruction.type === "A" || instruction.type === "C" || instruction.type === "L" || instruction.type === "M" || instruction.type === "Q" || instruction.type === "S" || instruction.type === "T") {
1795
1817
  normalized.push(instruction);
@@ -3275,6 +3297,101 @@ var interpolatePath = (value, firstPath, secondPath) => {
3275
3297
  const commandInterpolator = interpolateInstructions(aCommands, bCommands);
3276
3298
  return serializeInstructions(commandInterpolator(value));
3277
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
+ };
3278
3395
  // src/reset-path.ts
3279
3396
  var resetPath = (d) => {
3280
3397
  const box = getBoundingBox(d);
@@ -3735,6 +3852,7 @@ export {
3735
3852
  reduceInstructions,
3736
3853
  parsePath,
3737
3854
  normalizePath,
3855
+ interpolatePaths,
3738
3856
  interpolatePath,
3739
3857
  getTangentAtLength,
3740
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;
@@ -17,8 +17,8 @@ const normalizeInstructions = (instructions) => {
17
17
  moveY = instruction.y;
18
18
  }
19
19
  else if (instruction.type === 'm') {
20
- moveX += instruction.dx;
21
- moveY += instruction.dy;
20
+ moveX = x + instruction.dx;
21
+ moveY = y + instruction.dy;
22
22
  }
23
23
  if (instruction.type === 'A' ||
24
24
  instruction.type === 'C' ||
@@ -42,9 +42,36 @@ const makeInstructions = (arr, instruction, cb) => {
42
42
  };
43
43
  const segmentRegExp = /([astvzqmhlc])([^astvzqmhlc]*)/gi;
44
44
  const numberRegExp = /-?[0-9]*\.?[0-9]+(?:e[-+]?\d+)?/gi;
45
+ const arcNumberRegExp = /[\s,]*([-+]?(?:[0-9]+\.?[0-9]*|\.[0-9]+)(?:e[-+]?\d+)?)/iy;
46
+ const arcFlagRegExp = /[\s,]*([01])/y;
45
47
  const parseValues = (args, instructionType) => {
46
- const numbers = args.match(numberRegExp);
47
- if (!numbers) {
48
+ let numbers;
49
+ if (instructionType === 'A' || instructionType === 'a') {
50
+ // Arc flags are a single "0" or "1" and don't need a separator,
51
+ // e.g. "A1 1 0 011 1" (https://www.w3.org/TR/SVG2/paths.html#PathDataBNF)
52
+ numbers = [];
53
+ let match;
54
+ let lastIndex = 0;
55
+ do {
56
+ const regExp = numbers.length % 7 === 3 || numbers.length % 7 === 4
57
+ ? arcFlagRegExp
58
+ : arcNumberRegExp;
59
+ regExp.lastIndex = lastIndex;
60
+ match = regExp.exec(args);
61
+ if (match) {
62
+ numbers.push(match[1]);
63
+ lastIndex = regExp.lastIndex;
64
+ }
65
+ } while (match);
66
+ const rest = args.slice(lastIndex);
67
+ if (!/^[\s,]*$/.test(rest)) {
68
+ throw new Error(`Malformed path data: ${instructionType} has an invalid value at "${rest.trim()}"`);
69
+ }
70
+ }
71
+ else {
72
+ numbers = args.match(numberRegExp);
73
+ }
74
+ if (!numbers || numbers.length === 0) {
48
75
  if (instructionType === 'Z' || instructionType === 'z') {
49
76
  return [];
50
77
  }
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.527",
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.527",
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
  ".": {