@glissade/lottie 0.47.0 → 0.48.0-pre.0
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.js +103 -14
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1500,14 +1500,32 @@ function decimateLinearKeys(keys, relEps = .002) {
|
|
|
1500
1500
|
* (constant topology), and TEXT (ty:5): a Text node → a text layer with a font
|
|
1501
1501
|
* reference (fonts.list) + a text-document keyframe stream (static = one doc;
|
|
1502
1502
|
* animated text/fill/fontSize → doc keyframes sampled on the frame grid, held).
|
|
1503
|
+
*
|
|
1504
|
+
* GROUP OPACITY (baked into descendants): Lottie null-parent parenting inherits
|
|
1505
|
+
* the transform MATRIX only, never opacity, so a `Group{opacity<1}` (or an
|
|
1506
|
+
* animated `opacity` track) is composited by MULTIPLYING its opacity into every
|
|
1507
|
+
* LEAF descendant's `ks.o` (static → a product; animated → the product sampled on
|
|
1508
|
+
* the frame grid + decimated, the sampleComponentVec discipline) while the group
|
|
1509
|
+
* null keeps its p/r/s and carries `o:{a:0,k:100}`. This is EXACT when a group's
|
|
1510
|
+
* translucent descendants DON'T overlap (or the group is near-0/near-1 — the
|
|
1511
|
+
* reported leak-through case: a group fading to ~0 hides its children since
|
|
1512
|
+
* 0×anything≈0). LIMIT (warned, never silent): OVERLAPPING translucent siblings
|
|
1513
|
+
* double-composite — glissade composites the subtree as a unit then applies the
|
|
1514
|
+
* group alpha once, whereas per-child baking stacks the alphas (0.5 over 0.5 ≈
|
|
1515
|
+
* 0.75, not the correct single 0.5). Same limitation the importer documents at
|
|
1516
|
+
* convert.ts:470-475. A correct-for-overlap precomp (ty:0 + assets) is a later phase.
|
|
1517
|
+
*
|
|
1503
1518
|
* OUT (warned + dropped): Image/Video, gradient/mesh paint (solid only),
|
|
1504
|
-
* non-center anchors,
|
|
1505
|
-
* opacity), text typewriter `reveal`/`revealFraction`, variable-font axes
|
|
1519
|
+
* non-center anchors, text typewriter `reveal`/`revealFraction`, variable-font axes
|
|
1506
1520
|
* (`fontAxes`/`fontVariationSettings` — no Lottie doc field), `box` valign
|
|
1507
1521
|
* (baseline-approximated) and wrap `width` (the player self-reflows), TokenHighlight.
|
|
1508
1522
|
* Animated primitive geometry (width/radius tracks) is SAMPLED, not channel-mapped.
|
|
1509
1523
|
*/
|
|
1510
1524
|
const EMPTY_TRACKS = /* @__PURE__ */ new Map();
|
|
1525
|
+
const IDENTITY_OPACITY = {
|
|
1526
|
+
factor: 1,
|
|
1527
|
+
tracks: []
|
|
1528
|
+
};
|
|
1511
1529
|
/** Convert a SceneModule to a Lottie document. Pure over (scene, timeline). */
|
|
1512
1530
|
function exportLottie(mod, opts) {
|
|
1513
1531
|
const scene = mod.createScene();
|
|
@@ -1538,7 +1556,7 @@ function exportLottie(mod, opts) {
|
|
|
1538
1556
|
ind: 0,
|
|
1539
1557
|
fonts: /* @__PURE__ */ new Map()
|
|
1540
1558
|
};
|
|
1541
|
-
walkChildren(ctx, scene.root.children, void 0, byNode);
|
|
1559
|
+
walkChildren(ctx, scene.root.children, void 0, byNode, IDENTITY_OPACITY);
|
|
1542
1560
|
const fonts = [...ctx.fonts.values()];
|
|
1543
1561
|
return {
|
|
1544
1562
|
v: BODYMOVIN_VERSION,
|
|
@@ -1580,7 +1598,7 @@ function computeOp(tl, fr) {
|
|
|
1580
1598
|
* node gets the SMALLER `ind` — the importer reconstructs paint order from
|
|
1581
1599
|
* `zIndex = -ind`, so a descending ind per sibling group preserves it.
|
|
1582
1600
|
*/
|
|
1583
|
-
function walkChildren(ctx, children, parentInd, byNode) {
|
|
1601
|
+
function walkChildren(ctx, children, parentInd, byNode, opacity) {
|
|
1584
1602
|
for (let i = children.length - 1; i >= 0; i--) {
|
|
1585
1603
|
const node = children[i];
|
|
1586
1604
|
const kind = classify(node);
|
|
@@ -1590,10 +1608,34 @@ function walkChildren(ctx, children, parentInd, byNode) {
|
|
|
1590
1608
|
}
|
|
1591
1609
|
const myInd = ++ctx.ind;
|
|
1592
1610
|
const tracks = (node.id !== void 0 ? byNode.get(node.id) : void 0) ?? EMPTY_TRACKS;
|
|
1593
|
-
ctx.layers.push(kind === "group" ? buildNullLayer(ctx, node, myInd, parentInd, tracks) : kind === "text" ? buildTextLayer(ctx, node, myInd, parentInd, tracks) : buildShapeLayer(ctx, node, kind, myInd, parentInd, tracks));
|
|
1594
|
-
if (node instanceof Group) walkChildren(ctx, node.children, myInd, byNode);
|
|
1611
|
+
ctx.layers.push(kind === "group" ? buildNullLayer(ctx, node, myInd, parentInd, tracks) : kind === "text" ? buildTextLayer(ctx, node, myInd, parentInd, tracks, opacity) : buildShapeLayer(ctx, node, kind, myInd, parentInd, tracks, opacity));
|
|
1612
|
+
if (node instanceof Group) walkChildren(ctx, node.children, myInd, byNode, childOpacity(node, tracks, opacity));
|
|
1595
1613
|
}
|
|
1596
1614
|
}
|
|
1615
|
+
/**
|
|
1616
|
+
* The opacity accumulator for a group's children: a group WITH an opacity track
|
|
1617
|
+
* contributes that track (sampled per-frame); one WITHOUT multiplies its static
|
|
1618
|
+
* opacity into `factor`. The group's own opacity is thus pushed down onto its
|
|
1619
|
+
* descendants (the null layer itself carries `o:100`).
|
|
1620
|
+
*/
|
|
1621
|
+
function childOpacity(group, tracks, parent) {
|
|
1622
|
+
const track = tracks.get("opacity");
|
|
1623
|
+
if (track) return {
|
|
1624
|
+
factor: parent.factor,
|
|
1625
|
+
tracks: [...parent.tracks, track]
|
|
1626
|
+
};
|
|
1627
|
+
return {
|
|
1628
|
+
factor: parent.factor * group.opacity(),
|
|
1629
|
+
tracks: parent.tracks
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
/** Count exportable LEAF (non-group, non-drop) descendants — the overlap-warn heuristic. */
|
|
1633
|
+
function countLeafDescendants(group) {
|
|
1634
|
+
let n = 0;
|
|
1635
|
+
for (const child of group.children) if (child instanceof Group) n += countLeafDescendants(child);
|
|
1636
|
+
else if (classify(child) !== "drop") n += 1;
|
|
1637
|
+
return n;
|
|
1638
|
+
}
|
|
1597
1639
|
function classify(node) {
|
|
1598
1640
|
if (node instanceof Rect) return "rect";
|
|
1599
1641
|
if (node instanceof Circle) return "circle";
|
|
@@ -1603,7 +1645,7 @@ function classify(node) {
|
|
|
1603
1645
|
return "drop";
|
|
1604
1646
|
}
|
|
1605
1647
|
const describe = (node) => `${node.describeType}${node.id !== void 0 ? ` '${node.id}'` : ""}`;
|
|
1606
|
-
function buildTransform(ctx, node, tracks) {
|
|
1648
|
+
function buildTransform(ctx, node, tracks, o) {
|
|
1607
1649
|
if (node.hasAnchor && (node.anchor[0] !== .5 || node.anchor[1] !== .5)) ctx.warn(`${describe(node)}: a non-center anchor is not exported (MVP centers geometry) — placement may shift`);
|
|
1608
1650
|
return {
|
|
1609
1651
|
a: {
|
|
@@ -1613,7 +1655,51 @@ function buildTransform(ctx, node, tracks) {
|
|
|
1613
1655
|
p: positionProp(ctx, tracks, node.position()),
|
|
1614
1656
|
s: vecProp(ctx, tracks, "scale", node.scale(), (v) => [v[0] * 100, v[1] * 100]),
|
|
1615
1657
|
r: scalarProp(ctx, tracks, "rotation", node.rotation(), (v) => v),
|
|
1616
|
-
o
|
|
1658
|
+
o
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
/**
|
|
1662
|
+
* A leaf's `ks.o`, folding in the group opacity accumulated from its ancestors.
|
|
1663
|
+
* With NO ancestor contribution (identity accumulator) this is byte-identical to
|
|
1664
|
+
* the pre-feature `scalarProp` path — the exact cubicBezier/hold inversion is
|
|
1665
|
+
* preserved. A static-only accumulator multiplies into a single `{a:0,k}`.
|
|
1666
|
+
* Anything animated (a leaf `opacity` track and/or an ancestor track) samples
|
|
1667
|
+
* the product `leaf_opacity(t) × Π ancestor_opacity(t)` on the union frame span
|
|
1668
|
+
* and decimates — the identical discipline sampleComponentVec uses.
|
|
1669
|
+
*/
|
|
1670
|
+
function combineOpacity(ctx, node, tracks, accum) {
|
|
1671
|
+
const leafTrack = tracks.get("opacity");
|
|
1672
|
+
const leafStatic = node.opacity();
|
|
1673
|
+
if (accum.factor === 1 && accum.tracks.length === 0) return scalarProp(ctx, tracks, "opacity", leafStatic, (v) => v * 100);
|
|
1674
|
+
if (leafTrack === void 0 && accum.tracks.length === 0) return {
|
|
1675
|
+
a: 0,
|
|
1676
|
+
k: leafStatic * accum.factor * 100
|
|
1677
|
+
};
|
|
1678
|
+
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;
|
|
1682
|
+
let product = (leafTrack ? sampleTrack(leafTrack, t) : leafStatic) * accum.factor;
|
|
1683
|
+
for (const at of accum.tracks) product *= sampleTrack(at, t);
|
|
1684
|
+
const frame = {
|
|
1685
|
+
t: f,
|
|
1686
|
+
s: [product * 100]
|
|
1687
|
+
};
|
|
1688
|
+
if (f < f1) {
|
|
1689
|
+
frame.o = {
|
|
1690
|
+
x: 0,
|
|
1691
|
+
y: 0
|
|
1692
|
+
};
|
|
1693
|
+
frame.i = {
|
|
1694
|
+
x: 1,
|
|
1695
|
+
y: 1
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
out.push(frame);
|
|
1699
|
+
}
|
|
1700
|
+
return {
|
|
1701
|
+
a: 1,
|
|
1702
|
+
k: decimateLinearKeys(out)
|
|
1617
1703
|
};
|
|
1618
1704
|
}
|
|
1619
1705
|
function scalarProp(ctx, tracks, prop, staticVal, map) {
|
|
@@ -1717,18 +1803,21 @@ function frameSpan(ctx, tracks) {
|
|
|
1717
1803
|
return bounds.length > 0 ? [Math.min(...bounds), Math.max(...bounds)] : [ctx.ip, ctx.op];
|
|
1718
1804
|
}
|
|
1719
1805
|
function buildNullLayer(ctx, node, ind, parentInd, tracks) {
|
|
1720
|
-
if (node.opacity() !== 1 || tracks.has("opacity")) ctx.warn(`${describe(node)}: group opacity is
|
|
1806
|
+
if ((node.opacity() !== 1 || tracks.has("opacity")) && node instanceof Group && countLeafDescendants(node) >= 2) ctx.warn(`${describe(node)}: group opacity is baked into descendant leaves; OVERLAPPING translucent descendants may double-composite (exact when they don't overlap)`);
|
|
1721
1807
|
return {
|
|
1722
1808
|
ty: 3,
|
|
1723
1809
|
nm: node.id ?? `group${ind}`,
|
|
1724
1810
|
ind,
|
|
1725
1811
|
ip: ctx.ip,
|
|
1726
1812
|
op: ctx.op,
|
|
1727
|
-
ks: buildTransform(ctx, node, tracks
|
|
1813
|
+
ks: buildTransform(ctx, node, tracks, {
|
|
1814
|
+
a: 0,
|
|
1815
|
+
k: 100
|
|
1816
|
+
}),
|
|
1728
1817
|
...parentInd !== void 0 ? { parent: parentInd } : {}
|
|
1729
1818
|
};
|
|
1730
1819
|
}
|
|
1731
|
-
function buildShapeLayer(ctx, node, kind, ind, parentInd, tracks) {
|
|
1820
|
+
function buildShapeLayer(ctx, node, kind, ind, parentInd, tracks, opacity) {
|
|
1732
1821
|
const shapes = [buildGeometry(ctx, node, kind, tracks)];
|
|
1733
1822
|
const stroke = buildStroke(ctx, node, tracks);
|
|
1734
1823
|
if (stroke) shapes.push(stroke);
|
|
@@ -1740,7 +1829,7 @@ function buildShapeLayer(ctx, node, kind, ind, parentInd, tracks) {
|
|
|
1740
1829
|
ind,
|
|
1741
1830
|
ip: ctx.ip,
|
|
1742
1831
|
op: ctx.op,
|
|
1743
|
-
ks: buildTransform(ctx, node, tracks),
|
|
1832
|
+
ks: buildTransform(ctx, node, tracks, combineOpacity(ctx, node, tracks, opacity)),
|
|
1744
1833
|
shapes,
|
|
1745
1834
|
...parentInd !== void 0 ? { parent: parentInd } : {}
|
|
1746
1835
|
};
|
|
@@ -1854,7 +1943,7 @@ function warnTextUnsupported(ctx, node, tracks) {
|
|
|
1854
1943
|
if (node.box !== void 0) ctx.warn(`${describe(node)}: box valign is approximated as baseline-anchored (no Lottie ink-box anchor) — vertical placement may shift`);
|
|
1855
1944
|
if (tracks.has("width") || node.width() > 0) ctx.warn(`${describe(node)}: wrap 'width' relies on the player's own line reflow — wrapping may diverge from glissade's`);
|
|
1856
1945
|
}
|
|
1857
|
-
function buildTextLayer(ctx, node, ind, parentInd, tracks) {
|
|
1946
|
+
function buildTextLayer(ctx, node, ind, parentInd, tracks, opacity) {
|
|
1858
1947
|
const fName = registerFont(ctx, node);
|
|
1859
1948
|
warnTextUnsupported(ctx, node, tracks);
|
|
1860
1949
|
const t = {
|
|
@@ -1867,7 +1956,7 @@ function buildTextLayer(ctx, node, ind, parentInd, tracks) {
|
|
|
1867
1956
|
ind,
|
|
1868
1957
|
ip: ctx.ip,
|
|
1869
1958
|
op: ctx.op,
|
|
1870
|
-
ks: buildTransform(ctx, node, tracks),
|
|
1959
|
+
ks: buildTransform(ctx, node, tracks, combineOpacity(ctx, node, tracks, opacity)),
|
|
1871
1960
|
t,
|
|
1872
1961
|
...parentInd !== void 0 ? { parent: parentInd } : {}
|
|
1873
1962
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glissade/lottie",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.48.0-pre.0",
|
|
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.
|
|
22
|
-
"@glissade/scene": "0.
|
|
21
|
+
"@glissade/core": "0.48.0-pre.0",
|
|
22
|
+
"@glissade/scene": "0.48.0-pre.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@glissade/backend-skia": "0.
|
|
25
|
+
"@glissade/backend-skia": "0.48.0-pre.0"
|
|
26
26
|
},
|
|
27
27
|
"repository": {
|
|
28
28
|
"type": "git",
|