UNPKG

remotion

Version:

Make videos programmatically

798 lines (797 loc) • 30.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.assertValidInterpolateEasingOption = assertValidInterpolateEasingOption; exports.assertValidInterpolatePosterizeOption = assertValidInterpolatePosterizeOption; exports.interpolate = interpolate; const easing_js_1 = require("./easing.js"); const normalize_number_js_1 = require("./normalize-number.js"); const angleUnits = new Set(['deg', 'rad', 'grad', 'turn']); const lengthUnits = new Set([ '%', 'cap', 'ch', 'cm', 'cqb', 'cqh', 'cqi', 'cqmax', 'cqmin', 'cqw', 'dvh', 'dvw', 'em', 'ex', 'ic', 'in', 'lh', 'lvh', 'lvw', 'mm', 'pc', 'pt', 'px', 'q', 'rem', 'rlh', 'svh', 'svw', 'vb', 'vh', 'vi', 'vmax', 'vmin', 'vw', ]); const cssNumberRegex = /^([+-]?(?:\d+\.?\d*|\.\d+))([a-zA-Z%]+)?$/; const transformOriginKeywords = new Set([ 'left', 'center', 'right', 'top', 'bottom', ]); const transformOriginKeywordOptions = (keyword) => { if (keyword === 'left') { return [{ axis: 'x', value: { value: 0, unit: '%' } }]; } if (keyword === 'right') { return [{ axis: 'x', value: { value: 100, unit: '%' } }]; } if (keyword === 'top') { return [{ axis: 'y', value: { value: 0, unit: '%' } }]; } if (keyword === 'bottom') { return [{ axis: 'y', value: { value: 100, unit: '%' } }]; } return [ { axis: 'x', value: { value: 50, unit: '%' } }, { axis: 'y', value: { value: 50, unit: '%' } }, ]; }; const transformOriginCenter = { value: 50, unit: '%' }; const stringifyNumber = (value) => { return String((0, normalize_number_js_1.normalizeNumber)(value)); }; class UnsupportedStringInterpolationValueError extends TypeError { } const parseStringInterpolationComponent = (component, value) => { var _a; const match = cssNumberRegex.exec(component); if (match === null) { throw new UnsupportedStringInterpolationValueError(`Cannot interpolate "${value}" because "${component}" is not a supported scale, translate, or rotate value`); } const unit = (_a = match[2]) !== null && _a !== void 0 ? _a : null; const numberValue = Number(match[1]); if (!Number.isFinite(numberValue)) { throw new TypeError(`Cannot interpolate "${value}" because "${component}" is not finite`); } if (unit === null) { return { kind: 'scale', value: numberValue, unit: null }; } if (angleUnits.has(unit)) { return { kind: 'rotate', value: numberValue, unit }; } if (lengthUnits.has(unit)) { return { kind: 'translate', value: numberValue, unit }; } throw new TypeError(`Cannot interpolate "${value}" because "${unit}" is not a supported translate or rotate unit`); }; const parseTransformOriginLengthPercentage = ({ component, value, allowPercentage, }) => { var _a; const match = cssNumberRegex.exec(component); if (match === null) { throw new TypeError(`Cannot interpolate "${value}" because "${component}" is not a supported transform-origin ${allowPercentage ? 'length-percentage' : 'z length'}`); } const unit = (_a = match[2]) !== null && _a !== void 0 ? _a : null; const numberValue = Number(match[1]); if (!Number.isFinite(numberValue)) { throw new TypeError(`Cannot interpolate "${value}" because "${component}" is not finite`); } if (unit === null || !lengthUnits.has(unit) || (!allowPercentage && unit === '%')) { throw new TypeError(`Cannot interpolate "${value}" because "${component}" is not a supported transform-origin ${allowPercentage ? 'length-percentage' : 'z length'}`); } return { value: numberValue, unit }; }; const parseTransformOriginToken = (component, value) => { const lower = component.toLowerCase(); if (transformOriginKeywords.has(lower)) { return { type: 'keyword', keyword: lower }; } return { type: 'length-percentage', parsed: parseTransformOriginLengthPercentage({ component, value, allowPercentage: true, }), }; }; const parseTwoTransformOriginKeywords = (first, second, value) => { const candidates = []; for (const firstOption of transformOriginKeywordOptions(first)) { for (const secondOption of transformOriginKeywordOptions(second)) { if (firstOption.axis === secondOption.axis) { continue; } candidates.push(firstOption.axis === 'x' ? [firstOption.value, secondOption.value] : [secondOption.value, firstOption.value]); } } if (candidates.length === 0) { throw new TypeError(`Cannot interpolate "${value}" because "${first} ${second}" is not a valid transform-origin keyword pair`); } return candidates[0]; }; const parseTransformOriginXY = (parts, value) => { if (parts.length === 1) { const token = parseTransformOriginToken(parts[0], value); if (token.type === 'length-percentage') { return [token.parsed, transformOriginCenter]; } if (token.keyword === 'top' || token.keyword === 'bottom') { return [ transformOriginCenter, transformOriginKeywordOptions(token.keyword)[0].value, ]; } return [ transformOriginKeywordOptions(token.keyword)[0].value, transformOriginCenter, ]; } const first = parseTransformOriginToken(parts[0], value); const second = parseTransformOriginToken(parts[1], value); if (first.type === 'length-percentage' && second.type === 'length-percentage') { return [first.parsed, second.parsed]; } if (first.type === 'keyword' && second.type === 'keyword') { return parseTwoTransformOriginKeywords(first.keyword, second.keyword, value); } const keyword = first.type === 'keyword' ? first : second.type === 'keyword' ? second : null; const length = first.type === 'length-percentage' ? first.parsed : second.type === 'length-percentage' ? second.parsed : null; if (keyword === null || length === null) { throw new Error('Expected a keyword and a length-percentage value'); } const keywordIsFirst = first.type === 'keyword'; if (keyword.keyword === 'left' || keyword.keyword === 'right') { if (!keywordIsFirst) { throw new TypeError(`Cannot interpolate "${value}" because horizontal transform-origin keywords must come before a length-percentage value`); } return [transformOriginKeywordOptions(keyword.keyword)[0].value, length]; } if (keyword.keyword === 'top' || keyword.keyword === 'bottom') { return [length, transformOriginKeywordOptions(keyword.keyword)[0].value]; } return keywordIsFirst ? [transformOriginCenter, length] : [length, transformOriginCenter]; }; const parseTransformOriginValue = (output, parts) => { const [x, y] = parseTransformOriginXY(parts.slice(0, 2), output); const z = parts[2] === undefined ? { value: 0, unit: null } : parseTransformOriginLengthPercentage({ component: parts[2], value: output, allowPercentage: false, }); return { kind: 'translate', values: [x.value, y.value, z.value, 0], units: [x.unit, y.unit, z.unit, null], dimensions: parts[2] === undefined ? 2 : 3, axisRotation: false, }; }; const parseAxisRotationValue = (output) => { const parts = output.trim().split(/\s+/); const keywordAxis = parts.length === 2 ? parts[0].toLowerCase() : null; if (keywordAxis === 'x' || keywordAxis === 'y' || keywordAxis === 'z') { const keywordAngle = parseStringInterpolationComponent(parts[1], output); if (keywordAngle.kind !== 'rotate') { return null; } return { kind: 'rotate', values: keywordAxis === 'x' ? [1, 0, 0, keywordAngle.value] : keywordAxis === 'y' ? [0, 1, 0, keywordAngle.value] : [0, 0, 1, keywordAngle.value], units: [null, null, null, keywordAngle.unit], dimensions: 4, axisRotation: true, }; } if (parts.length !== 4) { return null; } const axis = parts.slice(0, 3).map(Number); if (!axis.every(Number.isFinite)) { return null; } const vectorAngle = parseStringInterpolationComponent(parts[3], output); if (vectorAngle.kind !== 'rotate') { return null; } return { kind: 'rotate', values: [axis[0], axis[1], axis[2], vectorAngle.value], units: [null, null, null, vectorAngle.unit], dimensions: 4, axisRotation: true, }; }; const parseStringInterpolationValue = (output) => { var _a, _b, _c, _d, _e, _f; var _g, _h, _j, _k, _l, _m; if (typeof output === 'number') { if (!Number.isFinite(output)) { throw new Error(`outputRange must contain only finite numbers, but got [${output}]`); } return { kind: 'scale', values: [output, output, 1, 0], units: [null, null, null, null], dimensions: 1, axisRotation: false, }; } const axisRotation = parseAxisRotationValue(output); if (axisRotation !== null) { return axisRotation; } const parts = output.trim().split(/\s+/); if (parts.length < 1 || parts.length > 3 || parts[0] === '') { throw new TypeError(`String outputRange values must contain 1 to 3 components, but got "${output}"`); } if (parts.some((part) => transformOriginKeywords.has(part.toLowerCase()))) { return parseTransformOriginValue(output, parts); } const parsed = parts.map((part) => parseStringInterpolationComponent(part, output)); const [{ kind }] = parsed; for (const part of parsed) { if (part.kind !== kind) { throw new TypeError(`Cannot interpolate "${output}" because it mixes ${kind} and ${part.kind} values`); } } if (kind === 'scale') { const x = parsed[0].value; const y = (_g = (_a = parsed[1]) === null || _a === void 0 ? void 0 : _a.value) !== null && _g !== void 0 ? _g : x; const z = (_h = (_b = parsed[2]) === null || _b === void 0 ? void 0 : _b.value) !== null && _h !== void 0 ? _h : 1; return { kind, values: [x, y, z, 0], units: [null, null, null, null], dimensions: parsed.length, axisRotation: false, }; } return { kind, values: [parsed[0].value, (_j = (_c = parsed[1]) === null || _c === void 0 ? void 0 : _c.value) !== null && _j !== void 0 ? _j : 0, (_k = (_d = parsed[2]) === null || _d === void 0 ? void 0 : _d.value) !== null && _k !== void 0 ? _k : 0, 0], units: [ parsed[0].unit, (_l = (_e = parsed[1]) === null || _e === void 0 ? void 0 : _e.unit) !== null && _l !== void 0 ? _l : null, (_m = (_f = parsed[2]) === null || _f === void 0 ? void 0 : _f.unit) !== null && _m !== void 0 ? _m : null, null, ], dimensions: parsed.length, axisRotation: false, }; }; const serializeStringInterpolationValue = ({ kind, values, units, dimensions, axisRotation, }) => { if (axisRotation) { return `${stringifyNumber(values[0])} ${stringifyNumber(values[1])} ${stringifyNumber(values[2])} ${stringifyNumber(values[3])}${units[3]}`; } if (kind === 'scale') { return values .slice(0, dimensions) .map((value) => stringifyNumber(value)) .join(' '); } return values .slice(0, dimensions) .map((value, index) => `${stringifyNumber(value)}${units[index]}`) .join(' '); }; const toSignedArea = (scale) => { if (scale === 0) { return 0; } return Math.sign(scale) * scale * scale; }; const fromSignedArea = (area) => { if (area === 0) { return 0; } return Math.sign(area) * Math.sqrt(Math.abs(area)); }; function interpolateFunction(input, inputRange, outputRange, options) { const { extrapolateLeft, extrapolateRight, easing, output } = options; let result = input; const [inputMin, inputMax] = inputRange; const [outputMin, outputMax] = outputRange; if (result < inputMin) { if (extrapolateLeft === 'identity') { return result; } if (extrapolateLeft === 'clamp') { result = inputMin; } else if (extrapolateLeft === 'wrap') { const range = inputMax - inputMin; result = ((((result - inputMin) % range) + range) % range) + inputMin; } else if (extrapolateLeft === 'extend') { // Noop } } if (result > inputMax) { if (extrapolateRight === 'identity') { return result; } if (extrapolateRight === 'clamp') { result = inputMax; } else if (extrapolateRight === 'wrap') { const range = inputMax - inputMin; result = ((((result - inputMin) % range) + range) % range) + inputMin; } else if (extrapolateRight === 'extend') { // Noop } } if (outputMin === outputMax) { return outputMin; } // Input Range result = (result - inputMin) / (inputMax - inputMin); // Easing result = easing(result); // Output Range if (output === 'perceptual-scale') { const signedAreaMin = toSignedArea(outputMin); const signedAreaMax = toSignedArea(outputMax); result = fromSignedArea(result * (signedAreaMax - signedAreaMin) + signedAreaMin); } else { result = result * (outputMax - outputMin) + outputMin; } return result; } function findRange(input, inputRange) { let i; for (i = 1; i < inputRange.length - 1; ++i) { if (inputRange[i] >= input) { break; } } return i - 1; } const defaultEasing = (num) => num; const resolveOutputOption = (output) => { return output !== null && output !== void 0 ? output : 'linear'; }; const shouldExtendRightForEasing = (easing) => { return easing.remotionShouldExtendRight === true; }; const resolveEasingForSegment = ({ easing, segmentIndex, }) => { if (easing === undefined) { return defaultEasing; } if (typeof easing === 'function') { return easing; } // `segmentIndex` is in [0, inputRange.length - 2]; array length was validated above. return easing[segmentIndex]; }; const interpolateSegment = ({ input, inputRange, outputRange, easing, extrapolateLeft, extrapolateRight, output, }) => { return interpolateFunction(input, inputRange, outputRange, { easing, extrapolateLeft, extrapolateRight: input > inputRange[1] && extrapolateRight === 'clamp' && shouldExtendRightForEasing(easing) ? 'extend' : extrapolateRight, output, }); }; const interpolateNumber = ({ input, inputRange, outputRange, options, }) => { const output = resolveOutputOption(options === null || options === void 0 ? void 0 : options.output); if (inputRange.length === 1) { return outputRange[0]; } const easingOption = options === null || options === void 0 ? void 0 : options.easing; let extrapolateLeft = 'extend'; if ((options === null || options === void 0 ? void 0 : options.extrapolateLeft) !== undefined) { extrapolateLeft = options.extrapolateLeft; } let extrapolateRight = 'extend'; if ((options === null || options === void 0 ? void 0 : options.extrapolateRight) !== undefined) { extrapolateRight = options.extrapolateRight; } const posterizedInput = (options === null || options === void 0 ? void 0 : options.posterize) === undefined ? input : Math.floor(input / options.posterize) * options.posterize; const range = findRange(posterizedInput, inputRange); const easing = resolveEasingForSegment({ easing: easingOption, segmentIndex: range, }); let result = interpolateSegment({ input: posterizedInput, inputRange: [inputRange[range], inputRange[range + 1]], outputRange: [outputRange[range], outputRange[range + 1]], easing, extrapolateLeft, extrapolateRight, output, }); for (let segmentIndex = 0; segmentIndex < range; segmentIndex++) { const previousEasing = resolveEasingForSegment({ easing: easingOption, segmentIndex, }); if (!shouldExtendRightForEasing(previousEasing)) { continue; } const previousSegmentEnd = inputRange[segmentIndex + 1]; if (posterizedInput <= previousSegmentEnd) { continue; } const continuedSegmentValue = interpolateSegment({ input: posterizedInput, inputRange: [inputRange[segmentIndex], previousSegmentEnd], outputRange: [outputRange[segmentIndex], outputRange[segmentIndex + 1]], easing: previousEasing, extrapolateLeft, extrapolateRight: 'extend', output, }); result += continuedSegmentValue - outputRange[segmentIndex + 1]; } return result; }; const interpolateString = ({ input, inputRange, outputRange, options, }) => { var _a; const initiallyParsedOutputRange = outputRange.map(parseStringInterpolationValue); const hasAxisRotation = initiallyParsedOutputRange.some((parsed) => parsed.axisRotation); const posterizedInput = (options === null || options === void 0 ? void 0 : options.posterize) === undefined ? input : Math.floor(input / options.posterize) * options.posterize; const segmentIndex = inputRange.length === 1 ? 0 : findRange(posterizedInput, inputRange); const parsedOutputRange = hasAxisRotation ? initiallyParsedOutputRange.map((parsed, index) => { if (parsed.kind !== 'rotate') { return parsed; } if (parsed.axisRotation) { return parsed; } if (parsed.dimensions !== 1) { throw new TypeError('Cannot interpolate a multi-angle rotate value with an axis rotation'); } // A zero-degree rotation is the identity regardless of its axis. Reusing // the active neighbor's axis prevents the axis from drifting while the // angle interpolates away from zero. const adjacentAxisRotation = parsed.values[0] === 0 ? index === 0 ? initiallyParsedOutputRange.find((candidate) => candidate.axisRotation) : index === initiallyParsedOutputRange.length - 1 ? [...initiallyParsedOutputRange] .reverse() .find((candidate) => candidate.axisRotation) : index === segmentIndex ? initiallyParsedOutputRange[index + 1] : index === segmentIndex + 1 ? initiallyParsedOutputRange[index - 1] : undefined : undefined; const axis = (adjacentAxisRotation === null || adjacentAxisRotation === void 0 ? void 0 : adjacentAxisRotation.axisRotation) ? adjacentAxisRotation.values : [0, 0, 1]; return { kind: 'rotate', values: [axis[0], axis[1], axis[2], parsed.values[0]], units: [null, null, null, parsed.units[0]], dimensions: 4, axisRotation: true, }; }) : initiallyParsedOutputRange; const kind = (_a = parsedOutputRange[0]) === null || _a === void 0 ? void 0 : _a.kind; if (kind === undefined) { throw new Error('outputRange must have at least 1 element'); } for (const parsed of parsedOutputRange) { if (parsed.kind !== kind) { throw new TypeError(`Cannot interpolate ${kind} values with ${parsed.kind} values`); } } const dimensions = Math.max(...parsedOutputRange.map((parsed) => parsed.dimensions)); const units = [ null, null, null, null, ]; if (kind !== 'scale') { for (let axis = 0; axis < dimensions; axis++) { if (hasAxisRotation && axis < 3) { continue; } for (const parsed of parsedOutputRange) { const unit = parsed.units[axis]; if (unit === null) { continue; } if (units[axis] === null) { units[axis] = unit; continue; } if (units[axis] !== unit) { throw new TypeError(`Cannot interpolate ${kind} values with different units on axis ${axis + 1}: ${units[axis]} and ${unit}`); } } if (units[axis] === null) { throw new TypeError(`Cannot interpolate ${kind} values because axis ${axis + 1} has no unit`); } } } const values = [0, 0, 0, 0]; for (let axis = 0; axis < dimensions; axis++) { values[axis] = interpolateNumber({ input, inputRange, outputRange: parsedOutputRange.map((parsed) => parsed.values[axis]), options, }); } return serializeStringInterpolationValue({ kind, values, units, dimensions, axisRotation: hasAxisRotation, }); }; const interpolateDiscreteString = ({ input, inputRange, outputRange, options, }) => { if (inputRange.length === 1) { return outputRange[0]; } for (let segmentIndex = 0; segmentIndex < inputRange.length - 1; segmentIndex++) { if (resolveEasingForSegment({ easing: options === null || options === void 0 ? void 0 : options.easing, segmentIndex, }) !== easing_js_1.Easing.step1) { throw new TypeError('Non-numeric strings can only be interpolated using Easing.step1'); } } const posterizedInput = (options === null || options === void 0 ? void 0 : options.posterize) === undefined ? input : Math.floor(input / options.posterize) * options.posterize; const inputMin = inputRange[0]; const inputMax = inputRange[inputRange.length - 1]; let resolvedInput = posterizedInput; if (resolvedInput < inputMin) { if ((options === null || options === void 0 ? void 0 : options.extrapolateLeft) === 'identity') { throw new TypeError('extrapolateLeft: "identity" is not supported for non-numeric strings'); } if ((options === null || options === void 0 ? void 0 : options.extrapolateLeft) === 'wrap') { const wrapRange = inputMax - inputMin; resolvedInput = ((((resolvedInput - inputMin) % wrapRange) + wrapRange) % wrapRange) + inputMin; } else { return outputRange[0]; } } if (resolvedInput > inputMax) { if ((options === null || options === void 0 ? void 0 : options.extrapolateRight) === 'identity') { throw new TypeError('extrapolateRight: "identity" is not supported for non-numeric strings'); } if ((options === null || options === void 0 ? void 0 : options.extrapolateRight) === 'wrap') { const wrapRange = inputMax - inputMin; resolvedInput = ((((resolvedInput - inputMin) % wrapRange) + wrapRange) % wrapRange) + inputMin; } else { return outputRange[outputRange.length - 1]; } } const range = findRange(resolvedInput, inputRange); return resolvedInput >= inputRange[range + 1] ? outputRange[range + 1] : outputRange[range]; }; const validateTupleOutputRange = (outputRange) => { var _a; const dimensions = (_a = outputRange[0]) === null || _a === void 0 ? void 0 : _a.length; if (dimensions === undefined) { throw new Error('outputRange must have at least 1 element'); } if (dimensions === 0) { throw new TypeError('outputRange tuples must contain at least 1 number'); } for (const output of outputRange) { if (output.length !== dimensions) { throw new TypeError(`outputRange tuples must all have the same length, but got ${dimensions} and ${output.length}`); } for (const value of output) { if (typeof value !== 'number' || !Number.isFinite(value)) { throw new TypeError(`outputRange tuples must contain only finite numbers, but got [${output.join(',')}]`); } } } return dimensions; }; const interpolateTuple = ({ input, inputRange, outputRange, options, }) => { const dimensions = validateTupleOutputRange(outputRange); return new Array(dimensions).fill(true).map((_, axis) => interpolateNumber({ input, inputRange, outputRange: outputRange.map((output) => output[axis]), options, })); }; function checkValidInputRange(arr) { for (let i = 1; i < arr.length; ++i) { if (!(arr[i] > arr[i - 1])) { throw new Error(`inputRange must be strictly monotonically increasing but got [${arr.join(',')}]`); } } } function checkInfiniteRange(name, arr) { if (arr.length < 1) { throw new Error(name + ' must have at least 1 element'); } for (const element of arr) { if (typeof element !== 'number') { throw new Error(`${name} must contain only numbers`); } if (!Number.isFinite(element)) { throw new Error(`${name} must contain only finite numbers, but got [${arr.join(',')}]`); } } } function assertValidInterpolateEasingOption(easing, inputRangeLength) { if (easing === undefined) { return; } if (typeof easing === 'function') { return; } const expectedLength = inputRangeLength - 1; if (easing.length !== expectedLength) { throw new Error(`When easing is an array, it must have one entry per segment between keyframes (length inputRange.length - 1 = ${expectedLength}), but got length ${easing.length}`); } for (let i = 0; i < easing.length; i++) { if (typeof easing[i] !== 'function') { throw new Error(`easing[${i}] must be a function`); } } } function assertValidInterpolatePosterizeOption(posterize) { if (posterize === undefined) { return; } if (typeof posterize !== 'number' || !Number.isFinite(posterize) || posterize <= 0) { throw new Error(`posterize must be a positive finite number, but got ${posterize}`); } } function assertValidInterpolateOutputOption(output) { if (output === undefined || output === 'linear' || output === 'perceptual-scale') { return; } throw new Error(`output must be "linear" or "perceptual-scale", but got ${String(output)}`); } function interpolate(input, inputRange, outputRange, options) { if (typeof input === 'undefined') { throw new Error('input can not be undefined'); } if (typeof inputRange === 'undefined') { throw new Error('inputRange can not be undefined'); } if (typeof outputRange === 'undefined') { throw new Error('outputRange can not be undefined'); } if (inputRange.length !== outputRange.length) { throw new Error('inputRange (' + inputRange.length + ') and outputRange (' + outputRange.length + ') must have the same length'); } checkInfiniteRange('inputRange', inputRange); checkValidInputRange(inputRange); assertValidInterpolateEasingOption(options === null || options === void 0 ? void 0 : options.easing, inputRange.length); assertValidInterpolatePosterizeOption(options === null || options === void 0 ? void 0 : options.posterize); assertValidInterpolateOutputOption(options === null || options === void 0 ? void 0 : options.output); if (typeof input !== 'number') { throw new TypeError('Cannot interpolate an input which is not a number'); } if (!Array.isArray(outputRange)) { throw new Error('outputRange must contain only numbers'); } const hasStringOutput = outputRange.some((output) => typeof output === 'string'); if (hasStringOutput) { if (!outputRange.every((output) => typeof output === 'string' || typeof output === 'number')) { throw new TypeError('outputRange must contain only numbers, or supported scale, translate, and rotate strings'); } try { return interpolateString({ input, inputRange, outputRange, options }); } catch (error) { if (!outputRange.every((output) => typeof output === 'string')) { throw error; } const hasNonNumericString = outputRange.some((output) => { try { parseStringInterpolationValue(output); return false; } catch (parseError) { return parseError instanceof UnsupportedStringInterpolationValueError; } }); if (!hasNonNumericString) { throw error; } return interpolateDiscreteString({ input, inputRange, outputRange, options, }); } } if (outputRange.every((output) => Array.isArray(output))) { return interpolateTuple({ input, inputRange, outputRange, options }); } if (!outputRange.every((output) => typeof output === 'number')) { throw new TypeError('outputRange must contain only numbers, numeric tuples, or supported scale, translate, and rotate strings'); } checkInfiniteRange('outputRange', outputRange); return interpolateNumber({ input, inputRange, outputRange, options }); } /* eslint-enable no-redeclare */