mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-28 03:57:44 -06:00
TextDoc gains character style runs (font, size, colours, tracking, leading, baseline shift, h/v scale, tsume, faux styles, caps, super/subscript, kerning metrics/optical/manual, ligatures) and per-paragraph settings (justification with last-line modes, indents, space before/after, direction, composer, hanging punctuation), serde-compatible with single-style documents. Layout handles mixed runs on a line (shared baseline, largest leading), every-line composer, editing geometry (carets, hit testing, selection quads) and basic vertical type. Renderer colours per run. Expressions get the sourceText style API (style, getStyleAt, createStyle, setters with character ranges). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
880 lines
35 KiB
JavaScript
880 lines
35 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;
|
|
|
|
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;
|
|
}
|
|
|
|
function __finish(r) {
|
|
r = __v(r);
|
|
if (r !== null && typeof r === 'object' && 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);
|
|
}
|
|
|
|
function lookAt(from, at) {
|
|
var d = normalize(sub(__v(at), __v(from)));
|
|
var x = d[0], y = d[1], z = d[2] || 0;
|
|
var rx = -Math.asin(Math.max(-1, Math.min(1, y))) * 180 / Math.PI;
|
|
var ry = Math.atan2(x, z) * 180 / Math.PI;
|
|
return [rx, ry, 0];
|
|
}
|
|
|
|
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); }
|
|
|
|
// ---------------------------------------------------------------------------------------------
|
|
// 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;
|
|
return Math.floor(t * fps + 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 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; };
|
|
|
|
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]);
|
|
}
|
|
|
|
// 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); },
|
|
layer: function (a, rel) {
|
|
if (a !== null && typeof a === 'object' && 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 new 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);
|
|
}
|
|
|
|
// Layer -----------------------------------------------------------------------------------------
|
|
|
|
function Layer(c, l) { this.__c = c; this.__l = l; this.__i = null; }
|
|
|
|
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 __info() { return this.__i || (this.__i = __h(__LINFO, this.__c, this.__l)); },
|
|
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 new 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); },
|
|
__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 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] };
|
|
},
|
|
sampleImage: function () { return [0, 0, 0, 0]; },
|
|
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 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);
|
|
},
|
|
};
|
|
|
|
function __mkGroup(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 Proxy(f, __groupHandler);
|
|
}
|
|
|
|
// 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]; },
|
|
__wrap: function (v) {
|
|
var kind = this.__info[2];
|
|
if (kind === 'layer') return v === null ? null : new 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 : new 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(); },
|
|
// 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' || k in t) return t[k];
|
|
var v = t.value;
|
|
if (v === null || v === undefined) return undefined;
|
|
var x = v[k];
|
|
return typeof x === 'function' ? x.bind(v) : x;
|
|
},
|
|
};
|
|
|
|
function __mkProp(c, l, path) {
|
|
return new Proxy(new Prop(c, l, path), __propHandler);
|
|
}
|
|
|
|
// 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 (var k = 0; k < this.__ops.length; k++) {
|
|
var o = this.__ops[k];
|
|
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', ''); }),
|
|
setSuperscript: __styleSetter('superscript', Boolean),
|
|
setSubscript: __styleSetter('subscript', Boolean),
|
|
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];
|
|
return { time: m[0], duration: m[1], comment: m[2], chapter: m[3], url: m[4], index: n };
|
|
},
|
|
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.
|
|
|
|
function __global(name, get) {
|
|
Object.defineProperty(globalThis, name, {
|
|
configurable: true,
|
|
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 = new 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',
|
|
'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); }
|