@glissade/lottie 0.48.0-pre.0 → 0.48.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.
Files changed (2) hide show
  1. package/dist/index.js +67 -13
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Circle, Group, ImageNode, Path, Rect, Text, createScene } from "@glissade/scene";
2
- import { cubicBezier, formatColor, parseColor, sampleTrack, track } from "@glissade/core";
2
+ import { compileTimeline, cubicBezier, formatColor, parseColor, sampleTrack, track } from "@glissade/core";
3
3
  import "@glissade/core/expr";
4
4
  //#region src/spec.ts
5
5
  var LottieImportError = class extends Error {
@@ -1402,7 +1402,56 @@ function sampleToLottieKeys(tr, fr, ip, op, toS) {
1402
1402
  }
1403
1403
  out.push(frame);
1404
1404
  }
1405
- return decimateLinearKeys(out);
1405
+ return anchorSampledSpan(decimateLinearKeys(out), f0, f1, ip, op, (frame) => toS(sampleTrack(tr, frame / fr)));
1406
+ }
1407
+ /**
1408
+ * Anchor the document boundaries of a densely-SAMPLED channel whose keyed span
1409
+ * [f0,f1] doesn't reach the document bounds [ip,op]. Lottie extrapolates a channel
1410
+ * by HOLDING its first key backward and its last key forward, so when the span
1411
+ * starts AFTER ip the first EMITTED sample holds backward across the whole leading
1412
+ * dormant run — and for a fade-in whose first key rounds to a frame PAST the fade
1413
+ * start (a fractional key time, `round(t·fr) > t·fr`), that first sample is already
1414
+ * non-zero (e.g. ~9%), so a "hidden" element GHOSTS at ~9% from t=0 instead of 0.
1415
+ * Make the true base explicit:
1416
+ * • f0 > ip → PREPEND a HOLD key at ip carrying `sampleAt(ip)` — the value
1417
+ * `sampleTrack` holds across the dormant run (0 for a dormant-at-0 fade). HELD,
1418
+ * not linearly ramped, so a long dormant window stays at the base value the
1419
+ * whole way instead of sloping up to the first sample.
1420
+ * • f1 < op → APPEND a key at op carrying `sampleAt(op)`. Lottie already holds
1421
+ * the last key forward, but a fractional last-key round-DOWN leaves the true
1422
+ * tail value unsampled (e.g. a fade-out whose final 0 is skipped); this pins it.
1423
+ * `body` is the already-decimated sampled keyframes; the boundary keys sit OUTSIDE
1424
+ * it, so decimation (which assumes pure linear segments) never touches them. A span
1425
+ * that already covers [ip,op] (the common integer-keyed case) returns `body`
1426
+ * unchanged — byte-identical to before this fix.
1427
+ */
1428
+ function anchorSampledSpan(body, f0, f1, ip, op, sampleAt) {
1429
+ if (body.length === 0 || f0 <= ip && f1 >= op) return body;
1430
+ const out = body.slice();
1431
+ if (f1 < op) {
1432
+ const last = out[out.length - 1];
1433
+ out[out.length - 1] = {
1434
+ ...last,
1435
+ o: {
1436
+ x: 0,
1437
+ y: 0
1438
+ },
1439
+ i: {
1440
+ x: 1,
1441
+ y: 1
1442
+ }
1443
+ };
1444
+ out.push({
1445
+ t: op,
1446
+ s: sampleAt(op)
1447
+ });
1448
+ }
1449
+ if (f0 > ip) out.unshift({
1450
+ t: ip,
1451
+ s: sampleAt(ip),
1452
+ h: 1
1453
+ });
1454
+ return out;
1406
1455
  }
1407
1456
  /**
1408
1457
  * Ramer–Douglas–Peucker over linear-interpolated keyframes: keep the endpoints
@@ -1531,8 +1580,9 @@ function exportLottie(mod, opts) {
1531
1580
  const scene = mod.createScene();
1532
1581
  const fr = opts.fps ?? mod.timeline.fps ?? 60;
1533
1582
  const warn = opts.onWarn ?? ((m) => console.warn(`gs export: ${m}`));
1583
+ const compiled = compileTimeline(mod.timeline);
1534
1584
  const byNode = /* @__PURE__ */ new Map();
