mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-09 04:55:04 +00:00
* 3D: Orientation and X/Y/Z Rotation turn Z first, keyed Orientation slerps, as After Effects After Effects composes a 3D layer's (and a camera's) Orientation and its X/Y/Z Rotation as Rx * Ry * Rz each, Orientation before the axis rotations; EffectCraft used Rz * Ry * Rx. Measured on synthetic AE 2026 cases: Orientation [10,20,30] (488) was 5.2 % of the frame off and now matches to the anti-aliased edge (0.03 %); X/Y/Z Rotation 10/20/30 (800), Orientation + X Rotation (801) and one-node cameras (804, 805) likewise. Keyed Orientation moves along the shortest arc between the keys (quaternion slerp, eased in time, angles written in [0, 360)): keys [0,0,0] -> [10,20,30] read back [6.2643, 9.3531, 15.2190] half-way where EffectCraft gave [5,10,15]; [0,0,0] -> [60,80,40] linear and eased match AE's read-backs within 0.002 deg. lookAt() returns AE's angles (z axis on the target, x axis level; two AE read-backs within 1e-3 deg). The camera tracker, VR editor, Look At and orbit commands use the new decomposition. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keyframes: a linear side next to an eased side has 16.67 % influence, as After Effects In a segment that is Bezier on one side and linear on the other, the linear side keeps the segment's average speed, but with the 16.67 % influence After Effects shows for linear keys in the Keyframe Velocity dialog (and reports to scripts), not 33.3 %. Measured on an After Effects 2026 time-remap render (a square at x = 50 + 455 * remap): out of a key at 2 s (3.5, speed 0, influence 50 %) into a linear key at 4 s (0.5), AE plays 3.290 at 2.5 s and 2.657 at 3 s; EffectCraft played 3.308 and 2.629 (8 and 13 px off), now the same square positions to the pixel. LINEAR_SIDE_INFLUENCE is used by evaluation (scalar, per-dimension, paths and spatial), side_ease (Graph Editor, velocity dialog, keyIn/OutTemporalEase) and the Lottie export, so exported easing plays as EffectCraft does. Segments linear on both sides stay straight lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Timewarp: retime the layer's source without the effects above it, as After Effects After Effects' Timewarp reads the layer's source (with its masks) at the retimed times and ignores the effects applied before it: a red solid with a Gradient Ramp and then Timewarp (Speed 25) renders as the red solid in AE 2026 (EXPORTER e2 read-back). EffectCraft retimed the layer with its effects (mean difference 127.5/255 over the comp), now identical (0, CPU and GPU). Echo, Posterize Time and the other time effects keep reading the effects before them, as measured before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Image sequences: a gap in the numbering holds the previous frame, as After Effects After Effects 2026 shows the last existing file across a gap in a sequence's numbering (case 860: files 1, 2, 4, 5 show frame 2 at frame 3; VERIFY read-back). EffectCraft drew colour bars there. The media pool now holds the previous file (Footage::sequence_file_held); colour bars remain only where no file precedes the frame. Missing-frame reports on import are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Review: keyed Orientation uses LINEAR_SIDE_INFLUENCE for linear sides; real NaN assert evaluate_orientation still gave a linear side (and the unreachable missing-key fallback) 1/3 influence while segment_value, side_ease and the Lottie export use LINEAR_SIDE_INFLUENCE (1/6), so a keyed Orientation next to an eased key moved on a different curve than every other property (CodeRabbit on #1876). New test: orientation_linear_side_next_to_an_eased_side_matches_a_scalar (both directions). The always-true NaN assert in keyed_orientation_slerps_as_after_effects now checks that a non-finite key comes back as given; Property::value_at says why the match_id "orientation" routing is safe. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Format keyframe orientation test (cargo fmt) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Move the image-sequence gap change out of this branch The gap change (hold the previous file instead of colour bars) reverses #297's documented behaviour and goes to its own PR, as requested in review. pool.rs, sequence.rs, the footage test and docs/footage.md / docs/agents.md are back to main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
1469 lines
63 KiB
JavaScript
1469 lines
63 KiB
JavaScript
// EffectCraft expression runtime: the After Effects expression object model and global
|
||
// functions, written from the public Expression Language Reference (behaviour only).
|
||
//
|
||
// Natives (Rust): __h(op, ...) host data request; __wig(...) wiggle offsets; __noise(x, y, z);
|
||
// __rnd(...) seeded random. This file is evaluated once per runtime and is NOT passed through
|
||
// the array-maths rewriter, so it uses plain JS operators.
|
||
|
||
var time = 0;
|
||
var value = 0;
|
||
var __S = null;
|
||
var __rc = 0;
|
||
var __seed = 0;
|
||
var __timeless = false;
|
||
// Text Expression Selector inputs (1, 1, [100, 100, 100] outside a selector).
|
||
var textIndex = 1;
|
||
var textTotal = 1;
|
||
var selectorValue = [100, 100, 100];
|
||
|
||
// Request ops (must match runtime.rs).
|
||
var __COMP = 0, __COMPNAME = 1, __LAYER = 2, __LINFO = 3, __CHILD = 4, __PINFO = 5, __VALUE = 6, __XFORM = 7, __RECT = 8, __MARKERS = 9, __DOC = 10;
|
||
var __FOOT = 11, __FDATA = 12, __SAMPLE = 13, __PROJ = 14, __SOURCE = 15, __ANCESTOR = 16;
|
||
|
||
function __begin(t, v, c, l, path, uid, idx, fd, ti, tt, sv) {
|
||
time = t;
|
||
value = v;
|
||
textIndex = ti;
|
||
textTotal = tt;
|
||
selectorValue = sv;
|
||
__S = { c: c, l: l, path: path, uid: uid, idx: idx, fd: fd, comp: null, layer: null, prop: null, vars: {} };
|
||
__rc = 0;
|
||
__seed = 0;
|
||
__timeless = false;
|
||
__projectInfo = null;
|
||
__layersTrim();
|
||
}
|
||
|
||
function __finish(r) {
|
||
// Rethrow from this frame so the runtime's stack unwinds (see `Runtime::script`).
|
||
try {
|
||
return __result(r);
|
||
} catch (e) {
|
||
throw e;
|
||
}
|
||
}
|
||
|
||
function __result(r) {
|
||
r = __v(r);
|
||
if (typeof r === 'function' && r.__isLayer) return r.index;
|
||
if (r !== null && typeof r === 'object' && r.__isTextStyle) return r.__out();
|
||
// `[temp, temp]` where temp is a property (pick-whip 1D → 2D): read the values here, while
|
||
// host requests can still be answered and the script re-run.
|
||
if (Array.isArray(r)) return r.map(function (x) { return __v(x); });
|
||
return r;
|
||
}
|
||
|
||
function __err(msg) {
|
||
throw new Error(msg);
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Array maths (targets of the operator rewrite).
|
||
|
||
function __v(x) {
|
||
if (x !== null && typeof x === 'object' && x.__isProp === true) return x.value;
|
||
return x;
|
||
}
|
||
|
||
function __bin(a, b, f) {
|
||
a = __v(a);
|
||
b = __v(b);
|
||
var aa = Array.isArray(a), ba = Array.isArray(b), i, r;
|
||
if (aa && ba) {
|
||
var n = Math.max(a.length, b.length);
|
||
r = new Array(n);
|
||
for (i = 0; i < n; i++) r[i] = f(i < a.length ? a[i] : 0, i < b.length ? b[i] : 0);
|
||
return r;
|
||
}
|
||
if (aa && (typeof b === 'number' || typeof b === 'boolean')) {
|
||
r = new Array(a.length);
|
||
for (i = 0; i < a.length; i++) r[i] = f(a[i], b);
|
||
return r;
|
||
}
|
||
if (ba && (typeof a === 'number' || typeof a === 'boolean')) {
|
||
r = new Array(b.length);
|
||
for (i = 0; i < b.length; i++) r[i] = f(a, b[i]);
|
||
return r;
|
||
}
|
||
return f(a, b);
|
||
}
|
||
|
||
function __fadd(x, y) { return x + y; }
|
||
function __fsub(x, y) { return x - y; }
|
||
function __fmul(x, y) { return x * y; }
|
||
function __fdiv(x, y) { return x / y; }
|
||
|
||
function __add(a, b) {
|
||
if (typeof a === 'number' && typeof b === 'number') return a + b;
|
||
if (typeof a === 'string' || typeof b === 'string') return __v(a) + __v(b);
|
||
var x = __v(a), y = __v(b);
|
||
if (typeof x === 'string' || typeof y === 'string') return x + y;
|
||
if ((Array.isArray(x) && typeof y === 'string') || (Array.isArray(y) && typeof x === 'string')) return x + y;
|
||
return __bin(x, y, __fadd);
|
||
}
|
||
function __sub(a, b) {
|
||
if (typeof a === 'number' && typeof b === 'number') return a - b;
|
||
return __bin(a, b, __fsub);
|
||
}
|
||
function __mul(a, b) {
|
||
if (typeof a === 'number' && typeof b === 'number') return a * b;
|
||
return __bin(a, b, __fmul);
|
||
}
|
||
function __div(a, b) {
|
||
if (typeof a === 'number' && typeof b === 'number') return a / b;
|
||
return __bin(a, b, __fdiv);
|
||
}
|
||
function __neg(a) {
|
||
if (typeof a === 'number') return -a;
|
||
a = __v(a);
|
||
if (Array.isArray(a)) return a.map(function (x) { return -x; });
|
||
return -a;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Vector maths.
|
||
|
||
function add(a, b) { return __bin(a, b, __fadd); }
|
||
function sub(a, b) { return __bin(a, b, __fsub); }
|
||
function mul(a, b) { return __bin(a, b, __fmul); }
|
||
function div(a, b) { return __bin(a, b, __fdiv); }
|
||
|
||
function clamp(v, a, b) {
|
||
v = __v(v); a = __v(a); b = __v(b);
|
||
var lo = function (x, y) { return Math.min(Math.max(x, Math.min(y[0], y[1])), Math.max(y[0], y[1])); };
|
||
if (Array.isArray(v)) {
|
||
return v.map(function (x, i) {
|
||
return lo(x, [Array.isArray(a) ? a[i] : a, Array.isArray(b) ? b[i] : b]);
|
||
});
|
||
}
|
||
return lo(v, [a, b]);
|
||
}
|
||
|
||
function dot(a, b) {
|
||
a = __v(a); b = __v(b);
|
||
var s = 0;
|
||
for (var i = 0; i < Math.min(a.length, b.length); i++) s += a[i] * b[i];
|
||
return s;
|
||
}
|
||
|
||
function cross(a, b) {
|
||
a = __v(a); b = __v(b);
|
||
var a2 = a[2] || 0, b2 = b[2] || 0;
|
||
return [a[1] * b2 - a2 * b[1], a2 * b[0] - a[0] * b2, a[0] * b[1] - a[1] * b[0]];
|
||
}
|
||
|
||
function length(a, b) {
|
||
a = __v(a);
|
||
if (b !== undefined) a = sub(a, __v(b));
|
||
if (!Array.isArray(a)) return Math.abs(a);
|
||
var s = 0;
|
||
for (var i = 0; i < a.length; i++) s += a[i] * a[i];
|
||
return Math.sqrt(s);
|
||
}
|
||
|
||
function normalize(a) {
|
||
a = __v(a);
|
||
var l = length(a);
|
||
return l === 0 ? a.map(function () { return 0; }) : div(a, l);
|
||
}
|
||
|
||
// Orientation that points the layer's z axis at `at` with its x axis kept level, in the
|
||
// Orientation order (Rx · Ry · Rz) and wrapped to [0, 360), as After Effects returns it.
|
||
function lookAt(from, at) {
|
||
var d = normalize(sub(__v(at), __v(from)));
|
||
var x = d[0], y = d[1], z = d[2] || 0;
|
||
var p = Math.asin(Math.max(-1, Math.min(1, -y)));
|
||
var w = Math.atan2(x, z);
|
||
var cw = Math.cos(w), sw = Math.sin(w), cp = Math.cos(p), sp = Math.sin(p);
|
||
// The rotation Ry(w) · Rx(p), written as Orientation angles.
|
||
var oy = Math.asin(Math.max(-1, Math.min(1, sw * cp)));
|
||
var ox, oz;
|
||
if (Math.sqrt(cw * cw + sw * sw * sp * sp) > 1e-9) {
|
||
ox = Math.atan2(sp, cw * cp);
|
||
oz = Math.atan2(-sw * sp, cw);
|
||
} else {
|
||
ox = Math.atan2(cw * sp, cp);
|
||
oz = 0;
|
||
}
|
||
return [ox, oy, oz].map(function (r) {
|
||
var a = ((r * 180 / Math.PI) % 360 + 360) % 360;
|
||
return a >= 360 - 1e-9 || Math.abs(a) < 1e-9 ? 0 : a;
|
||
});
|
||
}
|
||
|
||
function degreesToRadians(d) { return d * Math.PI / 180; }
|
||
function radiansToDegrees(r) { return r * 180 / Math.PI; }
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Interpolation.
|
||
|
||
function __interp(shape, args) {
|
||
var t, tMin, tMax, v1, v2;
|
||
if (args.length <= 3) {
|
||
t = args[0]; tMin = 0; tMax = 1; v1 = args[1]; v2 = args[2];
|
||
} else {
|
||
t = args[0]; tMin = args[1]; tMax = args[2]; v1 = args[3]; v2 = args[4];
|
||
}
|
||
t = __v(t); v1 = __v(v1); v2 = __v(v2);
|
||
var u;
|
||
if (tMax === tMin) u = t >= tMax ? 1 : 0;
|
||
else u = (t - tMin) / (tMax - tMin);
|
||
u = Math.max(0, Math.min(1, u));
|
||
u = shape(u);
|
||
if (Array.isArray(v1) || Array.isArray(v2)) return add(v1, mul(sub(v2, v1), u));
|
||
return v1 + (v2 - v1) * u;
|
||
}
|
||
|
||
function __lin(u) { return u; }
|
||
function __ease(u) { return u * u * (3 - 2 * u); }
|
||
function __easeIn(u) { return u * u * (2 - u); }
|
||
function __easeOut(u) { var w = 1 - u; return 1 - w * w * (2 - w); }
|
||
|
||
function linear() { return __interp(__lin, arguments); }
|
||
function ease() { return __interp(__ease, arguments); }
|
||
function easeIn() { return __interp(__easeIn, arguments); }
|
||
function easeOut() { return __interp(__easeOut, arguments); }
|
||
|
||
// Cubic-Bezier easing (the CSS cubic-bezier() shape): [x1, y1, x2, y2], with x1 / x2 in 0..1
|
||
// and y1 / y2 free (overshoot included). Solves x(u) = x by Newton–Raphson, then by bisection
|
||
// when Newton leaves 0..1 or the derivative vanishes, and returns y(u). A curve that is not four
|
||
// finite numbers is linear, never an error.
|
||
function __cubicBezier(curve) {
|
||
var x1, y1, x2, y2;
|
||
if (Array.isArray(curve) && curve.length === 4) {
|
||
x1 = curve[0]; y1 = curve[1]; x2 = curve[2]; y2 = curve[3];
|
||
}
|
||
if (typeof x1 !== 'number' || typeof y1 !== 'number' || typeof x2 !== 'number' || typeof y2 !== 'number' ||
|
||
!isFinite(x1) || !isFinite(y1) || !isFinite(x2) || !isFinite(y2)) {
|
||
return __lin;
|
||
}
|
||
x1 = Math.max(0, Math.min(1, x1));
|
||
x2 = Math.max(0, Math.min(1, x2));
|
||
function bx(u) { var v = 1 - u; return 3 * v * v * u * x1 + 3 * v * u * u * x2 + u * u * u; }
|
||
function dbx(u) { var v = 1 - u; return 3 * v * v * x1 + 6 * v * u * (x2 - x1) + 3 * u * u * (1 - x2); }
|
||
function by(u) { var v = 1 - u; return 3 * v * v * u * y1 + 3 * v * u * u * y2 + u * u * u; }
|
||
function dby(u) { var v = 1 - u; return 3 * v * v * y1 + 6 * v * u * (y2 - y1) + 3 * u * u * (1 - y2); }
|
||
return function (x) {
|
||
if (x <= 0) return 0;
|
||
if (x >= 1) return 1;
|
||
var u = x;
|
||
for (var i = 0; i < 8; i++) {
|
||
var f = bx(u) - x;
|
||
var d = dbx(u);
|
||
if (Math.abs(d) < 1e-12) break;
|
||
// Stop when the *value* is accurate: near the endpoints dy/dx is steep, so a residual in x
|
||
// alone can still mean a visible error in y (f * dy/dx estimates it).
|
||
if (Math.abs(f * dby(u) / d) < 1e-9) return by(u);
|
||
var n = u - f / d;
|
||
if (!(n >= 0 && n <= 1)) break;
|
||
u = n;
|
||
}
|
||
var lo = 0, hi = 1;
|
||
for (var j = 0; j < 60; j++) {
|
||
var mid = (lo + hi) / 2;
|
||
if (bx(mid) < x) lo = mid; else hi = mid;
|
||
}
|
||
return by((lo + hi) / 2);
|
||
};
|
||
}
|
||
|
||
// bezier(t, tMin, tMax, value1, value2, curve), or bezier(t, value1, value2, curve): the curve is
|
||
// the sixth / fourth argument. Read it by position, not from the end: when it is missing or
|
||
// unusable, the interpolation falls back to linear without consuming the last value.
|
||
function bezier() {
|
||
var a = arguments, n = a.length;
|
||
if (n >= 6) return __interp(__cubicBezier(a[5]), [a[0], a[1], a[2], a[3], a[4]]);
|
||
if (n === 5) return __interp(__lin, [a[0], a[1], a[2], a[3], a[4]]);
|
||
if (n === 4) return __interp(__cubicBezier(a[3]), [a[0], a[1], a[2]]);
|
||
return __interp(__lin, [a[0], a[1], a[2]]);
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Random numbers and noise.
|
||
|
||
function seedRandom(offset, timeless) {
|
||
__seed = offset;
|
||
__timeless = !!timeless;
|
||
}
|
||
|
||
function __r01() {
|
||
return __rnd(__S.idx, __S.uid, __seed, __timeless, time, __rc++);
|
||
}
|
||
|
||
function __scaleRand(r, args) {
|
||
if (args.length === 0) return r();
|
||
var a = __v(args[0]);
|
||
if (args.length === 1) {
|
||
if (Array.isArray(a)) return a.map(function (m) { return r() * m; });
|
||
return r() * a;
|
||
}
|
||
var b = __v(args[1]);
|
||
if (Array.isArray(a) || Array.isArray(b)) {
|
||
var n = Math.max(Array.isArray(a) ? a.length : 1, Array.isArray(b) ? b.length : 1), out = [];
|
||
for (var i = 0; i < n; i++) {
|
||
var lo = Array.isArray(a) ? (a[i] || 0) : a, hi = Array.isArray(b) ? (b[i] || 0) : b;
|
||
out.push(lo + r() * (hi - lo));
|
||
}
|
||
return out;
|
||
}
|
||
return a + r() * (b - a);
|
||
}
|
||
|
||
function random() { return __scaleRand(__r01, arguments); }
|
||
|
||
function __gauss01() {
|
||
var u1 = Math.max(__r01(), 1e-12), u2 = __r01();
|
||
var z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
|
||
// ~90% of results fall in [0, 1].
|
||
return 0.5 + z * 0.304;
|
||
}
|
||
|
||
function gaussRandom() { return __scaleRand(__gauss01, arguments); }
|
||
|
||
function noise(x) {
|
||
x = __v(x);
|
||
if (Array.isArray(x)) return __noise(x[0] || 0, x[1] || 0, x[2] || 0);
|
||
return __noise(x, 0, 0);
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Time conversion.
|
||
|
||
function timeToFrames(t, fps, isDuration) {
|
||
if (t === undefined) t = time + thisComp.displayStartTime;
|
||
if (fps === undefined) fps = 1 / __S.fd;
|
||
var frames = t * fps;
|
||
// The 1e-6 keeps a whole number of frames that floating point overshoots (31/24 - 5/24) whole.
|
||
if (isDuration && frames !== 0) return frames < 0 ? Math.floor(frames + 1e-6) : Math.ceil(frames - 1e-6);
|
||
return Math.floor(frames + 1e-6);
|
||
}
|
||
|
||
function framesToTime(frames, fps) {
|
||
if (fps === undefined) fps = 1 / __S.fd;
|
||
return frames / fps;
|
||
}
|
||
|
||
function __pad(n) { return n < 10 ? '0' + n : '' + n; }
|
||
|
||
function timeToTimecode(t, base, isDuration) {
|
||
if (t === undefined) t = time + thisComp.displayStartTime;
|
||
if (base === undefined) base = 30;
|
||
var neg = t < 0;
|
||
var f = Math.floor(Math.abs(t) * base + 1e-6);
|
||
var ff = f % base, s = Math.floor(f / base), ss = s % 60, m = Math.floor(s / 60), mm = m % 60, h = Math.floor(m / 60);
|
||
return (neg ? '-' : '') + h + ':' + __pad(mm) + ':' + __pad(ss) + ':' + __pad(ff);
|
||
}
|
||
|
||
function timeToFeetAndFrames(t, fps, framesPerFoot, isDuration) {
|
||
if (t === undefined) t = time + thisComp.displayStartTime;
|
||
if (fps === undefined) fps = 1 / __S.fd;
|
||
if (framesPerFoot === undefined) framesPerFoot = 16;
|
||
var neg = t < 0;
|
||
var f = Math.floor(Math.abs(t) * fps + 1e-6);
|
||
var feet = Math.floor(f / framesPerFoot), fr = f % framesPerFoot;
|
||
var s = String(fr);
|
||
while (s.length < 2) s = '0' + s;
|
||
return (neg ? '-' : '') + feet + '+' + s;
|
||
}
|
||
|
||
function timeToCurrentFormat(t, fps) {
|
||
if (fps === undefined) fps = Math.round(1 / __S.fd);
|
||
return timeToTimecode(t, fps);
|
||
}
|
||
|
||
function posterizeTime(fps) {
|
||
if (!(fps > 0)) return;
|
||
var t = Math.floor(time * fps + 1e-6) / fps;
|
||
if (t !== time) {
|
||
var v = thisProperty.valueAtTime(t);
|
||
time = t;
|
||
value = v;
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Colour.
|
||
|
||
function rgbToHsl(c) {
|
||
c = __v(c);
|
||
var r = c[0], g = c[1], b = c[2], a = c.length > 3 ? c[3] : 1;
|
||
var mx = Math.max(r, g, b), mn = Math.min(r, g, b), l = (mx + mn) / 2, h = 0, s = 0;
|
||
if (mx !== mn) {
|
||
var d = mx - mn;
|
||
s = l > 0.5 ? d / (2 - mx - mn) : d / (mx + mn);
|
||
if (mx === r) h = (g - b) / d + (g < b ? 6 : 0);
|
||
else if (mx === g) h = (b - r) / d + 2;
|
||
else h = (r - g) / d + 4;
|
||
h /= 6;
|
||
}
|
||
return [h, s, l, a];
|
||
}
|
||
|
||
function hslToRgb(c) {
|
||
c = __v(c);
|
||
var h = c[0], s = c[1], l = c[2], a = c.length > 3 ? c[3] : 1;
|
||
if (s === 0) return [l, l, l, a];
|
||
var hue = function (p, q, t) {
|
||
if (t < 0) t += 1;
|
||
if (t > 1) t -= 1;
|
||
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
||
if (t < 1 / 2) return q;
|
||
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
||
return p;
|
||
};
|
||
var q = l < 0.5 ? l * (1 + s) : l + s - l * s, p = 2 * l - q;
|
||
return [hue(p, q, h + 1 / 3), hue(p, q, h), hue(p, q, h - 1 / 3), a];
|
||
}
|
||
|
||
function hexToRgb(hex) {
|
||
var s = String(hex).replace(/^#|^0x/i, '');
|
||
if (s.length === 3) s = s[0] + s[0] + s[1] + s[1] + s[2] + s[2];
|
||
var n = parseInt(s.slice(0, 6), 16);
|
||
var a = s.length >= 8 ? parseInt(s.slice(6, 8), 16) / 255 : 1;
|
||
return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255, a];
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Paths.
|
||
|
||
function __Path(pts, ins, outs, closed) {
|
||
this.__path = true;
|
||
this.__pts = pts; this.__in = ins; this.__out = outs; this.__closed = !!closed;
|
||
}
|
||
__Path.prototype.points = function () { return this.__pts; };
|
||
__Path.prototype.inTangents = function () { return this.__in; };
|
||
__Path.prototype.outTangents = function () { return this.__out; };
|
||
__Path.prototype.isClosed = function () { return this.__closed; };
|
||
// pointOnPath / tangentOnPath: a point (or unit tangent) at a fraction of the path's arc length.
|
||
__Path.prototype.pointOnPath = function (pct) { return __pathAt(this, pct === undefined ? 0.5 : pct, false); };
|
||
__Path.prototype.tangentOnPath = function (pct) { return __pathAt(this, pct === undefined ? 0.5 : pct, true); };
|
||
__Path.prototype.normalOnPath = function (pct) { var t = this.tangentOnPath(pct); return [t[1], -t[0]]; };
|
||
|
||
function __pathSegs(p) {
|
||
var n = p.__pts.length, out = [];
|
||
var m = p.__closed ? n : n - 1;
|
||
for (var i = 0; i < m; i++) {
|
||
var j = (i + 1) % n, a = p.__pts[i], b = p.__pts[j];
|
||
var o = p.__out[i] || [0, 0], ii = p.__in[j] || [0, 0];
|
||
out.push([a, [a[0] + o[0], a[1] + o[1]], [b[0] + ii[0], b[1] + ii[1]], b]);
|
||
}
|
||
return out;
|
||
}
|
||
function __bezAt(s, u) {
|
||
var v = 1 - u;
|
||
return [0, 1].map(function (k) { return v * v * v * s[0][k] + 3 * v * v * u * s[1][k] + 3 * v * u * u * s[2][k] + u * u * u * s[3][k]; });
|
||
}
|
||
function __bezDer(s, u) {
|
||
var v = 1 - u;
|
||
return [0, 1].map(function (k) { return 3 * v * v * (s[1][k] - s[0][k]) + 6 * v * u * (s[2][k] - s[1][k]) + 3 * u * u * (s[3][k] - s[2][k]); });
|
||
}
|
||
function __pathAt(p, pct, tangent) {
|
||
var segs = __pathSegs(p);
|
||
if (!segs.length) return tangent ? [1, 0] : p.__pts.length ? [p.__pts[0][0], p.__pts[0][1]] : [0, 0];
|
||
var N = 32, table = [], total = 0;
|
||
for (var k = 0; k < segs.length; k++) {
|
||
var prev = __bezAt(segs[k], 0);
|
||
for (var s = 1; s <= N; s++) {
|
||
var q = __bezAt(segs[k], s / N);
|
||
total += Math.sqrt((q[0] - prev[0]) * (q[0] - prev[0]) + (q[1] - prev[1]) * (q[1] - prev[1]));
|
||
table.push([total, k, s / N]);
|
||
prev = q;
|
||
}
|
||
}
|
||
var target = Math.max(0, Math.min(1, pct)) * total;
|
||
var i = 0;
|
||
while (i < table.length - 1 && table[i][0] < target) i++;
|
||
var e = table[i], seg = e[1];
|
||
var l0 = i > 0 ? table[i - 1][0] : 0, u0 = i > 0 && table[i - 1][1] === seg ? table[i - 1][2] : 0;
|
||
var u = e[0] > l0 ? u0 + (e[2] - u0) * (target - l0) / (e[0] - l0) : e[2];
|
||
if (!tangent) return __bezAt(segs[seg], u);
|
||
var d = __bezDer(segs[seg], u), len = Math.sqrt(d[0] * d[0] + d[1] * d[1]);
|
||
return len > 0 ? [d[0] / len, d[1] / len] : [1, 0];
|
||
}
|
||
|
||
function createPath(points, inTangents, outTangents, isClosed) {
|
||
points = points || [];
|
||
var zero = points.map(function () { return [0, 0]; });
|
||
return new __Path(points, inTangents || zero, outTangents || zero, isClosed === undefined ? true : isClosed);
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Object model.
|
||
|
||
function __mod(a, d) { return ((a % d) + d) % d; }
|
||
|
||
function __child(c, l, group, key) {
|
||
var info = __h(__CHILD, c, l, group, key);
|
||
if (info === null) return undefined;
|
||
return info[1] ? __mkGroup(c, l, info) : __mkProp(c, l, info[0]);
|
||
}
|
||
|
||
// `propertyGroup(countUp)` of the property or group at `path`: the group `countUp` levels above
|
||
// it (default 1), the layer at the top (#1619).
|
||
function __ancestor(c, l, path, countUp) {
|
||
var n = countUp === undefined ? 1 : __v(countUp);
|
||
if (typeof n !== 'number' || !(n >= 1) || !isFinite(n)) __err('propertyGroup(' + countUp + '): the count must be a number of 1 or more');
|
||
n = Math.floor(n);
|
||
var info = __h(__ANCESTOR, c, l, path, n);
|
||
if (info === 'layer') return __layer(c, l);
|
||
if (info === null) __err('propertyGroup(' + n + ') goes above the layer');
|
||
return __mkGroup(c, l, info);
|
||
}
|
||
|
||
// Comp ------------------------------------------------------------------------------------------
|
||
|
||
function Comp(c) { this.__c = c; this.__i = null; }
|
||
Comp.prototype = {
|
||
__isComp: true,
|
||
get __info() { return this.__i || (this.__i = __h(__COMP, this.__c)); },
|
||
get name() { return this.__info[0]; },
|
||
get width() { return this.__info[1]; },
|
||
get height() { return this.__info[2]; },
|
||
get duration() { return this.__info[3]; },
|
||
get frameDuration() { return this.__info[4]; },
|
||
get numLayers() { return this.__info[5]; },
|
||
get pixelAspect() { return this.__info[6]; },
|
||
get displayStartTime() { return this.__info[7]; },
|
||
get bgColor() { return this.__info[8]; },
|
||
get marker() { return new Markers(this.__c, -1); },
|
||
// The topmost enabled camera active at the current time.
|
||
get activeCamera() {
|
||
for (var i = 1; i <= this.numLayers; i++) {
|
||
var l = this.layer(i);
|
||
if (l.__info[10] === 'Camera' && l.active) return l;
|
||
}
|
||
return __err('Composition "' + this.name + '" has no active camera');
|
||
},
|
||
layer: function (a, rel) {
|
||
if (typeof a === 'function' && a.__isLayer) return this.layer(a.index + (rel || 0));
|
||
var id = __h(__LAYER, this.__c, a);
|
||
if (id === null) __err(typeof a === 'string' ? 'Layer named "' + a + '" does not exist' : 'Layer index ' + a + ' out of range');
|
||
return __layer(this.__c, id);
|
||
},
|
||
toString: function () { return '[object Comp]'; },
|
||
};
|
||
|
||
function comp(name) {
|
||
var id = __h(__COMPNAME, String(name));
|
||
if (id === null) __err('Comp named "' + name + '" does not exist');
|
||
return new Comp(id);
|
||
}
|
||
|
||
// Footage (data-driven animation) and the project ------------------------------------------------
|
||
|
||
function Footage(info) { this.__i = info; this.__d = null; }
|
||
Footage.prototype = {
|
||
__isFootage: true,
|
||
get name() { return this.__i[0]; },
|
||
get width() { return this.__i[1]; },
|
||
get height() { return this.__i[2]; },
|
||
get duration() { return this.__i[3]; },
|
||
get frameDuration() { return this.__i[4]; },
|
||
get pixelAspect() { return this.__i[5]; },
|
||
get ntscDropFrame() { return false; },
|
||
// The data file's text (JSON / CSV / TSV), or "" for media.
|
||
get sourceText() { return this.__i[7] === null ? '' : this.__i[7]; },
|
||
get __data() {
|
||
if (this.__d !== null) return this.__d;
|
||
var j = __h(__FDATA, this.__i[0]);
|
||
if (j === null) __err('Footage "' + this.name + '" has no data (or it could not be parsed)');
|
||
return (this.__d = JSON.parse(j));
|
||
},
|
||
// JSON: the parsed document; CSV/TSV: one object per row keyed by the header row.
|
||
get sourceData() { return this.__data.data; },
|
||
// CSV/TSV cell by [column, row] (0-based, rows after the header).
|
||
dataValue: function (idx) {
|
||
idx = __v(idx);
|
||
var rows = this.__data.rows;
|
||
if (rows === null) {
|
||
// JSON: a path of keys / indexes.
|
||
var v = this.__data.data;
|
||
for (var i = 0; i < idx.length; i++) {
|
||
if (v === null || v === undefined) __err('dataValue: no value at ' + JSON.stringify(idx));
|
||
v = v[idx[i]];
|
||
}
|
||
return v;
|
||
}
|
||
var r = rows[idx[1]];
|
||
if (r === undefined || r[idx[0]] === undefined) __err('dataValue: no cell at column ' + idx[0] + ', row ' + idx[1]);
|
||
return r[idx[0]];
|
||
},
|
||
get dataKeyCount() { var r = this.__data.rows; return r === null ? 0 : r.length; },
|
||
// The column names (CSV/TSV header row).
|
||
get dataKeyNames() { return this.__data.header || []; },
|
||
toString: function () { return '[object Footage]'; },
|
||
};
|
||
|
||
function footage(name) {
|
||
var info = __h(__FOOT, String(name));
|
||
if (info === null) __err('Footage named "' + name + '" does not exist');
|
||
return new Footage(info);
|
||
}
|
||
|
||
var __projectInfo = null;
|
||
// The project's bits per channel (8, 16 or 32).
|
||
__global('colorDepth', function () { return thisProject.bitsPerChannel; });
|
||
var thisProject = {
|
||
get __info() { return __projectInfo || (__projectInfo = __h(__PROJ)); },
|
||
get fullPath() { return ''; },
|
||
get bitsPerChannel() { return this.__info[0]; },
|
||
get linearBlending() { return this.__info[1]; },
|
||
toString: function () { return '[object Project]'; },
|
||
};
|
||
|
||
// Layer -----------------------------------------------------------------------------------------
|
||
|
||
// A layer is a property group too, as in After Effects: `thisLayer("transform")`, another layer's
|
||
// `("effects")("Slider Control")`, `thisLayer(1)` (what the pick whip writes with Compact English
|
||
// off). Callable like a group (`__mkGroup`), with the Layer methods.
|
||
function Layer(c, l) {
|
||
var f = function (k) { return f.__group(k); };
|
||
delete f.name;
|
||
delete f.length;
|
||
Object.setPrototypeOf(f, Layer.prototype);
|
||
// Kept across evaluations (`__layer`), so an expression can't change which layer it is, delete
|
||
// a field or leave its own on it. Its info is private to `__info`, which reads it again in an
|
||
// evaluation whose `__S` isn't the one it was read in (every evaluation has a new `__S`), so
|
||
// freezing the layer can't keep its info either.
|
||
var info = null, read = null;
|
||
Object.defineProperty(f, '__c', { value: c });
|
||
Object.defineProperty(f, '__l', { value: l });
|
||
Object.defineProperty(f, '__info', {
|
||
get: function () {
|
||
if (read !== __S) { info = __h(__LINFO, c, l); read = __S; }
|
||
return info;
|
||
},
|
||
});
|
||
Object.preventExtensions(f);
|
||
return f;
|
||
}
|
||
|
||
// Layer `l` of comp `c`, made once per runtime: a callable Layer is dearer to make than a plain
|
||
// object, so it is kept, and its info (`__info`) is read again in each evaluation.
|
||
// The kept layers are out of an expression's reach (their maps have no prototype, so a change to
|
||
// `Object.prototype` doesn't reach them either), and bounded like the runtime's scripts: once
|
||
// `max` are kept, the layers after them aren't, and the next evaluation starts over
|
||
// (`__layersTrim`, so a layer stays the same object for a whole evaluation).
|
||
var __layer, __layersTrim;
|
||
(function () {
|
||
var layers = Object.create(null), kept = 0, max = 4096;
|
||
__layer = function (c, l) {
|
||
// By comp, then layer: a `c + ':' + l` key costs more to build than the lookup saves.
|
||
var m = layers[c], f = m && m[l];
|
||
if (f) return f;
|
||
f = new Layer(c, l);
|
||
if (kept < max) {
|
||
(m || (layers[c] = Object.create(null)))[l] = f;
|
||
kept++;
|
||
}
|
||
return f;
|
||
};
|
||
__layersTrim = function () {
|
||
if (kept >= max) { layers = Object.create(null); kept = 0; }
|
||
};
|
||
})();
|
||
|
||
function __m3(m, p) {
|
||
var x = p[0] || 0, y = p[1] || 0;
|
||
var X = m[0] * x + m[1] * y + m[2], Y = m[3] * x + m[4] * y + m[5], W = m[6] * x + m[7] * y + m[8];
|
||
if (W !== 0 && W !== 1) { X /= W; Y /= W; }
|
||
return p.length > 2 ? [X, Y, 0] : [X, Y];
|
||
}
|
||
function __m3v(m, p) {
|
||
var x = p[0] || 0, y = p[1] || 0;
|
||
return p.length > 2 ? [m[0] * x + m[1] * y, m[3] * x + m[4] * y, 0] : [m[0] * x + m[1] * y, m[3] * x + m[4] * y];
|
||
}
|
||
function __m4(m, p, vec) {
|
||
var x = p[0] || 0, y = p[1] || 0, z = p[2] || 0, k = vec ? 0 : 1;
|
||
var X = m[0] * x + m[1] * y + m[2] * z + m[3] * k;
|
||
var Y = m[4] * x + m[5] * y + m[6] * z + m[7] * k;
|
||
var Z = m[8] * x + m[9] * y + m[10] * z + m[11] * k;
|
||
var W = vec ? 1 : m[12] * x + m[13] * y + m[14] * z + m[15];
|
||
if (W !== 0 && W !== 1) { X /= W; Y /= W; Z /= W; }
|
||
return [X, Y, Z];
|
||
}
|
||
|
||
Layer.prototype = {
|
||
__isLayer: true,
|
||
get name() { return this.__info[0]; },
|
||
get index() { return this.__info[1]; },
|
||
get inPoint() { return this.__info[2]; },
|
||
get outPoint() { return this.__info[3]; },
|
||
get startTime() { return this.__info[4]; },
|
||
get width() { return this.__info[5]; },
|
||
get height() { return this.__info[6]; },
|
||
get hasParent() { return this.__info[7] !== null; },
|
||
get parent() {
|
||
var p = this.__info[7];
|
||
if (p === null) __err('Layer "' + this.name + '" has no parent');
|
||
return __layer(this.__c, p);
|
||
},
|
||
get hasVideo() { return this.__info[8]; },
|
||
get enabled() { return this.__info[11]; },
|
||
get active() { return this.__info[11] && time >= this.inPoint && time < this.outPoint; },
|
||
get containingComp() { return new Comp(this.__c); },
|
||
// The source item (AE's Layer.source): a Comp for a precomp layer, a Footage for footage, solid
|
||
// and model layers, null for text, shape, null, camera and light layers (#809).
|
||
get source() {
|
||
var s = __h(__SOURCE, this.__c, this.__l);
|
||
if (s === null) return null;
|
||
if (s[0] === 'comp') return new Comp(s[1]);
|
||
return new Footage([s[2], s[3], s[4], s[5], s[6], s[7], s[0], null, null]);
|
||
},
|
||
__group: function (k) {
|
||
var g = __child(this.__c, this.__l, '', k);
|
||
if (g === undefined) __err('Layer "' + this.name + '" has no property "' + k + '"');
|
||
return g;
|
||
},
|
||
get transform() { return this.__group('transform'); },
|
||
get position() { return this.transform.position; },
|
||
get scale() { return this.transform.scale; },
|
||
get rotation() { return this.transform.rotation; },
|
||
get opacity() { return this.transform.opacity; },
|
||
get anchorPoint() { return this.transform.anchorPoint; },
|
||
get text() { return this.__group('text'); },
|
||
get cameraOption() { return this.__group('cameraOptions'); },
|
||
get lightOption() { return this.__group('lightOptions'); },
|
||
get marker() { return new Markers(this.__c, this.__l); },
|
||
effect: function (k) {
|
||
var g = __child(this.__c, this.__l, '', 'effects');
|
||
var e = g === undefined ? undefined : __child(g.__c, g.__l, g.__p, k);
|
||
if (e === undefined) __err('Effect ' + (typeof k === 'string' ? '"' + k + '"' : k) + ' not found on layer "' + this.name + '"');
|
||
return e;
|
||
},
|
||
mask: function (k) { return this.__group('masks').__get(k); },
|
||
content: function (k) { return this.__group('contents').__get(k); },
|
||
__x: function (t) { return __h(__XFORM, this.__c, this.__l, t === undefined ? time : t); },
|
||
toComp: function (p, t) { return __m3(this.__x(t)[0], __v(p)); },
|
||
fromComp: function (p, t) { return __m3(this.__x(t)[1], __v(p)); },
|
||
toWorld: function (p, t) { return __m4(this.__x(t)[2], __v(p), false); },
|
||
fromWorld: function (p, t) { return __m4(this.__x(t)[3], __v(p), false); },
|
||
toCompVec: function (p, t) { return __m3v(this.__x(t)[0], __v(p)); },
|
||
fromCompVec: function (p, t) { return __m3v(this.__x(t)[1], __v(p)); },
|
||
toWorldVec: function (p, t) { return __m4(this.__x(t)[2], __v(p), true); },
|
||
fromWorldVec: function (p, t) { return __m4(this.__x(t)[3], __v(p), true); },
|
||
sourceRectAtTime: function (t, includeExtents) {
|
||
var r = __h(__RECT, this.__c, this.__l, t === undefined ? time : t, !!includeExtents);
|
||
return { top: r[0], left: r[1], width: r[2], height: r[3] };
|
||
},
|
||
// Average straight RGBA of the layer's pixels in `point ± radius` (layer space).
|
||
sampleImage: function (point, radius, postEffect, t) {
|
||
point = __v(point);
|
||
radius = radius === undefined ? [0.5, 0.5] : __v(radius);
|
||
if (!Array.isArray(radius)) radius = [radius, radius];
|
||
return __h(__SAMPLE, this.__c, this.__l, point[0], point[1], radius[0], radius.length > 1 ? radius[1] : radius[0],
|
||
postEffect === undefined ? true : !!__v(postEffect), t === undefined ? time : __v(t));
|
||
},
|
||
toString: function () { return '[object Layer]'; },
|
||
};
|
||
|
||
// Property groups (callable: `effect("Slider Control")("Slider")`) ---------------------------------
|
||
|
||
var __GroupProto = {
|
||
__isGroup: true,
|
||
__get: function (k) {
|
||
var r = __child(this.__c, this.__l, this.__p, k);
|
||
if (r === undefined) __err('Property ' + (typeof k === 'string' ? '"' + k + '"' : k) + ' not found in "' + this.__info[2] + '"');
|
||
return r;
|
||
},
|
||
param: function (k) { return this.__get(k); },
|
||
property: function (k) { return this.__get(k); },
|
||
content: function (k) {
|
||
var inner = __child(this.__c, this.__l, this.__p, 'contents');
|
||
return (inner !== undefined && inner.__isGroup ? inner : this).__get(k);
|
||
},
|
||
get name() { return this.__info[2]; },
|
||
get matchName() { return this.__info[3]; },
|
||
get numProperties() { return this.__info[4]; },
|
||
get propertyIndex() { return this.__info[5]; },
|
||
propertyGroup: function (n) { return __ancestor(this.__c, this.__l, this.__p, n); },
|
||
get active() { return true; },
|
||
toString: function () { return '[object PropertyGroup]'; },
|
||
};
|
||
|
||
var __groupHandler = {
|
||
get: function (t, k) {
|
||
if (typeof k !== 'string' || k in t) return t[k];
|
||
return __child(t.__c, t.__l, t.__p, k);
|
||
},
|
||
};
|
||
|
||
// `Proxy` is private to the object model (see "Stack safety" at the end).
|
||
var __mkGroup = (function (P) {
|
||
return function (c, l, info) {
|
||
var f = function (k) { return f.__get(k); };
|
||
delete f.name;
|
||
delete f.length;
|
||
Object.setPrototypeOf(f, __GroupProto);
|
||
f.__c = c; f.__l = l; f.__p = info[0]; f.__info = info;
|
||
return new P(f, __groupHandler);
|
||
};
|
||
})(Proxy);
|
||
|
||
// Properties ------------------------------------------------------------------------------------
|
||
|
||
function Prop(c, l, path) {
|
||
this.__c = c; this.__l = l; this.__p = path; this.__i = null;
|
||
this.__self = __S !== null && c === __S.c && l === __S.l && path === __S.path;
|
||
}
|
||
|
||
function __avg(vals) {
|
||
var s = vals[0];
|
||
for (var i = 1; i < vals.length; i++) s = add(s, vals[i]);
|
||
return div(s, vals.length);
|
||
}
|
||
|
||
function __loop(p, out, type, n, dur) {
|
||
type = type === undefined ? 'cycle' : String(type).toLowerCase();
|
||
var times = p.__info[4], N = times.length, t = time;
|
||
if (N === 0) return p.value;
|
||
var tStart, tEnd, h = 1e-3, k, ph;
|
||
if (out) {
|
||
tEnd = times[N - 1];
|
||
if (t <= tEnd) return p.value;
|
||
if (dur !== undefined && dur > 0) tStart = tEnd - dur;
|
||
else if (n > 0 && N - 1 - n >= 0) tStart = times[N - 1 - n];
|
||
else tStart = times[0];
|
||
} else {
|
||
tStart = times[0];
|
||
if (t >= tStart) return p.value;
|
||
if (dur !== undefined && dur > 0) tEnd = tStart + dur;
|
||
else if (n > 0 && n < N) tEnd = times[n];
|
||
else tEnd = times[N - 1];
|
||
}
|
||
var d = tEnd - tStart;
|
||
if (type === 'continue') {
|
||
if (out) {
|
||
var ve = p.valueAtTime(tEnd);
|
||
return add(ve, mul(div(sub(ve, p.valueAtTime(tEnd - h)), h), t - tEnd));
|
||
}
|
||
var vs = p.valueAtTime(tStart);
|
||
return add(vs, mul(div(sub(p.valueAtTime(tStart + h), vs), h), t - tStart));
|
||
}
|
||
if (!(d > 0)) return p.value;
|
||
ph = __mod(t - tStart, d);
|
||
if (type === 'pingpong') {
|
||
if (out) {
|
||
k = Math.floor((t - tStart) / d);
|
||
return p.valueAtTime(k % 2 === 1 ? tEnd - ph : tStart + ph);
|
||
}
|
||
k = Math.floor((tEnd - t) / d);
|
||
return p.valueAtTime(k % 2 === 1 ? tStart + __mod(tStart - t, d) : tEnd - __mod(tStart - t, d));
|
||
}
|
||
if (type === 'offset') {
|
||
k = Math.floor((t - tStart) / d);
|
||
return add(p.valueAtTime(tStart + ph), mul(sub(p.valueAtTime(tEnd), p.valueAtTime(tStart)), k));
|
||
}
|
||
return p.valueAtTime(tStart + ph);
|
||
}
|
||
|
||
Prop.prototype = {
|
||
__isProp: true,
|
||
get __info() { return this.__i || (this.__i = __h(__PINFO, this.__c, this.__l, this.__p)); },
|
||
get name() { return this.__info[0]; },
|
||
get __uid() { return this.__info[1]; },
|
||
get numKeys() { return this.__info[4].length; },
|
||
get propertyIndex() { return this.__info[7]; },
|
||
get matchName() { return this.__info[8]; },
|
||
propertyGroup: function (n) { return __ancestor(this.__c, this.__l, this.__p, n); },
|
||
__wrap: function (v) {
|
||
var kind = this.__info[2];
|
||
if (kind === 'layer') return v === null ? null : __layer(this.__c, v);
|
||
if (kind === 'path' && v !== null) return new __Path(v[0], v[1], v[2], v[3]);
|
||
return v;
|
||
},
|
||
get value() { return this.__self ? value : this.valueAtTime(time); },
|
||
valueAtTime: function (t) {
|
||
t = __v(t);
|
||
if (this.__self && t === time) return value;
|
||
return this.__wrap(__h(__VALUE, this.__c, this.__l, this.__p, t, this.__self));
|
||
},
|
||
velocityAtTime: function (t) {
|
||
var h = 1e-3;
|
||
return div(sub(this.valueAtTime(t + h), this.valueAtTime(t - h)), 2 * h);
|
||
},
|
||
get velocity() { return this.velocityAtTime(time); },
|
||
speedAtTime: function (t) { return length(this.velocityAtTime(t)); },
|
||
get speed() { return this.speedAtTime(time); },
|
||
key: function (n) {
|
||
var info = this.__info, N = info[4].length;
|
||
if (!(n >= 1 && n <= N)) __err('Key index ' + n + ' out of range (property has ' + N + ' keys)');
|
||
n = Math.floor(n);
|
||
return { time: info[4][n - 1], value: this.__wrap(info[5][n - 1]), index: n };
|
||
},
|
||
nearestKey: function (t) {
|
||
var times = this.__info[4];
|
||
if (times.length === 0) __err('Property has no keyframes');
|
||
var best = 0;
|
||
for (var i = 1; i < times.length; i++) if (Math.abs(times[i] - t) < Math.abs(times[best] - t)) best = i;
|
||
return this.key(best + 1);
|
||
},
|
||
__seedIndex: function () { return this.__self ? __S.idx : __layer(this.__c, this.__l).index; },
|
||
wiggle: function (freq, amp, octaves, ampMult, t) {
|
||
if (octaves === undefined) octaves = 1;
|
||
if (ampMult === undefined) ampMult = 0.5;
|
||
if (t === undefined) t = time;
|
||
var base = this.valueAtTime(t);
|
||
var dims = Array.isArray(base) ? base.length : 1;
|
||
var off = __wig(this.__seedIndex(), this.__uid, dims, __v(freq), __v(amp), octaves, ampMult, t);
|
||
if (!Array.isArray(base)) return base + off[0];
|
||
var r = base.slice();
|
||
for (var i = 0; i < r.length; i++) r[i] += off[i];
|
||
return r;
|
||
},
|
||
temporalWiggle: function (freq, amp, octaves, ampMult, t) {
|
||
if (octaves === undefined) octaves = 1;
|
||
if (ampMult === undefined) ampMult = 0.5;
|
||
if (t === undefined) t = time;
|
||
var off = __wig(this.__seedIndex(), this.__uid, 1, __v(freq), __v(amp), octaves, ampMult, t);
|
||
return this.valueAtTime(t + off[0]);
|
||
},
|
||
smooth: function (width, samples, t) {
|
||
if (width === undefined) width = 0.2;
|
||
if (samples === undefined) samples = 5;
|
||
if (t === undefined) t = time;
|
||
samples = Math.max(1, Math.round(samples));
|
||
if (samples === 1) return this.valueAtTime(t);
|
||
var vals = [];
|
||
for (var i = 0; i < samples; i++) vals.push(this.valueAtTime(t - width / 2 + width * i / (samples - 1)));
|
||
return __avg(vals);
|
||
},
|
||
loopOut: function (type, n) { return __loop(this, true, type, n || 0); },
|
||
loopIn: function (type, n) { return __loop(this, false, type, n || 0); },
|
||
loopOutDuration: function (type, d) { return __loop(this, true, type, 0, d === undefined ? 0 : d); },
|
||
loopInDuration: function (type, d) { return __loop(this, false, type, 0, d === undefined ? 0 : d); },
|
||
points: function (t) { return this.valueAtTime(t === undefined ? time : t).points(); },
|
||
inTangents: function (t) { return this.valueAtTime(t === undefined ? time : t).inTangents(); },
|
||
outTangents: function (t) { return this.valueAtTime(t === undefined ? time : t).outTangents(); },
|
||
isClosed: function () { return this.value.isClosed(); },
|
||
pointOnPath: function (pct, t) { return this.valueAtTime(t === undefined ? time : t).pointOnPath(pct); },
|
||
tangentOnPath: function (pct, t) { return this.valueAtTime(t === undefined ? time : t).tangentOnPath(pct); },
|
||
normalOnPath: function (pct, t) { return this.valueAtTime(t === undefined ? time : t).normalOnPath(pct); },
|
||
// Source Text style API.
|
||
__docAt: function (t) {
|
||
var d = __h(__DOC, this.__c, this.__l, this.__p, t, this.__self);
|
||
if (d === null) __err('Property "' + this.name + '" is not a Source Text property');
|
||
return JSON.parse(d);
|
||
},
|
||
get style() { return this.getStyleAt(0); },
|
||
getStyleAt: function (i, t) {
|
||
if (t === undefined) t = time;
|
||
return new __TextStyle(this.__docAt(__v(t)), i === undefined ? 0 : Math.floor(__v(i)), [], false);
|
||
},
|
||
createStyle: function () { return new __TextStyle(this.__docAt(time), 0, [], true); },
|
||
valueOf: function () { return this.value; },
|
||
toString: function () { return String(this.value); },
|
||
};
|
||
|
||
var __propHandler = {
|
||
get: function (t, k) {
|
||
if (typeof k !== 'string') return t[k];
|
||
// A layer-valued property (a Layer Control's Layer) is named as its layer (#809).
|
||
if (k === 'name' && t.__info[2] === 'layer') {
|
||
var lv = t.value;
|
||
if (lv?.__isLayer) return lv.name;
|
||
}
|
||
if (k in t) return t[k];
|
||
var v = t.value;
|
||
if (v === null || v === undefined) return undefined;
|
||
var x = v[k];
|
||
// Property groups and layers are callable: hand them over as they are, not bound like methods
|
||
// (#809).
|
||
if (typeof x === 'function' && (x.__isGroup || x.__isLayer)) return x;
|
||
return typeof x === 'function' ? x.bind(v) : x;
|
||
},
|
||
};
|
||
|
||
var __mkProp = (function (P) {
|
||
return function (c, l, path) {
|
||
return new P(new Prop(c, l, path), __propHandler);
|
||
};
|
||
})(Proxy);
|
||
|
||
// Text styles (the Source Text style API) ------------------------------------------------------
|
||
// A TextStyle is the source document (as JSON from the host), the character index it reads its
|
||
// attributes at, and the setter calls made on it ([key, value, start, count]). Setters return
|
||
// new objects; returning a style from a Source Text expression applies it.
|
||
|
||
function __TextStyle(doc, idx, ops, empty) { this.__d = doc; this.__i = idx; this.__ops = ops; this.__empty = empty; }
|
||
|
||
function __styleSetter(key, conv) {
|
||
return function (v, start, count) {
|
||
v = __v(v);
|
||
if (conv) v = conv(v, this);
|
||
var s = start === undefined ? -1 : Math.max(0, Math.floor(__v(start)));
|
||
var n = count === undefined ? -1 : Math.max(0, Math.floor(__v(count)));
|
||
return new __TextStyle(this.__d, this.__i, this.__ops.concat([[key, v, s, n]]), this.__empty);
|
||
};
|
||
}
|
||
|
||
function __colorIn(c) {
|
||
if (typeof c === 'string') return c;
|
||
return [c[0], c[1], c[2]];
|
||
}
|
||
|
||
__TextStyle.prototype = {
|
||
__isTextStyle: true,
|
||
__base: function () {
|
||
var runs = this.__d.runs, pos = 0, i = this.__i;
|
||
for (var k = 0; k < runs.length; k++) {
|
||
if (i < pos + runs[k].len || k === runs.length - 1) return runs[k].style;
|
||
pos += runs[k].len;
|
||
}
|
||
return {};
|
||
},
|
||
__para: function () {
|
||
var t = this.text, p = 0;
|
||
for (var k = 0; k < Math.min(this.__i, t.length); k++) if (t[k] === '\n' || t[k] === '\r') p++;
|
||
var paras = this.__d.paras;
|
||
return paras[Math.min(p, paras.length - 1)];
|
||
},
|
||
__get: function (key, para) {
|
||
for (var k = this.__ops.length - 1; k >= 0; k--) {
|
||
var o = this.__ops[k];
|
||
if (o[0] !== key) continue;
|
||
if (o[2] < 0 || (this.__i >= o[2] && (o[3] < 0 || this.__i < o[2] + o[3]))) return o[1];
|
||
}
|
||
return (para ? this.__para() : this.__base())[key];
|
||
},
|
||
get text() {
|
||
var t = this.__d.text;
|
||
for (const elem of this.__ops) {
|
||
var o = elem;
|
||
if (o[0] !== 'text') continue;
|
||
if (o[2] < 0) t = String(o[1]);
|
||
else t = t.slice(0, o[2]) + String(o[1]) + (o[3] < 0 ? '' : t.slice(o[2] + o[3]));
|
||
}
|
||
return t;
|
||
},
|
||
get font() { return this.__get('font'); },
|
||
get fontSize() { return this.__get('size'); },
|
||
get isFauxBold() { return this.__get('fauxBold'); },
|
||
get isFauxItalic() { return this.__get('fauxItalic'); },
|
||
get isAllCaps() { return this.__get('allCaps'); },
|
||
get isSmallCaps() { return this.__get('smallCaps'); },
|
||
get tracking() { return this.__get('tracking'); },
|
||
get autoLeading() { return this.__get('leading') === 'auto'; },
|
||
get leading() { var l = this.__get('leading'); return l === 'auto' ? this.fontSize * 1.2 : l; },
|
||
get baselineShift() { return this.__get('baselineShift'); },
|
||
get applyFill() { return this.__get('applyFill'); },
|
||
get fillColor() { var c = this.__get('fill'); return [c[0], c[1], c[2]]; },
|
||
get applyStroke() { return this.__get('applyStroke'); },
|
||
get strokeColor() { var c = this.__get('stroke'); return [c[0], c[1], c[2]]; },
|
||
get strokeWidth() { return this.__get('strokeWidth'); },
|
||
get horizontalScaling() { return this.__get('hScale'); },
|
||
get verticalScaling() { return this.__get('vScale'); },
|
||
get tsume() { return this.__get('tsume'); },
|
||
get baselineOption() { return this.__get('baseline'); },
|
||
get isSuperscript() { return this.__get('baseline') === 'superscript'; },
|
||
get isSubscript() { return this.__get('baseline') === 'subscript'; },
|
||
get kerningType() { var k = this.__get('kerning'); return typeof k === 'number' ? 'manual' : k; },
|
||
get kerning() { var k = this.__get('kerning'); return typeof k === 'number' ? k : 0; },
|
||
get isLigature() { return this.__get('ligatures'); },
|
||
get justification() { return this.__get('justify', true); },
|
||
get firstLineIndent() { return this.__get('indentFirst', true); },
|
||
get startIndent() { return this.__get('indentLeft', true); },
|
||
get endIndent() { return this.__get('indentRight', true); },
|
||
get spaceBefore() { return this.__get('spaceBefore', true); },
|
||
get spaceAfter() { return this.__get('spaceAfter', true); },
|
||
get direction() { return this.__get('direction', true); },
|
||
get isEveryLineComposer() { return this.__get('composer', true) === 'everyLine'; },
|
||
get isHangingRoman() { return this.__get('hangingPunctuation', true); },
|
||
setText: __styleSetter('text', function (v) { return String(v); }),
|
||
replaceText: __styleSetter('text', function (v) { return String(v); }),
|
||
setFont: __styleSetter('font', function (v) { return String(v); }),
|
||
setFontSize: __styleSetter('size'),
|
||
setFauxBold: __styleSetter('fauxBold', Boolean),
|
||
setFauxItalic: __styleSetter('fauxItalic', Boolean),
|
||
setAllCaps: __styleSetter('allCaps', Boolean),
|
||
setSmallCaps: __styleSetter('smallCaps', Boolean),
|
||
setTracking: __styleSetter('tracking'),
|
||
setLeading: __styleSetter('leading'),
|
||
setAutoLeading: __styleSetter('leading', function (v, s) { return v ? 'auto' : s.leading; }),
|
||
setBaselineShift: __styleSetter('baselineShift'),
|
||
setApplyFill: __styleSetter('applyFill', Boolean),
|
||
setFillColor: __styleSetter('fill', __colorIn),
|
||
setApplyStroke: __styleSetter('applyStroke', Boolean),
|
||
setStrokeColor: __styleSetter('stroke', __colorIn),
|
||
setStrokeWidth: __styleSetter('strokeWidth'),
|
||
setHorizontalScaling: __styleSetter('hScale'),
|
||
setVerticalScaling: __styleSetter('vScale'),
|
||
setTsume: __styleSetter('tsume'),
|
||
setBaselineOption: __styleSetter('baseline', function (v) { return String(v).toLowerCase().replace('_baseline', ''); }),
|
||
// Canonical baseline operations also make the returned TextStyle's getters reflect
|
||
// the pending edit, rather than waiting for the host to apply a superscript flag.
|
||
setSuperscript: __styleSetter('baseline', function (v) { return v ? 'superscript' : 'normal'; }),
|
||
setSubscript: __styleSetter('baseline', function (v) { return v ? 'subscript' : 'normal'; }),
|
||
setKerningType: __styleSetter('kerning', function (v) { v = String(v).toLowerCase(); return v === 'manual' ? 0 : v; }),
|
||
setKerning: __styleSetter('kerning', Number),
|
||
setLigature: __styleSetter('ligatures', Boolean),
|
||
setJustification: __styleSetter('justify', function (v) { return String(v); }),
|
||
setFirstLineIndent: __styleSetter('indentFirst'),
|
||
setStartIndent: __styleSetter('indentLeft'),
|
||
setEndIndent: __styleSetter('indentRight'),
|
||
setSpaceBefore: __styleSetter('spaceBefore'),
|
||
setSpaceAfter: __styleSetter('spaceAfter'),
|
||
setDirection: __styleSetter('direction', function (v) { v = String(v).toLowerCase(); return v.indexOf('right') === 0 || v === 'rtl' ? 'rtl' : 'ltr'; }),
|
||
setEveryLineComposer: __styleSetter('composer', function (v) { return v ? 'everyLine' : 'singleLine'; }),
|
||
setHangingRoman: __styleSetter('hangingPunctuation', Boolean),
|
||
__out: function () {
|
||
return { __style: true, __json: JSON.stringify({ doc: this.__empty ? null : this.__d.doc, ops: this.__ops }) };
|
||
},
|
||
toString: function () { return this.text; },
|
||
valueOf: function () { return this.text; },
|
||
};
|
||
Object.defineProperty(__TextStyle.prototype, 'length', { get: function () { return this.text.length; } });
|
||
|
||
// Markers ---------------------------------------------------------------------------------------
|
||
|
||
function Markers(c, l) { this.__c = c; this.__l = l; this.__m = null; }
|
||
Markers.prototype = {
|
||
get __list() { return this.__m || (this.__m = __h(__MARKERS, this.__c, this.__l)); },
|
||
get numKeys() { return this.__list.length; },
|
||
key: function (n) {
|
||
var list = this.__list, i;
|
||
if (typeof n === 'string') {
|
||
for (i = 0; i < list.length; i++) if (list[i][2] === n) return this.key(i + 1);
|
||
__err('Marker "' + n + '" not found');
|
||
}
|
||
if (!(n >= 1 && n <= list.length)) __err('Marker index ' + n + ' out of range');
|
||
var m = list[n - 1];
|
||
var params = {};
|
||
for (var k = 0; k < m[8].length; k++) params[m[8][k][0]] = m[8][k][1];
|
||
return {
|
||
time: m[0], duration: m[1], comment: m[2], chapter: m[3], url: m[4], index: n,
|
||
frameTarget: m[5], eventCuePoint: m[6], cuePointName: m[7], parameters: params, protectedRegion: m[9],
|
||
};
|
||
},
|
||
nearestKey: function (t) {
|
||
var list = this.__list;
|
||
if (list.length === 0) __err('No markers');
|
||
var best = 0;
|
||
for (var i = 1; i < list.length; i++) if (Math.abs(list[i][0] - t) < Math.abs(list[best][0] - t)) best = i;
|
||
return this.key(best + 1);
|
||
},
|
||
};
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Unqualified names: thisLayer / thisProperty attributes and the `this*` objects. They're
|
||
// accessors on the global object; assigning one (`var width = 10`) shadows it for the current
|
||
// evaluation only. They can't be deleted or redefined, so an expression can't take one away from
|
||
// the evaluations after it.
|
||
|
||
function __global(name, get) {
|
||
Object.defineProperty(globalThis, name, {
|
||
get: function () { return name in __S.vars ? __S.vars[name] : get(); },
|
||
set: function (v) { __S.vars[name] = v; },
|
||
});
|
||
}
|
||
|
||
__global('thisComp', function () { return __S.comp || (__S.comp = new Comp(__S.c)); });
|
||
__global('thisLayer', function () { return __S.layer || (__S.layer = __layer(__S.c, __S.l)); });
|
||
__global('thisProperty', function () { return __S.prop || (__S.prop = __mkProp(__S.c, __S.l, __S.path)); });
|
||
['name', 'index', 'inPoint', 'outPoint', 'startTime', 'width', 'height', 'hasParent', 'parent', 'hasVideo', 'enabled', 'active', 'source',
|
||
'transform', 'position', 'scale', 'rotation', 'opacity', 'anchorPoint', 'text', 'marker'].forEach(function (k) {
|
||
__global(k, function () { return thisLayer[k]; });
|
||
});
|
||
['numKeys', 'velocity', 'speed'].forEach(function (k) {
|
||
__global(k, function () { return thisProperty[k]; });
|
||
});
|
||
|
||
function effect(k) { return thisLayer.effect(k); }
|
||
function mask(k) { return thisLayer.mask(k); }
|
||
function content(k) { return thisLayer.content(k); }
|
||
function toComp(p, t) { return thisLayer.toComp(p, t); }
|
||
function fromComp(p, t) { return thisLayer.fromComp(p, t); }
|
||
function toWorld(p, t) { return thisLayer.toWorld(p, t); }
|
||
function fromWorld(p, t) { return thisLayer.fromWorld(p, t); }
|
||
function toCompVec(p, t) { return thisLayer.toCompVec(p, t); }
|
||
function fromCompVec(p, t) { return thisLayer.fromCompVec(p, t); }
|
||
function toWorldVec(p, t) { return thisLayer.toWorldVec(p, t); }
|
||
function fromWorldVec(p, t) { return thisLayer.fromWorldVec(p, t); }
|
||
function sourceRectAtTime(t, e) { return thisLayer.sourceRectAtTime(t, e); }
|
||
|
||
function wiggle(f, a, o, m, t) { return thisProperty.wiggle(f, a, o, m, t); }
|
||
function temporalWiggle(f, a, o, m, t) { return thisProperty.temporalWiggle(f, a, o, m, t); }
|
||
function smooth(w, s, t) { return thisProperty.smooth(w, s, t); }
|
||
function loopOut(type, n) { return thisProperty.loopOut(type, n); }
|
||
function loopIn(type, n) { return thisProperty.loopIn(type, n); }
|
||
function loopOutDuration(type, d) { return thisProperty.loopOutDuration(type, d); }
|
||
function loopInDuration(type, d) { return thisProperty.loopInDuration(type, d); }
|
||
function valueAtTime(t) { return thisProperty.valueAtTime(t); }
|
||
function velocityAtTime(t) { return thisProperty.velocityAtTime(t); }
|
||
function speedAtTime(t) { return thisProperty.speedAtTime(t); }
|
||
function key(n) { return thisProperty.key(n); }
|
||
function nearestKey(t) { return thisProperty.nearestKey(t); }
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Stack safety (#2124, #2125). A stack overflow aborts the whole app, so nothing a script does
|
||
// may nest native code without bound. The expression text is checked for nesting before it is
|
||
// compiled; source handed to `eval` and the function constructors at run time is checked by
|
||
// `__nest`. The natives that walk nested values recursively (`join`, which `toString` and string
|
||
// conversion use, `toLocaleString`, `JSON.stringify`, `flat`) call `__stack` once per level,
|
||
// which throws once the run has used its share of the stack. `Proxy` (chains of proxies nest
|
||
// without any script frame) and `Reflect` are not available to expressions.
|
||
(function () {
|
||
var nest = __nest, stack = __stack, apply = Reflect.apply, construct = Reflect.construct;
|
||
var define = Object.defineProperty, describe = Object.getOwnPropertyDescriptor, proto = Object.getPrototypeOf;
|
||
// Replace `obj[name]` keeping its attributes.
|
||
function replace(obj, name, f) {
|
||
var d = describe(obj, name);
|
||
define(obj, name, { value: f, writable: d.writable, enumerable: d.enumerable, configurable: true });
|
||
}
|
||
function guard(obj, name) {
|
||
var f = obj[name];
|
||
replace(obj, name, function () { stack(); return apply(f, this, arguments); });
|
||
}
|
||
|
||
// Indirect on purpose: a wrapper can't make a direct eval, so `eval` sees globals and `var`s
|
||
// but not `let`/`const` locals of the expression. Keeping direct eval would mean leaving it
|
||
// unchecked; expressions practically never need their locals inside eval.
|
||
var evalJs = eval;
|
||
eval = function (x) {
|
||
if (typeof x === 'string') {
|
||
stack();
|
||
nest(x);
|
||
}
|
||
return evalJs(x);
|
||
};
|
||
// `Function` and the generator / async variants (reachable as `(function* () {}).constructor`).
|
||
function dynamic(F) {
|
||
var w = function () {
|
||
var args = [];
|
||
for (var i = 0; i < arguments.length; i++) args.push(String(arguments[i]));
|
||
stack();
|
||
nest(args.slice(0, -1).join(','));
|
||
nest(args.length ? args[args.length - 1] : '');
|
||
return construct(F, args);
|
||
};
|
||
define(w, 'name', { value: F.name, configurable: true });
|
||
w.prototype = F.prototype;
|
||
replace(F.prototype, 'constructor', w);
|
||
return w;
|
||
}
|
||
Function = dynamic(Function);
|
||
dynamic(proto(function* () {}).constructor);
|
||
dynamic(proto(async function () {}).constructor);
|
||
dynamic(proto(async function* () {}).constructor);
|
||
|
||
guard(Array.prototype, 'join');
|
||
guard(Array.prototype, 'toLocaleString');
|
||
var flat = Array.prototype.flat;
|
||
replace(Array.prototype, 'flat', function (depth) {
|
||
stack();
|
||
var d = depth === undefined ? 1 : Number(depth);
|
||
return apply(flat, this, [d > 64 ? 64 : d]);
|
||
});
|
||
|
||
// A replacer function sees every value before it is serialized: the per-level check. An array
|
||
// replacer (a list of keys) becomes one that copies each object's listed keys, in list order.
|
||
// Each source object gets one copy per call, so a cycle in the source is a cycle of copies and
|
||
// the native check reports it (TypeError); an object shared without a cycle is still fine.
|
||
var stringify = JSON.stringify, MapJs = Map;
|
||
replace(JSON, 'stringify', function (value, replacer, space) {
|
||
var fn = typeof replacer === 'function' ? replacer : null;
|
||
var keys = null, copies = new MapJs();
|
||
if (Array.isArray(replacer)) {
|
||
keys = [];
|
||
for (var i = 0; i < replacer.length; i++) {
|
||
var k = replacer[i];
|
||
if (typeof k === 'number' || k instanceof Number || k instanceof String) k = String(k);
|
||
if (typeof k === 'string' && keys.indexOf(k) < 0) keys.push(k);
|
||
}
|
||
}
|
||
return apply(stringify, JSON, [value, function (key, v) {
|
||
stack();
|
||
if (fn !== null) v = apply(fn, this, [key, v]);
|
||
if (keys !== null && v !== null && typeof v === 'object' && !Array.isArray(v) &&
|
||
!(v instanceof Number || v instanceof String || v instanceof Boolean)) {
|
||
var o = copies.get(v);
|
||
if (o === undefined) {
|
||
o = {};
|
||
for (var j = 0; j < keys.length; j++) if (keys[j] in v) o[keys[j]] = v[keys[j]];
|
||
copies.set(v, o);
|
||
}
|
||
v = o;
|
||
}
|
||
return v;
|
||
}, space]);
|
||
});
|
||
|
||
delete globalThis.Proxy;
|
||
delete globalThis.Reflect;
|
||
})();
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// The runtime runs this file once and keeps it for the thread's later evaluations, so an
|
||
// expression can't change the object model or the internals: the prototypes are frozen, and the
|
||
// `__` globals, the constructors and `thisProject` can't be reassigned. The `__` state below is
|
||
// set again by `__begin` in each evaluation.
|
||
|
||
[Comp, Footage, Layer, Prop, Markers, __Path, __TextStyle].forEach(function (f) {
|
||
Object.freeze(f.prototype);
|
||
Object.freeze(f);
|
||
});
|
||
[__GroupProto, __groupHandler, __propHandler, thisProject].forEach(Object.freeze);
|
||
(function () {
|
||
var perEvaluation = { __S: true, __rc: true, __seed: true, __timeless: true, __projectInfo: true };
|
||
var model = { Comp: true, Footage: true, Layer: true, Prop: true, Markers: true, thisProject: true };
|
||
Object.getOwnPropertyNames(globalThis).forEach(function (k) {
|
||
if (!(k.slice(0, 2) === '__' && !perEvaluation[k]) && !model[k]) return;
|
||
if ('value' in Object.getOwnPropertyDescriptor(globalThis, k)) Object.defineProperty(globalThis, k, { writable: false, configurable: false });
|
||
});
|
||
})();
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// One runtime serves every expression on a thread, and user code runs as a script at global
|
||
// scope, so `linear = …` or a top-level `var clamp` would otherwise rebind a prelude name for
|
||
// every later expression, and for every later project in the session (#2133). The block above
|
||
// closes the `__` globals, the model constructors and the prelude's own prototypes; what is left
|
||
// is the rest of the global surface: the prelude's own helpers (`linear`, `clamp`, `wiggle`), the
|
||
// JavaScript built-ins that an expression and the prelude both call, and the prototypes those
|
||
// hang off. A non-writable, non-configurable binding makes such a write a silent no-op in sloppy
|
||
// mode, and `var` leaves an existing global property alone. A name the user introduces is not
|
||
// frozen, so an expression that declares its own functions still works.
|
||
(function () {
|
||
var g = globalThis;
|
||
// Every intrinsic this block needs, read once before the first prototype is rewritten. The
|
||
// block turns `Array.prototype.concat` and `forEach` into accessors as it goes, so reaching for
|
||
// them through a prototype part-way down the list would be reaching for something it has just
|
||
// changed under itself: the loops below index arrays directly for the same reason.
|
||
var describe = Object.getOwnPropertyDescriptor, define = Object.defineProperty;
|
||
var getNames = Object.getOwnPropertyNames, getSyms = Object.getOwnPropertySymbols;
|
||
var freeze = Object.freeze, isFrozen = Object.isFrozen, isExtensible = Object.isExtensible;
|
||
var protoOf = Object.getPrototypeOf;
|
||
|
||
var lock = function (n) {
|
||
var d = describe(g, n);
|
||
if (!d || !d.writable) return;
|
||
define(g, n, { value: d.value, writable: false, enumerable: d.enumerable, configurable: false });
|
||
};
|
||
|
||
// `name` and `message` on an error's prototype are the two names the repair below has to leave
|
||
// alone. The host renders a thrown error by reading both straight off the object, with no
|
||
// context to call a getter with, and it has no way to run one safely either: the time budget is
|
||
// checked between polls of the evaluation, and formatting happens after that returns, so a
|
||
// getter of the expression's own would run outside it — bounded by the loop and recursion limits
|
||
// at some hundreds of millions of iterations, which is a hang. Left as data properties they are
|
||
// frozen rather than repaired, so `e.name = 'MyError'` on an expression's own error is dropped
|
||
// in silence. That is the cost; an unbounded getter in the failure path is not worth paying to
|
||
// avoid it.
|
||
var HOST_READ = ['name', 'message'];
|
||
var hostRead = [];
|
||
var errorNames = ['Error', 'EvalError', 'RangeError', 'ReferenceError', 'SyntaxError', 'TypeError',
|
||
'URIError', 'AggregateError'];
|
||
for (var e = 0; e < errorNames.length; e++) {
|
||
var C = g[errorNames[e]];
|
||
if (typeof C === 'function') {
|
||
var cd = describe(C, 'prototype');
|
||
if (cd && 'value' in cd) hostRead.push(cd.value);
|
||
}
|
||
}
|
||
var readByHost = function (p, n) {
|
||
for (var i = 0; i < HOST_READ.length; i++) {
|
||
if (n !== HOST_READ[i]) continue;
|
||
for (var j = 0; j < hostRead.length; j++) if (hostRead[j] === p) return true;
|
||
}
|
||
return false;
|
||
};
|
||
|
||
// Freezing a prototype costs an expression the right to shadow an inherited name on its own
|
||
// object: once the inherited data property is non-writable, `o.toString = …` is dropped in
|
||
// sloppy mode rather than creating an own property — the "override mistake". That is ordinary
|
||
// expression code: `counts[w]` with `w` of "constructor" or "valueOf" uses an object as a
|
||
// dictionary, and `a.map = …` gives one array its own version of a method. So every name that
|
||
// was shadowable before the freeze becomes an accessor whose setter writes an own property on
|
||
// the instance, which leaves the shadowing that worked before this block working after it.
|
||
var tolerate = function (p, n) {
|
||
var d = describe(p, n);
|
||
// An accessor has no value to stand in for, and a name already non-writable or
|
||
// non-configurable could not be shadowed before this ran either: no regression to repair.
|
||
if (!d || !d.configurable || !('value' in d) || !d.writable) return;
|
||
var v = d.value;
|
||
define(p, n, {
|
||
get: function () { return v; },
|
||
set: function (nv) {
|
||
// This setter stands in for the data property it replaced, so it answers a write the way
|
||
// that property would: an accessor is handed the receiver and nothing else, while a data
|
||
// property has the receiver's own property consulted first
|
||
// (OrdinarySetWithOwnDescriptor). Doing that here keeps the repair invisible — an own
|
||
// writable property is updated in place and keeps its other attributes, an own
|
||
// non-writable one drops the write rather than being overwritten, and a target that
|
||
// cannot take a new property is left alone instead of being handed to `defineProperty`,
|
||
// which would throw. That last case covers a frozen prototype, an object the expression
|
||
// froze itself, and `null` or `undefined`.
|
||
if (this === p || this === null || this === undefined) return;
|
||
var own = describe(this, n);
|
||
if (own) {
|
||
if (own.writable) define(this, n, { value: nv });
|
||
return;
|
||
}
|
||
if (!isExtensible(this)) return;
|
||
define(this, n, { value: nv, writable: true, enumerable: true, configurable: true });
|
||
},
|
||
enumerable: d.enumerable,
|
||
configurable: false,
|
||
});
|
||
};
|
||
|
||
var freezeProto = function (p) {
|
||
if (p === null || (typeof p !== 'object' && typeof p !== 'function') || isFrozen(p)) return;
|
||
var names = getNames(p);
|
||
for (var i = 0; i < names.length; i++) if (!readByHost(p, names[i])) tolerate(p, names[i]);
|
||
// Symbol keys go the same way: `a[Symbol.iterator] = …` on an expression's own array is an
|
||
// ordinary way to make something iterable, and left frozen it is dropped in silence while
|
||
// `for…of` quietly walks the array's own elements instead.
|
||
var syms = getSyms(p);
|
||
for (var j = 0; j < syms.length; j++) tolerate(p, syms[j]);
|
||
freeze(p);
|
||
};
|
||
|
||
// Freezing a function also makes its own `prototype` property non-writable, so `Comp.prototype =
|
||
// {}` is a no-op; freezing that prototype object closes the methods hanging off it.
|
||
var shut = function (o) {
|
||
var d = describe(o, 'prototype');
|
||
if (d && 'value' in d) freezeProto(d.value);
|
||
freeze(o);
|
||
};
|
||
|
||
var globals = getNames(g);
|
||
for (var k = 0; k < globals.length; k++) {
|
||
var n = globals[k];
|
||
var d = describe(g, n);
|
||
if (!d || !('value' in d) || typeof d.value !== 'function') continue;
|
||
// The function object as well as the binding: the prelude calls `Array.isArray` and
|
||
// `Object.setPrototypeOf` while it answers an expression, so a replacement for one of those
|
||
// would not merely shadow a name, it would change what a later expression computes.
|
||
shut(d.value);
|
||
lock(n);
|
||
}
|
||
// `Math.sin = …` goes the same way, and the prelude leans on both of these itself. They are the
|
||
// `prototype` of no function, so the loop above never reaches them, and they are the prototype
|
||
// of no object either: nothing inherits `sin` or `parse`, so there is no shadowing for the
|
||
// accessor repair to keep, and a plain freeze closes them. That matters on the hot path, where
|
||
// a repaired name costs an accessor call on every read and a per-frame expression reads
|
||
// `Math.*` more than anything else. The bindings are locked as well, so `Math = {}` cannot take
|
||
// them away from the next expression either.
|
||
freeze(Math);
|
||
freeze(JSON);
|
||
lock('Math');
|
||
lock('JSON');
|
||
// The iterator intrinsics are the `prototype` of no global function either, and `for…of`,
|
||
// destructuring and spread all run through them: replacing `next` on the array iterator left
|
||
// every later expression on the thread unable to iterate at all.
|
||
var walk = function (o) {
|
||
for (var p = o; p !== null && p !== undefined; p = protoOf(p)) freezeProto(p);
|
||
};
|
||
var probes = [function () { return [][Symbol.iterator](); },
|
||
function () { return ''[Symbol.iterator](); },
|
||
function () { return new Map()[Symbol.iterator](); },
|
||
function () { return new Set()[Symbol.iterator](); },
|
||
function () { return (function* () {})(); },
|
||
function () { return 'a'.matchAll(/a/g); },
|
||
function () { return Int8Array.prototype; }];
|
||
for (var m = 0; m < probes.length; m++) {
|
||
// Each one is a probe for an intrinsic this engine may not have at all.
|
||
try { walk(protoOf(probes[m]())); } catch (e) { /* not present */ }
|
||
}
|
||
})();
|