@glissade/lottie 0.53.0 → 0.54.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.d.ts +9 -0
- package/dist/index.js +82 -6
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -320,6 +320,15 @@ interface ExportOptions {
|
|
|
320
320
|
* wouldn't match a faithful render, so we don't bake with it).
|
|
321
321
|
*/
|
|
322
322
|
measurer?: TextMeasurer;
|
|
323
|
+
/**
|
|
324
|
+
* PNG encoder threaded from the CLI (like `measurer`) so a MESH fill can be
|
|
325
|
+
* rasterized (the pure `rasterizeMesh` kernel) and embedded as a ty:2 image
|
|
326
|
+
* layer — Lottie has no mesh primitive. `@glissade/lottie` stays DOM/Node-free,
|
|
327
|
+
* so it can't encode PNG itself. Returns BASE64 (no `data:` prefix; the exporter
|
|
328
|
+
* prepends `data:image/png;base64,`). ABSENT (pure-JS callers/tests) → a mesh
|
|
329
|
+
* fill keeps the historical warn-drop, so non-CLI exports are unchanged.
|
|
330
|
+
*/
|
|
331
|
+
encodePng?: (rgba: Uint8ClampedArray, w: number, h: number) => string;
|
|
323
332
|
}
|
|
324
333
|
/** Convert a SceneModule to a Lottie document. Pure over (scene, timeline). */
|
|
325
334
|
declare function exportLottie(mod: SceneModule, opts: ExportOptions): LottieDocument;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Circle, Group, ImageNode, Path, Rect, Text, breakLines, createScene } from "@glissade/scene";
|
|
1
|
+
import { Circle, Group, ImageNode, Path, Rect, Text, breakLines, createScene, meshRasterSize, rasterizeMesh } from "@glissade/scene";
|
|
2
2
|
import { compileTimeline, cubicBezier, formatColor, parseColor, sampleTrack, track } from "@glissade/core";
|
|
3
3
|
import "@glissade/core/expr";
|
|
4
4
|
//#region src/spec.ts
|
|
@@ -1733,7 +1733,9 @@ function exportLottie(mod, opts) {
|
|
|
1733
1733
|
layers: [],
|
|
1734
1734
|
ind: 0,
|
|
1735
1735
|
fonts: /* @__PURE__ */ new Map(),
|
|
1736
|
-
measurer: opts.measurer
|
|
1736
|
+
measurer: opts.measurer,
|
|
1737
|
+
encodePng: opts.encodePng,
|
|
1738
|
+
assets: []
|
|
1737
1739
|
};
|
|
1738
1740
|
walkChildren(ctx, scene.root.children, void 0, byNode, IDENTITY_OPACITY);
|
|
1739
1741
|
const fonts = [...ctx.fonts.values()];
|
|
@@ -1746,6 +1748,7 @@ function exportLottie(mod, opts) {
|
|
|
1746
1748
|
h: opts.height,
|
|
1747
1749
|
nm: "glissade export",
|
|
1748
1750
|
layers: ctx.layers,
|
|
1751
|
+
...ctx.assets.length > 0 ? { assets: ctx.assets } : {},
|
|
1749
1752
|
...fonts.length > 0 ? { fonts: { list: fonts } } : {}
|
|
1750
1753
|
};
|
|
1751
1754
|
}
|
|
@@ -2026,7 +2029,7 @@ function buildShapeLayer(ctx, node, kind, ind, parentInd, tracks, opacity) {
|
|
|
2026
2029
|
const shapes = [buildGeometry(ctx, node, kind, tracks)];
|
|
2027
2030
|
const stroke = buildStroke(ctx, node, tracks);
|
|
2028
2031
|
if (stroke) shapes.push(stroke);
|
|
2029
|
-
const fill = buildFill(ctx, node, tracks);
|
|
2032
|
+
const fill = buildFill(ctx, node, tracks, ind);
|
|
2030
2033
|
if (fill) shapes.push(fill);
|
|
2031
2034
|
return {
|
|
2032
2035
|
ty: 4,
|
|
@@ -2267,7 +2270,7 @@ function colorToLottie(css) {
|
|
|
2267
2270
|
function colorKeys(ctx, tr) {
|
|
2268
2271
|
return isDirectlyInvertible(tr.keys, tr.expr) ? emitKeys(tr.keys, ctx.fr, colorToLottie) : sampleToLottieKeys(tr, ctx.fr, ctx.ip, ctx.op, colorToLottie);
|
|
2269
2272
|
}
|
|
2270
|
-
function buildFill(ctx, node, tracks) {
|
|
2273
|
+
function buildFill(ctx, node, tracks, ind) {
|
|
2271
2274
|
const tr = tracks.get("fill");
|
|
2272
2275
|
if (tr) {
|
|
2273
2276
|
if (tr.type === "color") return {
|
|
@@ -2281,7 +2284,7 @@ function buildFill(ctx, node, tracks) {
|
|
|
2281
2284
|
k: 100
|
|
2282
2285
|
}
|
|
2283
2286
|
};
|
|
2284
|
-
if (tr.type === "paint") return buildAnimatedGradientFill(ctx, node, tr);
|
|
2287
|
+
if (tr.type === "paint") return buildAnimatedGradientFill(ctx, node, tr, ind);
|
|
2285
2288
|
ctx.warn(`${describe(node)}: animated '${tr.type}' fill is not exported — dropped`);
|
|
2286
2289
|
return;
|
|
2287
2290
|
}
|
|
@@ -2312,12 +2315,80 @@ function buildFill(ctx, node, tracks) {
|
|
|
2312
2315
|
}
|
|
2313
2316
|
};
|
|
2314
2317
|
if (fill.kind === "mesh") {
|
|
2318
|
+
if (ctx.encodePng) {
|
|
2319
|
+
emitMeshRaster(ctx, node, fill, ind);
|
|
2320
|
+
return;
|
|
2321
|
+
}
|
|
2315
2322
|
ctx.warn(`${describe(node)}: a mesh fill has no Lottie gradient ramp (MVP: solid / linear / radial) — dropped`);
|
|
2316
2323
|
return;
|
|
2317
2324
|
}
|
|
2318
2325
|
warnGradientInterpolation(ctx, node, fill);
|
|
2319
2326
|
return gradientFillItem(fill, localBounds(node));
|
|
2320
2327
|
}
|
|
2328
|
+
/**
|
|
2329
|
+
* Rasterize a STATIC (or first-key-flattened) mesh Paint to a PNG and emit it as a
|
|
2330
|
+
* ty:2 image LAYER parented to the shape layer (`shapeInd`). The raster's placement
|
|
2331
|
+
* mirrors raster2d.fillMesh's blit rect EXACTLY: the buffer covers the fill's
|
|
2332
|
+
* `localBounds`, so expressing the image in SHAPE-LOCAL coordinates (anchor [0,0],
|
|
2333
|
+
* position = bounds top-left, scale = bounds/raster) lets the shape layer's own
|
|
2334
|
+
* transform (position/rotation/scale/anchor) carry it to screen — the importer
|
|
2335
|
+
* re-parents the image under the shape's transform group, aligning it with the
|
|
2336
|
+
* (now fill-less) geometry. Gated on `ctx.encodePng` by the caller. Deterministic:
|
|
2337
|
+
* `rasterizeMesh` is pure, the Skia PNG encode is byte-stable, base64 is total.
|
|
2338
|
+
*/
|
|
2339
|
+
function emitMeshRaster(ctx, node, mesh, shapeInd) {
|
|
2340
|
+
const b = localBounds(node);
|
|
2341
|
+
const bw = b.maxX - b.minX;
|
|
2342
|
+
const bh = b.maxY - b.minY;
|
|
2343
|
+
if (bw <= 0 || bh <= 0) {
|
|
2344
|
+
ctx.warn(`${describe(node)}: a mesh fill has empty local bounds — dropped`);
|
|
2345
|
+
return;
|
|
2346
|
+
}
|
|
2347
|
+
const { w: rw, h: rh } = meshRasterSize(bw, bh);
|
|
2348
|
+
const rgba = rasterizeMesh(mesh, rw, rh);
|
|
2349
|
+
const b64 = ctx.encodePng(rgba, rw, rh);
|
|
2350
|
+
const id = `mesh_${ctx.assets.length}`;
|
|
2351
|
+
ctx.assets.push({
|
|
2352
|
+
id,
|
|
2353
|
+
w: rw,
|
|
2354
|
+
h: rh,
|
|
2355
|
+
u: "",
|
|
2356
|
+
p: `data:image/png;base64,${b64}`,
|
|
2357
|
+
e: 1
|
|
2358
|
+
});
|
|
2359
|
+
const layerInd = ++ctx.ind;
|
|
2360
|
+
ctx.layers.push({
|
|
2361
|
+
ty: 2,
|
|
2362
|
+
nm: `${node.id ?? `mesh${shapeInd}`}_raster`,
|
|
2363
|
+
refId: id,
|
|
2364
|
+
ind: layerInd,
|
|
2365
|
+
ip: ctx.ip,
|
|
2366
|
+
op: ctx.op,
|
|
2367
|
+
ks: {
|
|
2368
|
+
a: {
|
|
2369
|
+
a: 0,
|
|
2370
|
+
k: [0, 0]
|
|
2371
|
+
},
|
|
2372
|
+
p: {
|
|
2373
|
+
a: 0,
|
|
2374
|
+
k: [b.minX, b.minY]
|
|
2375
|
+
},
|
|
2376
|
+
s: {
|
|
2377
|
+
a: 0,
|
|
2378
|
+
k: [bw / rw * 100, bh / rh * 100]
|
|
2379
|
+
},
|
|
2380
|
+
r: {
|
|
2381
|
+
a: 0,
|
|
2382
|
+
k: 0
|
|
2383
|
+
},
|
|
2384
|
+
o: {
|
|
2385
|
+
a: 0,
|
|
2386
|
+
k: 100
|
|
2387
|
+
}
|
|
2388
|
+
},
|
|
2389
|
+
parent: shapeInd
|
|
2390
|
+
});
|
|
2391
|
+
}
|
|
2321
2392
|
/** Local-space bounds of a shape's fill path — the gradient-geometry default source
|
|
2322
2393
|
* (matches raster2d.resolveFill: linear ⇒ vertical bounds sweep, radial ⇒ centre +
|
|
2323
2394
|
* half-diagonal). Rect/Circle draw centred at the origin; a Path bounds its anchors. */
|
|
@@ -2461,9 +2532,14 @@ function gradientFillItem(g, bounds) {
|
|
|
2461
2532
|
* the frame grid. A key whose kind/stop-count differs from the first would snap
|
|
2462
2533
|
* under paintType anyway, so its geometry/ramp is read as best-effort.
|
|
2463
2534
|
*/
|
|
2464
|
-
function buildAnimatedGradientFill(ctx, node, tr) {
|
|
2535
|
+
function buildAnimatedGradientFill(ctx, node, tr, ind) {
|
|
2465
2536
|
const first = tr.keys[0].value;
|
|
2466
2537
|
if (first.kind === "mesh") {
|
|
2538
|
+
if (ctx.encodePng) {
|
|
2539
|
+
ctx.warn(`${describe(node)}: mesh animation is flattened to a static raster (first key) — motion dropped`);
|
|
2540
|
+
emitMeshRaster(ctx, node, first, ind);
|
|
2541
|
+
return;
|
|
2542
|
+
}
|
|
2467
2543
|
ctx.warn(`${describe(node)}: an animated mesh fill has no Lottie gradient ramp — dropped`);
|
|
2468
2544
|
return;
|
|
2469
2545
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glissade/lottie",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.54.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.54.0-pre.0",
|
|
22
|
+
"@glissade/scene": "0.54.0-pre.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@glissade/backend-skia": "0.
|
|
25
|
+
"@glissade/backend-skia": "0.54.0-pre.0"
|
|
26
26
|
},
|
|
27
27
|
"repository": {
|
|
28
28
|
"type": "git",
|