1535
- for (const tr of mod.timeline.tracks) {
1585
+ for (const tr of compiled.tracks.values()) {
1536
1586
  const resolved = resolveTrackNode(scene.nodes, tr.target);
1537
1587
  if (resolved === void 0) {
1538
1588
  warn(`track '${tr.target}' targets no node in the scene — dropped`);
@@ -1676,14 +1726,17 @@ function combineOpacity(ctx, node, tracks, accum) {
1676
1726
  k: leafStatic * accum.factor * 100
1677
1727
  };
1678
1728
  const [f0, f1] = frameSpan(ctx, leafTrack ? [leafTrack, ...accum.tracks] : [...accum.tracks]);
1679
- const out = [];
1680
- for (let f = f0; f <= f1; f++) {
1681
- const t = f / ctx.fr;
1729
+ const sampleAt = (frame) => {
1730
+ const t = frame / ctx.fr;
1682
1731
  let product = (leafTrack ? sampleTrack(leafTrack, t) : leafStatic) * accum.factor;
1683
1732
  for (const at of accum.tracks) product *= sampleTrack(at, t);
1733
+ return [product * 100];
1734
+ };
1735
+ const out = [];
1736
+ for (let f = f0; f <= f1; f++) {
1684
1737
  const frame = {
1685
1738
  t: f,
1686
- s: [product * 100]
1739
+ s: sampleAt(f)
1687
1740
  };
1688
1741
  if (f < f1) {
1689
1742
  frame.o = {
@@ -1699,7 +1752,7 @@ function combineOpacity(ctx, node, tracks, accum) {
1699
1752
  }
1700
1753
  return {
1701
1754
  a: 1,
1702
- k: decimateLinearKeys(out)
1755
+ k: anchorSampledSpan(decimateLinearKeys(out), f0, f1, ctx.ip, ctx.op, sampleAt)
1703
1756
  };
1704
1757
  }
1705
1758
  function scalarProp(ctx, tracks, prop, staticVal, map) {
@@ -1773,14 +1826,15 @@ function positionProp(ctx, tracks, staticPos) {
1773
1826
  }
1774
1827
  function sampleComponentVec(ctx, xt, yt, staticVal, map) {
1775
1828
  const [f0, f1] = frameSpan(ctx, [xt, yt]);
1829
+ const sampleAt = (frame) => {
1830
+ const t = frame / ctx.fr;
1831
+ return map([xt ? sampleTrack(xt, t) : staticVal[0], yt ? sampleTrack(yt, t) : staticVal[1]]);
1832
+ };
1776
1833
  const out = [];
1777
1834
  for (let f = f0; f <= f1; f++) {
1778
- const t = f / ctx.fr;
1779
- const x = xt ? sampleTrack(xt, t) : staticVal[0];
1780
- const y = yt ? sampleTrack(yt, t) : staticVal[1];
1781
1835
  const frame = {
1782
1836
  t: f,
1783
- s: map([x, y])
1837
+ s: sampleAt(f)
1784
1838
  };
1785
1839
  if (f < f1) {
1786
1840
  frame.o = {
@@ -1794,7 +1848,7 @@ function sampleComponentVec(ctx, xt, yt, staticVal, map) {
1794
1848
  }
1795
1849
  out.push(frame);
1796
1850
  }
1797
- return decimateLinearKeys(out);
1851
+ return anchorSampledSpan(decimateLinearKeys(out), f0, f1, ctx.ip, ctx.op, sampleAt);
1798
1852
  }
1799
1853
  /** Union frame span of a set of tracks (their first→last key), else [ip, op]. */
1800
1854
  function frameSpan(ctx, tracks) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glissade/lottie",
3
- "version": "0.48.0-pre.0",
3
+ "version": "0.48.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.48.0-pre.0",
22
- "@glissade/scene": "0.48.0-pre.0"
21
+ "@glissade/core": "0.48.0-pre.2",
22
+ "@glissade/scene": "0.48.0-pre.2"
23
23
  },
24
24
  "devDependencies": {
25
- "@glissade/backend-skia": "0.48.0-pre.0"
25
+ "@glissade/backend-skia": "0.48.0-pre.2"
26
26
  },
27
27
  "repository": {
28
28
  "type": "git",