@glissade/lottie 0.45.0-pre.0 → 0.45.0-pre.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -170,6 +170,8 @@ interface LottieAsset {
170
170
  layers?: LottieLayer[];
171
171
  }
172
172
  interface LottieDocument {
173
+ /** bodymovin schema version (`v`) — strict lottie-web/dotLottie validators require it. */
174
+ v?: string;
173
175
  fr: number;
174
176
  ip: number;
175
177
  op: number;
package/dist/index.js CHANGED
@@ -1254,7 +1254,15 @@ function emitKeys(keys, fr, toS) {
1254
1254
  /**
1255
1255
  * Sample `tr` at every integer frame across its keyed span (or the whole
1256
1256
  * document `[ip, op]` for an expr track with no keys) and emit linear Lottie
1257
- * keys. `toS` maps a sampled value to the Lottie `s` payload.
1257
+ * keys, then DECIMATE redundant ones ({@link decimateLinearKeys}). `toS` maps a
1258
+ * sampled value to the Lottie `s` payload.
1259
+ *
1260
+ * Dense per-frame sampling is faithful but huge — a spring or named ease over a
1261
+ * long shot densifies to one key per frame on every channel (a real episode
1262
+ * measured ~148k keys / 139 MB). Since Lottie plays LINEAR between keys, most of
1263
+ * those samples lie on the straight run between two others and are redundant;
1264
+ * decimation drops them within a per-component tolerance, collapsing constant and
1265
+ * constant-velocity stretches to their endpoints while keeping the curvature.
1258
1266
  */
1259
1267
  function sampleToLottieKeys(tr, fr, ip, op, toS) {
1260
1268
  const keys = tr.keys;
@@ -1283,6 +1291,75 @@ function sampleToLottieKeys(tr, fr, ip, op, toS) {
1283
1291
  }
1284
1292
  out.push(frame);
1285
1293
  }
1294
+ return decimateLinearKeys(out);
1295
+ }
1296
+ /**
1297
+ * Ramer–Douglas–Peucker over linear-interpolated keyframes: keep the endpoints
1298
+ * plus any interior key whose value is NOT reproduced (within `relEps` of each
1299
+ * component's range) by linear interpolation between the kept neighbors — those
1300
+ * are exactly the keys Lottie's linear playback can't recreate, so the rest are
1301
+ * safe to drop. Endpoints (first/last, hence the exact start/end value and time)
1302
+ * are always kept; existing linear handles on the survivors stay valid. Only
1303
+ * flat-numeric `s` payloads are decimated — path `sh` data (nested vertex arrays)
1304
+ * is left dense. `relEps` is a fraction of each channel's value range, so it is
1305
+ * scale-invariant across position (px), opacity (0–100), scale (×100), color (0–1).
1306
+ */
1307
+ function decimateLinearKeys(keys, relEps = .002) {
1308
+ const n = keys.length;
1309
+ if (n <= 2) return keys;
1310
+ const isFlat = (s) => Array.isArray(s) && s.every((x) => typeof x === "number");
1311
+ if (!keys.every((k) => isFlat(k.s))) return keys;
1312
+ const sAt = (i) => keys[i].s;
1313
+ const dim = sAt(0).length;
1314
+ const min = new Array(dim).fill(Infinity);
1315
+ const max = new Array(dim).fill(-Infinity);
1316
+ for (let i = 0; i < n; i++) {
1317
+ const s = sAt(i);
1318
+ for (let c = 0; c < dim; c++) {
1319
+ const v = s[c];
1320
+ if (v < min[c]) min[c] = v;
1321
+ if (v > max[c]) max[c] = v;
1322
+ }
1323
+ }
1324
+ const invRange = new Array(dim);
1325
+ for (let c = 0; c < dim; c++) {
1326
+ const r = max[c] - min[c];
1327
+ invRange[c] = r > 1e-9 ? 1 / r : 0;
1328
+ }
1329
+ const keep = new Uint8Array(n);
1330
+ keep[0] = 1;
1331
+ keep[n - 1] = 1;
1332
+ const stack = [[0, n - 1]];
1333
+ while (stack.length > 0) {
1334
+ const [lo, hi] = stack.pop();
1335
+ if (hi - lo < 2) continue;
1336
+ const a = sAt(lo);
1337
+ const b = sAt(hi);
1338
+ const ta = keys[lo].t;
1339
+ const span = keys[hi].t - ta;
1340
+ let worst = -1;
1341
+ let worstDev = relEps;
1342
+ for (let i = lo + 1; i < hi; i++) {
1343
+ const s = sAt(i);
1344
+ const f = span > 0 ? (keys[i].t - ta) / span : 0;
1345
+ let dev = 0;
1346
+ for (let c = 0; c < dim; c++) {
1347
+ const chord = a[c] + (b[c] - a[c]) * f;
1348
+ const d = Math.abs(s[c] - chord) * invRange[c];
1349
+ if (d > dev) dev = d;
1350
+ }
1351
+ if (dev > worstDev) {
1352
+ worstDev = dev;
1353
+ worst = i;
1354
+ }
1355
+ }
1356
+ if (worst >= 0) {
1357
+ keep[worst] = 1;
1358
+ stack.push([lo, worst], [worst, hi]);
1359
+ }
1360
+ }
1361
+ const out = [];
1362
+ for (let i = 0; i < n; i++) if (keep[i] === 1) out.push(keys[i]);
1286
1363
  return out;
1287
1364
  }
1288
1365
  //#endregion
@@ -1346,6 +1423,7 @@ function exportLottie(mod, opts) {
1346
1423
  };
1347
1424
  walkChildren(ctx, scene.root.children, void 0, byNode);
1348
1425
  return {
1426
+ v: BODYMOVIN_VERSION,
1349
1427
  fr,
1350
1428
  ip: 0,
1351
1429
  op,
@@ -1355,6 +1433,12 @@ function exportLottie(mod, opts) {
1355
1433
  layers: ctx.layers
1356
1434
  };
1357
1435
  }
1436
+ /**
1437
+ * bodymovin schema version stamped on every export. Strict lottie-web / dotLottie
1438
+ * validators reject a document without a top-level `v`; 5.7.x is a widely-supported
1439
+ * modern version and the shape this exporter emits is a subset of it.
1440
+ */
1441
+ const BODYMOVIN_VERSION = "5.7.0";
1358
1442
  /** Resolve a track target to `[nodeId, propPath]` by the longest registered-id prefix. */
1359
1443
  function resolveTrackNode(nodes, target) {
1360
1444
  for (let slash = target.lastIndexOf("/"); slash > 0; slash = target.lastIndexOf("/", slash - 1)) {
@@ -1504,7 +1588,7 @@ function sampleComponentVec(ctx, xt, yt, staticVal, map) {
1504
1588
  }
1505
1589
  out.push(frame);
1506
1590
  }
1507
- return out;
1591
+ return decimateLinearKeys(out);
1508
1592
  }
1509
1593
  /** Union frame span of a set of tracks (their first→last key), else [ip, op]. */
1510
1594
  function frameSpan(ctx, tracks) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glissade/lottie",
3
- "version": "0.45.0-pre.0",
3
+ "version": "0.45.0-pre.2",
4
4
  "description": "glissade Lottie import (S1 MVP): pure .json (Lottie/bodymovin) → node specs + a v1 Timeline. Fail-fast feature audit; no DOM/Node dependencies.",
5
5
  "license": "Apache-2.0",
6
6
  "engines": {
@@ -18,11 +18,11 @@
18
18
  "dist"
19
19
  ],
20
20
  "dependencies": {
21
- "@glissade/core": "0.45.0-pre.0",
22
- "@glissade/scene": "0.45.0-pre.0"
21
+ "@glissade/core": "0.45.0-pre.2",
22
+ "@glissade/scene": "0.45.0-pre.2"
23
23
  },
24
24
  "devDependencies": {
25
- "@glissade/backend-skia": "0.45.0-pre.0"
25
+ "@glissade/backend-skia": "0.45.0-pre.2"
26
26
  },
27
27
  "repository": {
28
28
  "type": "git",