reframe-video 0.6.0 → 0.6.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/bin.js +45 -3
- package/dist/browserEntry.js +94 -13
- package/dist/cli.js +27 -2
- package/dist/index.js +120 -9
- package/dist/labels.js +27 -2
- package/dist/renderer-canvas.js +41 -6
- package/dist/trace-cli.js +3 -2
- package/dist/types/effects.d.ts +12 -0
- package/dist/types/evaluate.d.ts +15 -7
- package/dist/types/gradient.d.ts +28 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/ir.d.ts +52 -6
- package/dist/types/path.d.ts +6 -0
- package/guides/edsl-guide.md +44 -0
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -349,12 +349,36 @@ var init_compile = __esm({
|
|
|
349
349
|
function validateScene(ir) {
|
|
350
350
|
const problems = [];
|
|
351
351
|
const nodeById = /* @__PURE__ */ new Map();
|
|
352
|
+
const checkPaint = (where, value) => {
|
|
353
|
+
if (typeof value !== "object" || value === null) return;
|
|
354
|
+
const g = value;
|
|
355
|
+
if (g.kind !== "linear" && g.kind !== "radial" && g.kind !== "conic") {
|
|
356
|
+
problems.push(`${where}: a paint object must be a gradient with kind "linear" / "radial" / "conic"`);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (!Array.isArray(g.stops) || g.stops.length === 0) {
|
|
360
|
+
problems.push(`${where}: gradient "${g.kind}" needs at least one color stop`);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
g.stops.forEach((s, i) => {
|
|
364
|
+
const st = s;
|
|
365
|
+
if (typeof st?.color !== "string") problems.push(`${where}: gradient stop ${i} needs a color string`);
|
|
366
|
+
if (typeof st?.offset !== "number" || st.offset < 0 || st.offset > 1) {
|
|
367
|
+
problems.push(`${where}: gradient stop ${i} "offset" must be a number in 0..1`);
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
};
|
|
352
371
|
const collect = (nodes) => {
|
|
353
372
|
for (const node of nodes) {
|
|
354
373
|
if (nodeById.has(node.id)) {
|
|
355
374
|
problems.push(`duplicate node id "${node.id}" \u2014 every node id must be unique`);
|
|
356
375
|
}
|
|
357
376
|
nodeById.set(node.id, node);
|
|
377
|
+
const props = node.props;
|
|
378
|
+
checkPaint(`node "${node.id}" fill`, props.fill);
|
|
379
|
+
checkPaint(`node "${node.id}" stroke`, props.stroke);
|
|
380
|
+
if (typeof props.blur === "number" && props.blur < 0) problems.push(`node "${node.id}": blur must be >= 0`);
|
|
381
|
+
if (typeof props.shadowBlur === "number" && props.shadowBlur < 0) problems.push(`node "${node.id}": shadowBlur must be >= 0`);
|
|
358
382
|
if (node.type === "group") {
|
|
359
383
|
const clip = node.props.clip;
|
|
360
384
|
if (clip) {
|
|
@@ -569,16 +593,17 @@ function validateComposition(comp) {
|
|
|
569
593
|
}
|
|
570
594
|
if (problems.length > 0) throw new SceneValidationError(problems);
|
|
571
595
|
}
|
|
572
|
-
var COMMON_PROPS, CAMERA_PROPS, PROPS_BY_TYPE, SceneValidationError, TRANSITIONS;
|
|
596
|
+
var FX_PROPS, COMMON_PROPS, CAMERA_PROPS, PROPS_BY_TYPE, SceneValidationError, TRANSITIONS;
|
|
573
597
|
var init_validate = __esm({
|
|
574
598
|
"../core/src/validate.ts"() {
|
|
575
599
|
"use strict";
|
|
576
|
-
|
|
600
|
+
FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY"];
|
|
601
|
+
COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed", ...FX_PROPS];
|
|
577
602
|
CAMERA_PROPS = ["x", "y", "zoom", "rotation"];
|
|
578
603
|
PROPS_BY_TYPE = {
|
|
579
604
|
rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
|
|
580
605
|
ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
|
|
581
|
-
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress"],
|
|
606
|
+
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
|
|
582
607
|
text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
583
608
|
image: [...COMMON_PROPS, "src", "width", "height"],
|
|
584
609
|
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
@@ -899,6 +924,20 @@ var init_camera = __esm({
|
|
|
899
924
|
}
|
|
900
925
|
});
|
|
901
926
|
|
|
927
|
+
// ../core/src/gradient.ts
|
|
928
|
+
var init_gradient = __esm({
|
|
929
|
+
"../core/src/gradient.ts"() {
|
|
930
|
+
"use strict";
|
|
931
|
+
}
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
// ../core/src/effects.ts
|
|
935
|
+
var init_effects = __esm({
|
|
936
|
+
"../core/src/effects.ts"() {
|
|
937
|
+
"use strict";
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
|
|
902
941
|
// ../core/src/presets.ts
|
|
903
942
|
function makeRng(seed) {
|
|
904
943
|
let a = seed >>> 0 || 2654435769;
|
|
@@ -1292,6 +1331,7 @@ var init_evaluate = __esm({
|
|
|
1292
1331
|
"use strict";
|
|
1293
1332
|
init_behaviors();
|
|
1294
1333
|
init_camera();
|
|
1334
|
+
init_gradient();
|
|
1295
1335
|
init_interpolate();
|
|
1296
1336
|
init_path();
|
|
1297
1337
|
}
|
|
@@ -1354,6 +1394,8 @@ var init_src = __esm({
|
|
|
1354
1394
|
init_compile();
|
|
1355
1395
|
init_path();
|
|
1356
1396
|
init_camera();
|
|
1397
|
+
init_gradient();
|
|
1398
|
+
init_effects();
|
|
1357
1399
|
init_presets();
|
|
1358
1400
|
init_devicePreset();
|
|
1359
1401
|
init_cursor();
|
package/dist/browserEntry.js
CHANGED
|
@@ -6,6 +6,22 @@
|
|
|
6
6
|
var DEFAULT_MOTIONPATH_DURATION = 1;
|
|
7
7
|
|
|
8
8
|
// ../core/src/path.ts
|
|
9
|
+
function pathBBox(d) {
|
|
10
|
+
const nums = d.match(/-?\d*\.?\d+(?:e[-+]?\d+)?/gi);
|
|
11
|
+
if (!nums || nums.length < 2) return [0, 0, 1, 1];
|
|
12
|
+
let minx = Infinity, miny = Infinity, maxx = -Infinity, maxy = -Infinity;
|
|
13
|
+
for (let i = 0; i + 1 < nums.length; i += 2) {
|
|
14
|
+
const x = parseFloat(nums[i]);
|
|
15
|
+
const y = parseFloat(nums[i + 1]);
|
|
16
|
+
if (x < minx) minx = x;
|
|
17
|
+
if (x > maxx) maxx = x;
|
|
18
|
+
if (y < miny) miny = y;
|
|
19
|
+
if (y > maxy) maxy = y;
|
|
20
|
+
}
|
|
21
|
+
const w = maxx - minx;
|
|
22
|
+
const h = maxy - miny;
|
|
23
|
+
return [minx, miny, w > 0 ? w : 1, h > 0 ? h : 1];
|
|
24
|
+
}
|
|
9
25
|
function locate(segCount, u) {
|
|
10
26
|
if (segCount <= 0) return { i: 0, t: 0 };
|
|
11
27
|
const clamped = Math.max(0, Math.min(1, u));
|
|
@@ -318,11 +334,12 @@
|
|
|
318
334
|
}
|
|
319
335
|
|
|
320
336
|
// ../core/src/validate.ts
|
|
321
|
-
var
|
|
337
|
+
var FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY"];
|
|
338
|
+
var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed", ...FX_PROPS];
|
|
322
339
|
var PROPS_BY_TYPE = {
|
|
323
340
|
rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
|
|
324
341
|
ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
|
|
325
|
-
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress"],
|
|
342
|
+
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
|
|
326
343
|
text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
327
344
|
image: [...COMMON_PROPS, "src", "width", "height"],
|
|
328
345
|
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
@@ -344,6 +361,11 @@
|
|
|
344
361
|
return [a, b, c, d, W / 2 - a * x - c * y, H / 2 - b * x - d * y];
|
|
345
362
|
}
|
|
346
363
|
|
|
364
|
+
// ../core/src/gradient.ts
|
|
365
|
+
function isGradient(p) {
|
|
366
|
+
return typeof p === "object" && p !== null && (p.kind === "linear" || p.kind === "radial" || p.kind === "conic");
|
|
367
|
+
}
|
|
368
|
+
|
|
347
369
|
// ../core/src/presets.ts
|
|
348
370
|
var SET = 1 / 120;
|
|
349
371
|
|
|
@@ -658,9 +680,21 @@
|
|
|
658
680
|
const v = valueAt(target, prop, base ?? "");
|
|
659
681
|
return v === "" && base === void 0 ? void 0 : String(v);
|
|
660
682
|
};
|
|
683
|
+
const effectFx = (id, p) => {
|
|
684
|
+
const fx = {};
|
|
685
|
+
if (p.blur !== void 0) fx.blur = num(id, "blur", p.blur);
|
|
686
|
+
if (p.shadowColor !== void 0) {
|
|
687
|
+
fx.shadowColor = str(id, "shadowColor", p.shadowColor);
|
|
688
|
+
fx.shadowBlur = num(id, "shadowBlur", p.shadowBlur ?? 0);
|
|
689
|
+
fx.shadowX = num(id, "shadowX", p.shadowX ?? 0);
|
|
690
|
+
fx.shadowY = num(id, "shadowY", p.shadowY ?? 0);
|
|
691
|
+
}
|
|
692
|
+
return fx;
|
|
693
|
+
};
|
|
661
694
|
const walk = (node, parent, parentOpacity, clips) => {
|
|
662
695
|
const id = node.id;
|
|
663
696
|
const clipSpread = clips.length > 0 ? { clips } : void 0;
|
|
697
|
+
const fx = effectFx(id, node.props);
|
|
664
698
|
if (node.type === "line") {
|
|
665
699
|
const opacity2 = parentOpacity * num(id, "opacity", node.props.opacity ?? 1);
|
|
666
700
|
if (opacity2 <= 0) return;
|
|
@@ -678,6 +712,7 @@
|
|
|
678
712
|
y2: y1 + (num(id, "y2", node.props.y2) - y1) * progress,
|
|
679
713
|
stroke: str(id, "stroke", node.props.stroke),
|
|
680
714
|
strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1),
|
|
715
|
+
...fx,
|
|
681
716
|
...clipSpread
|
|
682
717
|
});
|
|
683
718
|
return;
|
|
@@ -709,8 +744,10 @@
|
|
|
709
744
|
const height = num(id, "height", node.props.height);
|
|
710
745
|
const [ax, ay] = ANCHOR_FACTORS[node.props.anchor ?? "top-left"];
|
|
711
746
|
const strokeWidth = num(id, "strokeWidth", node.props.strokeWidth ?? 1);
|
|
712
|
-
const
|
|
713
|
-
const
|
|
747
|
+
const fillP = node.props.fill;
|
|
748
|
+
const strokeP = node.props.stroke;
|
|
749
|
+
const fill = isGradient(fillP) ? fillP : opt(id, "fill", fillP);
|
|
750
|
+
const stroke = isGradient(strokeP) ? strokeP : opt(id, "stroke", strokeP);
|
|
714
751
|
ops.push({
|
|
715
752
|
type: node.type,
|
|
716
753
|
id,
|
|
@@ -723,6 +760,7 @@
|
|
|
723
760
|
...fill !== void 0 && { fill },
|
|
724
761
|
...stroke !== void 0 && { stroke, strokeWidth },
|
|
725
762
|
...node.type === "rect" && { radius: num(id, "radius", node.props.radius ?? 0) },
|
|
763
|
+
...fx,
|
|
726
764
|
...clipSpread
|
|
727
765
|
});
|
|
728
766
|
return;
|
|
@@ -741,6 +779,7 @@
|
|
|
741
779
|
height,
|
|
742
780
|
offsetX: -width * ax,
|
|
743
781
|
offsetY: -height * ay,
|
|
782
|
+
...fx,
|
|
744
783
|
...clipSpread
|
|
745
784
|
});
|
|
746
785
|
return;
|
|
@@ -748,17 +787,23 @@
|
|
|
748
787
|
case "path": {
|
|
749
788
|
const ox = num(id, "originX", node.props.originX ?? 0);
|
|
750
789
|
const oy = num(id, "originY", node.props.originY ?? 0);
|
|
751
|
-
const
|
|
752
|
-
const
|
|
790
|
+
const fillP = node.props.fill;
|
|
791
|
+
const strokeP = node.props.stroke;
|
|
792
|
+
const fill = isGradient(fillP) ? fillP : opt(id, "fill", fillP);
|
|
793
|
+
const stroke = isGradient(strokeP) ? strokeP : opt(id, "stroke", strokeP);
|
|
794
|
+
const dStr = str(id, "d", node.props.d);
|
|
795
|
+
const needsBox = isGradient(fill) || isGradient(stroke);
|
|
753
796
|
ops.push({
|
|
754
797
|
type: "path",
|
|
755
798
|
id,
|
|
756
799
|
transform: ox === 0 && oy === 0 ? matrix : multiply(matrix, [1, 0, 0, 1, -ox, -oy]),
|
|
757
800
|
opacity,
|
|
758
|
-
d:
|
|
801
|
+
d: dStr,
|
|
759
802
|
progress: Math.max(0, Math.min(1, num(id, "progress", node.props.progress ?? 1))),
|
|
760
803
|
...fill !== void 0 && { fill },
|
|
761
804
|
...stroke !== void 0 && { stroke, strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1) },
|
|
805
|
+
...needsBox && { bbox: pathBBox(dStr) },
|
|
806
|
+
...fx,
|
|
762
807
|
...clipSpread
|
|
763
808
|
});
|
|
764
809
|
return;
|
|
@@ -783,6 +828,7 @@
|
|
|
783
828
|
letterSpacing: num(id, "letterSpacing", node.props.letterSpacing ?? 0),
|
|
784
829
|
align: TEXT_ALIGN[ax] ?? "left",
|
|
785
830
|
baseline: TEXT_BASELINE[ay] ?? "top",
|
|
831
|
+
...fx,
|
|
786
832
|
...clipSpread
|
|
787
833
|
});
|
|
788
834
|
return;
|
|
@@ -806,6 +852,31 @@
|
|
|
806
852
|
}
|
|
807
853
|
|
|
808
854
|
// ../renderer-canvas/src/index.ts
|
|
855
|
+
function resolvePaint(ctx2, paint, box) {
|
|
856
|
+
if (typeof paint === "string") return paint;
|
|
857
|
+
const { x, y, w, h } = box;
|
|
858
|
+
let g;
|
|
859
|
+
if (paint.kind === "linear") {
|
|
860
|
+
const a = (paint.angle ?? 0) * Math.PI / 180;
|
|
861
|
+
const dx = Math.cos(a);
|
|
862
|
+
const dy = Math.sin(a);
|
|
863
|
+
const cx = x + w / 2;
|
|
864
|
+
const cy = y + h / 2;
|
|
865
|
+
const half = Math.abs(dx) * (w / 2) + Math.abs(dy) * (h / 2);
|
|
866
|
+
g = ctx2.createLinearGradient(cx - dx * half, cy - dy * half, cx + dx * half, cy + dy * half);
|
|
867
|
+
} else if (paint.kind === "radial") {
|
|
868
|
+
const cx = x + (paint.cx ?? 0.5) * w;
|
|
869
|
+
const cy = y + (paint.cy ?? 0.5) * h;
|
|
870
|
+
const r = Math.max((paint.r ?? 0.5) * Math.max(w, h), 1e-4);
|
|
871
|
+
g = ctx2.createRadialGradient(cx, cy, 0, cx, cy, r);
|
|
872
|
+
} else {
|
|
873
|
+
const cx = x + (paint.cx ?? 0.5) * w;
|
|
874
|
+
const cy = y + (paint.cy ?? 0.5) * h;
|
|
875
|
+
g = ctx2.createConicGradient((paint.angle ?? 0) * Math.PI / 180, cx, cy);
|
|
876
|
+
}
|
|
877
|
+
for (const s of paint.stops) g.addColorStop(Math.max(0, Math.min(1, s.offset)), s.color);
|
|
878
|
+
return g;
|
|
879
|
+
}
|
|
809
880
|
function renderFrame(ctx2, compiled2, t, images2) {
|
|
810
881
|
const { size, background } = compiled2.ir;
|
|
811
882
|
ctx2.setTransform(1, 0, 0, 1, 0, 0);
|
|
@@ -836,8 +907,16 @@
|
|
|
836
907
|
}
|
|
837
908
|
ctx2.setTransform(...op.transform);
|
|
838
909
|
ctx2.globalAlpha = Math.max(0, Math.min(1, op.opacity));
|
|
910
|
+
if (op.blur) ctx2.filter = `blur(${op.blur}px)`;
|
|
911
|
+
if (op.shadowColor) {
|
|
912
|
+
ctx2.shadowColor = op.shadowColor;
|
|
913
|
+
ctx2.shadowBlur = op.shadowBlur ?? 0;
|
|
914
|
+
ctx2.shadowOffsetX = op.shadowX ?? 0;
|
|
915
|
+
ctx2.shadowOffsetY = op.shadowY ?? 0;
|
|
916
|
+
}
|
|
839
917
|
switch (op.type) {
|
|
840
918
|
case "rect": {
|
|
919
|
+
const box = { x: op.offsetX, y: op.offsetY, w: op.width, h: op.height };
|
|
841
920
|
ctx2.beginPath();
|
|
842
921
|
if (op.radius && op.radius > 0) {
|
|
843
922
|
ctx2.roundRect(op.offsetX, op.offsetY, op.width, op.height, op.radius);
|
|
@@ -845,17 +924,18 @@
|
|
|
845
924
|
ctx2.rect(op.offsetX, op.offsetY, op.width, op.height);
|
|
846
925
|
}
|
|
847
926
|
if (op.fill) {
|
|
848
|
-
ctx2.fillStyle = op.fill;
|
|
927
|
+
ctx2.fillStyle = resolvePaint(ctx2, op.fill, box);
|
|
849
928
|
ctx2.fill();
|
|
850
929
|
}
|
|
851
930
|
if (op.stroke) {
|
|
852
|
-
ctx2.strokeStyle = op.stroke;
|
|
931
|
+
ctx2.strokeStyle = resolvePaint(ctx2, op.stroke, box);
|
|
853
932
|
ctx2.lineWidth = op.strokeWidth ?? 1;
|
|
854
933
|
ctx2.stroke();
|
|
855
934
|
}
|
|
856
935
|
break;
|
|
857
936
|
}
|
|
858
937
|
case "ellipse": {
|
|
938
|
+
const box = { x: op.offsetX, y: op.offsetY, w: op.width, h: op.height };
|
|
859
939
|
ctx2.beginPath();
|
|
860
940
|
ctx2.ellipse(
|
|
861
941
|
op.offsetX + op.width / 2,
|
|
@@ -867,11 +947,11 @@
|
|
|
867
947
|
Math.PI * 2
|
|
868
948
|
);
|
|
869
949
|
if (op.fill) {
|
|
870
|
-
ctx2.fillStyle = op.fill;
|
|
950
|
+
ctx2.fillStyle = resolvePaint(ctx2, op.fill, box);
|
|
871
951
|
ctx2.fill();
|
|
872
952
|
}
|
|
873
953
|
if (op.stroke) {
|
|
874
|
-
ctx2.strokeStyle = op.stroke;
|
|
954
|
+
ctx2.strokeStyle = resolvePaint(ctx2, op.stroke, box);
|
|
875
955
|
ctx2.lineWidth = op.strokeWidth ?? 1;
|
|
876
956
|
ctx2.stroke();
|
|
877
957
|
}
|
|
@@ -908,12 +988,13 @@
|
|
|
908
988
|
}
|
|
909
989
|
case "path": {
|
|
910
990
|
const p = new Path2D(op.d);
|
|
991
|
+
const box = op.bbox ? { x: op.bbox[0], y: op.bbox[1], w: op.bbox[2], h: op.bbox[3] } : { x: 0, y: 0, w: 1, h: 1 };
|
|
911
992
|
if (op.fill) {
|
|
912
|
-
ctx2.fillStyle = op.fill;
|
|
993
|
+
ctx2.fillStyle = resolvePaint(ctx2, op.fill, box);
|
|
913
994
|
ctx2.fill(p);
|
|
914
995
|
}
|
|
915
996
|
if (op.stroke && (op.strokeWidth ?? 1) > 0) {
|
|
916
|
-
ctx2.strokeStyle = op.stroke;
|
|
997
|
+
ctx2.strokeStyle = resolvePaint(ctx2, op.stroke, box);
|
|
917
998
|
ctx2.lineWidth = op.strokeWidth ?? 1;
|
|
918
999
|
ctx2.lineJoin = "round";
|
|
919
1000
|
ctx2.lineCap = "round";
|
package/dist/cli.js
CHANGED
|
@@ -324,12 +324,13 @@ function compileScene(ir) {
|
|
|
324
324
|
}
|
|
325
325
|
|
|
326
326
|
// ../core/src/validate.ts
|
|
327
|
-
var
|
|
327
|
+
var FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY"];
|
|
328
|
+
var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed", ...FX_PROPS];
|
|
328
329
|
var CAMERA_PROPS = ["x", "y", "zoom", "rotation"];
|
|
329
330
|
var PROPS_BY_TYPE = {
|
|
330
331
|
rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
|
|
331
332
|
ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
|
|
332
|
-
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress"],
|
|
333
|
+
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
|
|
333
334
|
text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
334
335
|
image: [...COMMON_PROPS, "src", "width", "height"],
|
|
335
336
|
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
@@ -347,12 +348,36 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
347
348
|
function validateScene(ir) {
|
|
348
349
|
const problems = [];
|
|
349
350
|
const nodeById = /* @__PURE__ */ new Map();
|
|
351
|
+
const checkPaint = (where, value) => {
|
|
352
|
+
if (typeof value !== "object" || value === null) return;
|
|
353
|
+
const g = value;
|
|
354
|
+
if (g.kind !== "linear" && g.kind !== "radial" && g.kind !== "conic") {
|
|
355
|
+
problems.push(`${where}: a paint object must be a gradient with kind "linear" / "radial" / "conic"`);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (!Array.isArray(g.stops) || g.stops.length === 0) {
|
|
359
|
+
problems.push(`${where}: gradient "${g.kind}" needs at least one color stop`);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
g.stops.forEach((s, i) => {
|
|
363
|
+
const st = s;
|
|
364
|
+
if (typeof st?.color !== "string") problems.push(`${where}: gradient stop ${i} needs a color string`);
|
|
365
|
+
if (typeof st?.offset !== "number" || st.offset < 0 || st.offset > 1) {
|
|
366
|
+
problems.push(`${where}: gradient stop ${i} "offset" must be a number in 0..1`);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
};
|
|
350
370
|
const collect = (nodes) => {
|
|
351
371
|
for (const node of nodes) {
|
|
352
372
|
if (nodeById.has(node.id)) {
|
|
353
373
|
problems.push(`duplicate node id "${node.id}" \u2014 every node id must be unique`);
|
|
354
374
|
}
|
|
355
375
|
nodeById.set(node.id, node);
|
|
376
|
+
const props = node.props;
|
|
377
|
+
checkPaint(`node "${node.id}" fill`, props.fill);
|
|
378
|
+
checkPaint(`node "${node.id}" stroke`, props.stroke);
|
|
379
|
+
if (typeof props.blur === "number" && props.blur < 0) problems.push(`node "${node.id}": blur must be >= 0`);
|
|
380
|
+
if (typeof props.shadowBlur === "number" && props.shadowBlur < 0) problems.push(`node "${node.id}": shadowBlur must be >= 0`);
|
|
356
381
|
if (node.type === "group") {
|
|
357
382
|
const clip = node.props.clip;
|
|
358
383
|
if (clip) {
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,22 @@ var DEFAULT_MOTIONPATH_DURATION = 1;
|
|
|
6
6
|
var DEFAULT_FPS = 30;
|
|
7
7
|
|
|
8
8
|
// ../core/src/path.ts
|
|
9
|
+
function pathBBox(d) {
|
|
10
|
+
const nums = d.match(/-?\d*\.?\d+(?:e[-+]?\d+)?/gi);
|
|
11
|
+
if (!nums || nums.length < 2) return [0, 0, 1, 1];
|
|
12
|
+
let minx = Infinity, miny = Infinity, maxx = -Infinity, maxy = -Infinity;
|
|
13
|
+
for (let i = 0; i + 1 < nums.length; i += 2) {
|
|
14
|
+
const x = parseFloat(nums[i]);
|
|
15
|
+
const y = parseFloat(nums[i + 1]);
|
|
16
|
+
if (x < minx) minx = x;
|
|
17
|
+
if (x > maxx) maxx = x;
|
|
18
|
+
if (y < miny) miny = y;
|
|
19
|
+
if (y > maxy) maxy = y;
|
|
20
|
+
}
|
|
21
|
+
const w = maxx - minx;
|
|
22
|
+
const h = maxy - miny;
|
|
23
|
+
return [minx, miny, w > 0 ? w : 1, h > 0 ? h : 1];
|
|
24
|
+
}
|
|
9
25
|
function locate(segCount, u) {
|
|
10
26
|
if (segCount <= 0) return { i: 0, t: 0 };
|
|
11
27
|
const clamped = Math.max(0, Math.min(1, u));
|
|
@@ -318,12 +334,13 @@ function compileScene(ir) {
|
|
|
318
334
|
}
|
|
319
335
|
|
|
320
336
|
// ../core/src/validate.ts
|
|
321
|
-
var
|
|
337
|
+
var FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY"];
|
|
338
|
+
var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed", ...FX_PROPS];
|
|
322
339
|
var CAMERA_PROPS = ["x", "y", "zoom", "rotation"];
|
|
323
340
|
var PROPS_BY_TYPE = {
|
|
324
341
|
rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
|
|
325
342
|
ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
|
|
326
|
-
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress"],
|
|
343
|
+
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
|
|
327
344
|
text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
328
345
|
image: [...COMMON_PROPS, "src", "width", "height"],
|
|
329
346
|
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
@@ -341,12 +358,36 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
341
358
|
function validateScene(ir) {
|
|
342
359
|
const problems = [];
|
|
343
360
|
const nodeById = /* @__PURE__ */ new Map();
|
|
361
|
+
const checkPaint = (where, value) => {
|
|
362
|
+
if (typeof value !== "object" || value === null) return;
|
|
363
|
+
const g = value;
|
|
364
|
+
if (g.kind !== "linear" && g.kind !== "radial" && g.kind !== "conic") {
|
|
365
|
+
problems.push(`${where}: a paint object must be a gradient with kind "linear" / "radial" / "conic"`);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
if (!Array.isArray(g.stops) || g.stops.length === 0) {
|
|
369
|
+
problems.push(`${where}: gradient "${g.kind}" needs at least one color stop`);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
g.stops.forEach((s, i) => {
|
|
373
|
+
const st = s;
|
|
374
|
+
if (typeof st?.color !== "string") problems.push(`${where}: gradient stop ${i} needs a color string`);
|
|
375
|
+
if (typeof st?.offset !== "number" || st.offset < 0 || st.offset > 1) {
|
|
376
|
+
problems.push(`${where}: gradient stop ${i} "offset" must be a number in 0..1`);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
};
|
|
344
380
|
const collect = (nodes) => {
|
|
345
381
|
for (const node of nodes) {
|
|
346
382
|
if (nodeById.has(node.id)) {
|
|
347
383
|
problems.push(`duplicate node id "${node.id}" \u2014 every node id must be unique`);
|
|
348
384
|
}
|
|
349
385
|
nodeById.set(node.id, node);
|
|
386
|
+
const props = node.props;
|
|
387
|
+
checkPaint(`node "${node.id}" fill`, props.fill);
|
|
388
|
+
checkPaint(`node "${node.id}" stroke`, props.stroke);
|
|
389
|
+
if (typeof props.blur === "number" && props.blur < 0) problems.push(`node "${node.id}": blur must be >= 0`);
|
|
390
|
+
if (typeof props.shadowBlur === "number" && props.shadowBlur < 0) problems.push(`node "${node.id}": shadowBlur must be >= 0`);
|
|
350
391
|
if (node.type === "group") {
|
|
351
392
|
const clip = node.props.clip;
|
|
352
393
|
if (clip) {
|
|
@@ -917,6 +958,46 @@ function cameraTo(props, opts = {}) {
|
|
|
917
958
|
return tween(CAMERA_ID, props, opts);
|
|
918
959
|
}
|
|
919
960
|
|
|
961
|
+
// ../core/src/gradient.ts
|
|
962
|
+
function isGradient(p) {
|
|
963
|
+
return typeof p === "object" && p !== null && (p.kind === "linear" || p.kind === "radial" || p.kind === "conic");
|
|
964
|
+
}
|
|
965
|
+
function toStops(stops) {
|
|
966
|
+
if (stops.length > 0 && typeof stops[0] === "object") return stops;
|
|
967
|
+
const cs = stops;
|
|
968
|
+
const n3 = cs.length;
|
|
969
|
+
return cs.map((color, i) => ({ offset: n3 <= 1 ? 0 : i / (n3 - 1), color }));
|
|
970
|
+
}
|
|
971
|
+
function linearGradient(stops, opts = {}) {
|
|
972
|
+
return { kind: "linear", ...opts.angle !== void 0 && { angle: opts.angle }, stops: toStops(stops) };
|
|
973
|
+
}
|
|
974
|
+
function radialGradient(stops, opts = {}) {
|
|
975
|
+
return {
|
|
976
|
+
kind: "radial",
|
|
977
|
+
...opts.cx !== void 0 && { cx: opts.cx },
|
|
978
|
+
...opts.cy !== void 0 && { cy: opts.cy },
|
|
979
|
+
...opts.r !== void 0 && { r: opts.r },
|
|
980
|
+
stops: toStops(stops)
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
function conicGradient(stops, opts = {}) {
|
|
984
|
+
return {
|
|
985
|
+
kind: "conic",
|
|
986
|
+
...opts.angle !== void 0 && { angle: opts.angle },
|
|
987
|
+
...opts.cx !== void 0 && { cx: opts.cx },
|
|
988
|
+
...opts.cy !== void 0 && { cy: opts.cy },
|
|
989
|
+
stops: toStops(stops)
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// ../core/src/effects.ts
|
|
994
|
+
function glow(color, blur = 24) {
|
|
995
|
+
return { shadowColor: color, shadowBlur: blur, shadowX: 0, shadowY: 0 };
|
|
996
|
+
}
|
|
997
|
+
function dropShadow(color, blur = 24, x = 0, y = 12) {
|
|
998
|
+
return { shadowColor: color, shadowBlur: blur, shadowX: x, shadowY: y };
|
|
999
|
+
}
|
|
1000
|
+
|
|
920
1001
|
// ../core/src/presets.ts
|
|
921
1002
|
var PRESET_NAMES = [
|
|
922
1003
|
"draw-bloom",
|
|
@@ -1456,11 +1537,11 @@ function ikReach(upper, lower, dx, dy, flip = false) {
|
|
|
1456
1537
|
function humanoid(opts = {}) {
|
|
1457
1538
|
const line2 = opts.color ?? DEFAULT_LINE;
|
|
1458
1539
|
const fill = opts.fill ?? DEFAULT_FILL;
|
|
1459
|
-
const
|
|
1540
|
+
const glow2 = opts.glow;
|
|
1460
1541
|
const blob = (jid, a, b, cy) => {
|
|
1461
1542
|
const d = ovalPath(a, b, 0, cy);
|
|
1462
1543
|
const nodes = [];
|
|
1463
|
-
if (
|
|
1544
|
+
if (glow2) nodes.push(path({ id: `${jid}-glow`, d, x: 0, y: 0, fill: "none", stroke: glow2, strokeWidth: GLOW_W, opacity: 0.18 }));
|
|
1464
1545
|
nodes.push(path({ id: `${jid}-shape`, d, x: 0, y: 0, fill, stroke: line2, strokeWidth: LINE_W }));
|
|
1465
1546
|
return nodes;
|
|
1466
1547
|
};
|
|
@@ -2868,9 +2949,21 @@ function evaluate(compiled, t) {
|
|
|
2868
2949
|
const v = valueAt(target, prop, base ?? "");
|
|
2869
2950
|
return v === "" && base === void 0 ? void 0 : String(v);
|
|
2870
2951
|
};
|
|
2952
|
+
const effectFx = (id, p) => {
|
|
2953
|
+
const fx = {};
|
|
2954
|
+
if (p.blur !== void 0) fx.blur = num(id, "blur", p.blur);
|
|
2955
|
+
if (p.shadowColor !== void 0) {
|
|
2956
|
+
fx.shadowColor = str(id, "shadowColor", p.shadowColor);
|
|
2957
|
+
fx.shadowBlur = num(id, "shadowBlur", p.shadowBlur ?? 0);
|
|
2958
|
+
fx.shadowX = num(id, "shadowX", p.shadowX ?? 0);
|
|
2959
|
+
fx.shadowY = num(id, "shadowY", p.shadowY ?? 0);
|
|
2960
|
+
}
|
|
2961
|
+
return fx;
|
|
2962
|
+
};
|
|
2871
2963
|
const walk = (node, parent, parentOpacity, clips) => {
|
|
2872
2964
|
const id = node.id;
|
|
2873
2965
|
const clipSpread = clips.length > 0 ? { clips } : void 0;
|
|
2966
|
+
const fx = effectFx(id, node.props);
|
|
2874
2967
|
if (node.type === "line") {
|
|
2875
2968
|
const opacity2 = parentOpacity * num(id, "opacity", node.props.opacity ?? 1);
|
|
2876
2969
|
if (opacity2 <= 0) return;
|
|
@@ -2888,6 +2981,7 @@ function evaluate(compiled, t) {
|
|
|
2888
2981
|
y2: y1 + (num(id, "y2", node.props.y2) - y1) * progress,
|
|
2889
2982
|
stroke: str(id, "stroke", node.props.stroke),
|
|
2890
2983
|
strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1),
|
|
2984
|
+
...fx,
|
|
2891
2985
|
...clipSpread
|
|
2892
2986
|
});
|
|
2893
2987
|
return;
|
|
@@ -2919,8 +3013,10 @@ function evaluate(compiled, t) {
|
|
|
2919
3013
|
const height = num(id, "height", node.props.height);
|
|
2920
3014
|
const [ax, ay] = ANCHOR_FACTORS[node.props.anchor ?? "top-left"];
|
|
2921
3015
|
const strokeWidth = num(id, "strokeWidth", node.props.strokeWidth ?? 1);
|
|
2922
|
-
const
|
|
2923
|
-
const
|
|
3016
|
+
const fillP = node.props.fill;
|
|
3017
|
+
const strokeP = node.props.stroke;
|
|
3018
|
+
const fill = isGradient(fillP) ? fillP : opt(id, "fill", fillP);
|
|
3019
|
+
const stroke = isGradient(strokeP) ? strokeP : opt(id, "stroke", strokeP);
|
|
2924
3020
|
ops.push({
|
|
2925
3021
|
type: node.type,
|
|
2926
3022
|
id,
|
|
@@ -2933,6 +3029,7 @@ function evaluate(compiled, t) {
|
|
|
2933
3029
|
...fill !== void 0 && { fill },
|
|
2934
3030
|
...stroke !== void 0 && { stroke, strokeWidth },
|
|
2935
3031
|
...node.type === "rect" && { radius: num(id, "radius", node.props.radius ?? 0) },
|
|
3032
|
+
...fx,
|
|
2936
3033
|
...clipSpread
|
|
2937
3034
|
});
|
|
2938
3035
|
return;
|
|
@@ -2951,6 +3048,7 @@ function evaluate(compiled, t) {
|
|
|
2951
3048
|
height,
|
|
2952
3049
|
offsetX: -width * ax,
|
|
2953
3050
|
offsetY: -height * ay,
|
|
3051
|
+
...fx,
|
|
2954
3052
|
...clipSpread
|
|
2955
3053
|
});
|
|
2956
3054
|
return;
|
|
@@ -2958,17 +3056,23 @@ function evaluate(compiled, t) {
|
|
|
2958
3056
|
case "path": {
|
|
2959
3057
|
const ox = num(id, "originX", node.props.originX ?? 0);
|
|
2960
3058
|
const oy = num(id, "originY", node.props.originY ?? 0);
|
|
2961
|
-
const
|
|
2962
|
-
const
|
|
3059
|
+
const fillP = node.props.fill;
|
|
3060
|
+
const strokeP = node.props.stroke;
|
|
3061
|
+
const fill = isGradient(fillP) ? fillP : opt(id, "fill", fillP);
|
|
3062
|
+
const stroke = isGradient(strokeP) ? strokeP : opt(id, "stroke", strokeP);
|
|
3063
|
+
const dStr = str(id, "d", node.props.d);
|
|
3064
|
+
const needsBox = isGradient(fill) || isGradient(stroke);
|
|
2963
3065
|
ops.push({
|
|
2964
3066
|
type: "path",
|
|
2965
3067
|
id,
|
|
2966
3068
|
transform: ox === 0 && oy === 0 ? matrix : multiply(matrix, [1, 0, 0, 1, -ox, -oy]),
|
|
2967
3069
|
opacity,
|
|
2968
|
-
d:
|
|
3070
|
+
d: dStr,
|
|
2969
3071
|
progress: Math.max(0, Math.min(1, num(id, "progress", node.props.progress ?? 1))),
|
|
2970
3072
|
...fill !== void 0 && { fill },
|
|
2971
3073
|
...stroke !== void 0 && { stroke, strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1) },
|
|
3074
|
+
...needsBox && { bbox: pathBBox(dStr) },
|
|
3075
|
+
...fx,
|
|
2972
3076
|
...clipSpread
|
|
2973
3077
|
});
|
|
2974
3078
|
return;
|
|
@@ -2993,6 +3097,7 @@ function evaluate(compiled, t) {
|
|
|
2993
3097
|
letterSpacing: num(id, "letterSpacing", node.props.letterSpacing ?? 0),
|
|
2994
3098
|
align: TEXT_ALIGN[ax] ?? "left",
|
|
2995
3099
|
baseline: TEXT_BASELINE[ay] ?? "top",
|
|
3100
|
+
...fx,
|
|
2996
3101
|
...clipSpread
|
|
2997
3102
|
});
|
|
2998
3103
|
return;
|
|
@@ -3116,6 +3221,7 @@ export {
|
|
|
3116
3221
|
compileScene,
|
|
3117
3222
|
composeScene,
|
|
3118
3223
|
composition,
|
|
3224
|
+
conicGradient,
|
|
3119
3225
|
cursor,
|
|
3120
3226
|
cursorClick,
|
|
3121
3227
|
cursorDouble,
|
|
@@ -3126,17 +3232,21 @@ export {
|
|
|
3126
3232
|
deviceScreen,
|
|
3127
3233
|
deviceScreenCenter,
|
|
3128
3234
|
deviceScreenPoint,
|
|
3235
|
+
dropShadow,
|
|
3129
3236
|
ellipse,
|
|
3130
3237
|
evaluate,
|
|
3131
3238
|
figure,
|
|
3132
3239
|
formatComposeReport,
|
|
3240
|
+
glow,
|
|
3133
3241
|
group,
|
|
3134
3242
|
humanoid,
|
|
3135
3243
|
ikReach,
|
|
3136
3244
|
image,
|
|
3137
3245
|
isColor,
|
|
3246
|
+
isGradient,
|
|
3138
3247
|
lerpValue,
|
|
3139
3248
|
line,
|
|
3249
|
+
linearGradient,
|
|
3140
3250
|
motionOp,
|
|
3141
3251
|
motionOpLabel,
|
|
3142
3252
|
motionPath,
|
|
@@ -3149,6 +3259,7 @@ export {
|
|
|
3149
3259
|
pathPoint,
|
|
3150
3260
|
pathTangentAngle,
|
|
3151
3261
|
poseTo,
|
|
3262
|
+
radialGradient,
|
|
3152
3263
|
rect,
|
|
3153
3264
|
resolveAudioPlan,
|
|
3154
3265
|
resolveCompositionAudioPlan,
|
package/dist/labels.js
CHANGED
|
@@ -318,12 +318,13 @@ function compileScene(ir) {
|
|
|
318
318
|
}
|
|
319
319
|
|
|
320
320
|
// ../core/src/validate.ts
|
|
321
|
-
var
|
|
321
|
+
var FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY"];
|
|
322
|
+
var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed", ...FX_PROPS];
|
|
322
323
|
var CAMERA_PROPS = ["x", "y", "zoom", "rotation"];
|
|
323
324
|
var PROPS_BY_TYPE = {
|
|
324
325
|
rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
|
|
325
326
|
ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
|
|
326
|
-
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress"],
|
|
327
|
+
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
|
|
327
328
|
text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
328
329
|
image: [...COMMON_PROPS, "src", "width", "height"],
|
|
329
330
|
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
@@ -341,12 +342,36 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
341
342
|
function validateScene(ir) {
|
|
342
343
|
const problems = [];
|
|
343
344
|
const nodeById = /* @__PURE__ */ new Map();
|
|
345
|
+
const checkPaint = (where, value) => {
|
|
346
|
+
if (typeof value !== "object" || value === null) return;
|
|
347
|
+
const g = value;
|
|
348
|
+
if (g.kind !== "linear" && g.kind !== "radial" && g.kind !== "conic") {
|
|
349
|
+
problems.push(`${where}: a paint object must be a gradient with kind "linear" / "radial" / "conic"`);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (!Array.isArray(g.stops) || g.stops.length === 0) {
|
|
353
|
+
problems.push(`${where}: gradient "${g.kind}" needs at least one color stop`);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
g.stops.forEach((s, i) => {
|
|
357
|
+
const st = s;
|
|
358
|
+
if (typeof st?.color !== "string") problems.push(`${where}: gradient stop ${i} needs a color string`);
|
|
359
|
+
if (typeof st?.offset !== "number" || st.offset < 0 || st.offset > 1) {
|
|
360
|
+
problems.push(`${where}: gradient stop ${i} "offset" must be a number in 0..1`);
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
};
|
|
344
364
|
const collect = (nodes) => {
|
|
345
365
|
for (const node of nodes) {
|
|
346
366
|
if (nodeById.has(node.id)) {
|
|
347
367
|
problems.push(`duplicate node id "${node.id}" \u2014 every node id must be unique`);
|
|
348
368
|
}
|
|
349
369
|
nodeById.set(node.id, node);
|
|
370
|
+
const props = node.props;
|
|
371
|
+
checkPaint(`node "${node.id}" fill`, props.fill);
|
|
372
|
+
checkPaint(`node "${node.id}" stroke`, props.stroke);
|
|
373
|
+
if (typeof props.blur === "number" && props.blur < 0) problems.push(`node "${node.id}": blur must be >= 0`);
|
|
374
|
+
if (typeof props.shadowBlur === "number" && props.shadowBlur < 0) problems.push(`node "${node.id}": shadowBlur must be >= 0`);
|
|
350
375
|
if (node.type === "group") {
|
|
351
376
|
const clip = node.props.clip;
|
|
352
377
|
if (clip) {
|
package/dist/renderer-canvas.js
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
// ../renderer-canvas/src/index.ts
|
|
2
2
|
import { evaluate } from "@reframe/core";
|
|
3
|
+
function resolvePaint(ctx, paint, box) {
|
|
4
|
+
if (typeof paint === "string") return paint;
|
|
5
|
+
const { x, y, w, h } = box;
|
|
6
|
+
let g;
|
|
7
|
+
if (paint.kind === "linear") {
|
|
8
|
+
const a = (paint.angle ?? 0) * Math.PI / 180;
|
|
9
|
+
const dx = Math.cos(a);
|
|
10
|
+
const dy = Math.sin(a);
|
|
11
|
+
const cx = x + w / 2;
|
|
12
|
+
const cy = y + h / 2;
|
|
13
|
+
const half = Math.abs(dx) * (w / 2) + Math.abs(dy) * (h / 2);
|
|
14
|
+
g = ctx.createLinearGradient(cx - dx * half, cy - dy * half, cx + dx * half, cy + dy * half);
|
|
15
|
+
} else if (paint.kind === "radial") {
|
|
16
|
+
const cx = x + (paint.cx ?? 0.5) * w;
|
|
17
|
+
const cy = y + (paint.cy ?? 0.5) * h;
|
|
18
|
+
const r = Math.max((paint.r ?? 0.5) * Math.max(w, h), 1e-4);
|
|
19
|
+
g = ctx.createRadialGradient(cx, cy, 0, cx, cy, r);
|
|
20
|
+
} else {
|
|
21
|
+
const cx = x + (paint.cx ?? 0.5) * w;
|
|
22
|
+
const cy = y + (paint.cy ?? 0.5) * h;
|
|
23
|
+
g = ctx.createConicGradient((paint.angle ?? 0) * Math.PI / 180, cx, cy);
|
|
24
|
+
}
|
|
25
|
+
for (const s of paint.stops) g.addColorStop(Math.max(0, Math.min(1, s.offset)), s.color);
|
|
26
|
+
return g;
|
|
27
|
+
}
|
|
3
28
|
function renderFrame(ctx, compiled, t, images) {
|
|
4
29
|
const { size, background } = compiled.ir;
|
|
5
30
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
@@ -30,8 +55,16 @@ function drawDisplayList(ctx, ops, images) {
|
|
|
30
55
|
}
|
|
31
56
|
ctx.setTransform(...op.transform);
|
|
32
57
|
ctx.globalAlpha = Math.max(0, Math.min(1, op.opacity));
|
|
58
|
+
if (op.blur) ctx.filter = `blur(${op.blur}px)`;
|
|
59
|
+
if (op.shadowColor) {
|
|
60
|
+
ctx.shadowColor = op.shadowColor;
|
|
61
|
+
ctx.shadowBlur = op.shadowBlur ?? 0;
|
|
62
|
+
ctx.shadowOffsetX = op.shadowX ?? 0;
|
|
63
|
+
ctx.shadowOffsetY = op.shadowY ?? 0;
|
|
64
|
+
}
|
|
33
65
|
switch (op.type) {
|
|
34
66
|
case "rect": {
|
|
67
|
+
const box = { x: op.offsetX, y: op.offsetY, w: op.width, h: op.height };
|
|
35
68
|
ctx.beginPath();
|
|
36
69
|
if (op.radius && op.radius > 0) {
|
|
37
70
|
ctx.roundRect(op.offsetX, op.offsetY, op.width, op.height, op.radius);
|
|
@@ -39,17 +72,18 @@ function drawDisplayList(ctx, ops, images) {
|
|
|
39
72
|
ctx.rect(op.offsetX, op.offsetY, op.width, op.height);
|
|
40
73
|
}
|
|
41
74
|
if (op.fill) {
|
|
42
|
-
ctx.fillStyle = op.fill;
|
|
75
|
+
ctx.fillStyle = resolvePaint(ctx, op.fill, box);
|
|
43
76
|
ctx.fill();
|
|
44
77
|
}
|
|
45
78
|
if (op.stroke) {
|
|
46
|
-
ctx.strokeStyle = op.stroke;
|
|
79
|
+
ctx.strokeStyle = resolvePaint(ctx, op.stroke, box);
|
|
47
80
|
ctx.lineWidth = op.strokeWidth ?? 1;
|
|
48
81
|
ctx.stroke();
|
|
49
82
|
}
|
|
50
83
|
break;
|
|
51
84
|
}
|
|
52
85
|
case "ellipse": {
|
|
86
|
+
const box = { x: op.offsetX, y: op.offsetY, w: op.width, h: op.height };
|
|
53
87
|
ctx.beginPath();
|
|
54
88
|
ctx.ellipse(
|
|
55
89
|
op.offsetX + op.width / 2,
|
|
@@ -61,11 +95,11 @@ function drawDisplayList(ctx, ops, images) {
|
|
|
61
95
|
Math.PI * 2
|
|
62
96
|
);
|
|
63
97
|
if (op.fill) {
|
|
64
|
-
ctx.fillStyle = op.fill;
|
|
98
|
+
ctx.fillStyle = resolvePaint(ctx, op.fill, box);
|
|
65
99
|
ctx.fill();
|
|
66
100
|
}
|
|
67
101
|
if (op.stroke) {
|
|
68
|
-
ctx.strokeStyle = op.stroke;
|
|
102
|
+
ctx.strokeStyle = resolvePaint(ctx, op.stroke, box);
|
|
69
103
|
ctx.lineWidth = op.strokeWidth ?? 1;
|
|
70
104
|
ctx.stroke();
|
|
71
105
|
}
|
|
@@ -102,12 +136,13 @@ function drawDisplayList(ctx, ops, images) {
|
|
|
102
136
|
}
|
|
103
137
|
case "path": {
|
|
104
138
|
const p = new Path2D(op.d);
|
|
139
|
+
const box = op.bbox ? { x: op.bbox[0], y: op.bbox[1], w: op.bbox[2], h: op.bbox[3] } : { x: 0, y: 0, w: 1, h: 1 };
|
|
105
140
|
if (op.fill) {
|
|
106
|
-
ctx.fillStyle = op.fill;
|
|
141
|
+
ctx.fillStyle = resolvePaint(ctx, op.fill, box);
|
|
107
142
|
ctx.fill(p);
|
|
108
143
|
}
|
|
109
144
|
if (op.stroke && (op.strokeWidth ?? 1) > 0) {
|
|
110
|
-
ctx.strokeStyle = op.stroke;
|
|
145
|
+
ctx.strokeStyle = resolvePaint(ctx, op.stroke, box);
|
|
111
146
|
ctx.lineWidth = op.strokeWidth ?? 1;
|
|
112
147
|
ctx.lineJoin = "round";
|
|
113
148
|
ctx.lineCap = "round";
|
package/dist/trace-cli.js
CHANGED
|
@@ -6,11 +6,12 @@ import { resolve as resolve2 } from "node:path";
|
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
7
|
|
|
8
8
|
// ../core/src/validate.ts
|
|
9
|
-
var
|
|
9
|
+
var FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY"];
|
|
10
|
+
var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed", ...FX_PROPS];
|
|
10
11
|
var PROPS_BY_TYPE = {
|
|
11
12
|
rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
|
|
12
13
|
ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
|
|
13
|
-
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress"],
|
|
14
|
+
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
|
|
14
15
|
text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
15
16
|
image: [...COMMON_PROPS, "src", "width", "height"],
|
|
16
17
|
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drop-shadow / outer-glow sugar. These return a partial-props object you spread
|
|
3
|
+
* into a shape node; the underlying `shadow*` props stay animatable (e.g. pulse a
|
|
4
|
+
* glow with `oscillate(id, "shadowBlur", …)`). Units are screen pixels.
|
|
5
|
+
*/
|
|
6
|
+
import type { BaseProps } from "./ir.js";
|
|
7
|
+
type ShadowProps = Pick<BaseProps, "shadowColor" | "shadowBlur" | "shadowX" | "shadowY">;
|
|
8
|
+
/** An outer glow — a shadow with no offset. `rect({ …, ...glow("#FFD24B", 28) })`. */
|
|
9
|
+
export declare function glow(color: string, blur?: number): ShadowProps;
|
|
10
|
+
/** A drop shadow (offset downward by default). `rect({ …, ...dropShadow("#0008", 40, 0, 16) })`. */
|
|
11
|
+
export declare function dropShadow(color: string, blur?: number, x?: number, y?: number): ShadowProps;
|
|
12
|
+
export {};
|
package/dist/types/evaluate.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* always. Renderers only draw; they never compute animation.
|
|
5
5
|
*/
|
|
6
6
|
import type { CompiledScene } from "./compile.js";
|
|
7
|
-
import type { ClipShape, PropValue } from "./ir.js";
|
|
7
|
+
import type { ClipShape, Paint, PropValue } from "./ir.js";
|
|
8
8
|
/** Canvas-style 2D affine matrix [a, b, c, d, e, f]. */
|
|
9
9
|
export type Mat2D = [number, number, number, number, number, number];
|
|
10
10
|
/** A clip from an ancestor group: its shape in the group's coordinate space,
|
|
@@ -24,6 +24,12 @@ interface OpBase {
|
|
|
24
24
|
opacity: number;
|
|
25
25
|
/** Clip regions from ancestor groups (intersected by the renderer). */
|
|
26
26
|
clips?: ClipRegion[];
|
|
27
|
+
/** Paint effects (screen-pixel space). Present only when authored. */
|
|
28
|
+
blur?: number;
|
|
29
|
+
shadowColor?: string;
|
|
30
|
+
shadowBlur?: number;
|
|
31
|
+
shadowX?: number;
|
|
32
|
+
shadowY?: number;
|
|
27
33
|
}
|
|
28
34
|
export type DisplayOp = (OpBase & {
|
|
29
35
|
type: "rect";
|
|
@@ -31,8 +37,8 @@ export type DisplayOp = (OpBase & {
|
|
|
31
37
|
height: number;
|
|
32
38
|
offsetX: number;
|
|
33
39
|
offsetY: number;
|
|
34
|
-
fill?:
|
|
35
|
-
stroke?:
|
|
40
|
+
fill?: Paint;
|
|
41
|
+
stroke?: Paint;
|
|
36
42
|
strokeWidth?: number;
|
|
37
43
|
radius?: number;
|
|
38
44
|
}) | (OpBase & {
|
|
@@ -41,8 +47,8 @@ export type DisplayOp = (OpBase & {
|
|
|
41
47
|
height: number;
|
|
42
48
|
offsetX: number;
|
|
43
49
|
offsetY: number;
|
|
44
|
-
fill?:
|
|
45
|
-
stroke?:
|
|
50
|
+
fill?: Paint;
|
|
51
|
+
stroke?: Paint;
|
|
46
52
|
strokeWidth?: number;
|
|
47
53
|
}) | (OpBase & {
|
|
48
54
|
type: "line";
|
|
@@ -76,9 +82,11 @@ export type DisplayOp = (OpBase & {
|
|
|
76
82
|
d: string;
|
|
77
83
|
/** 0..1 fraction of the stroke outline drawn (draw-on). */
|
|
78
84
|
progress: number;
|
|
79
|
-
fill?:
|
|
80
|
-
stroke?:
|
|
85
|
+
fill?: Paint;
|
|
86
|
+
stroke?: Paint;
|
|
81
87
|
strokeWidth?: number;
|
|
88
|
+
/** Local-space bbox [x,y,w,h] for mapping a gradient paint (set only when one is used). */
|
|
89
|
+
bbox?: [number, number, number, number];
|
|
82
90
|
});
|
|
83
91
|
export type DisplayList = DisplayOp[];
|
|
84
92
|
/**
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gradient paints — the structured alternative to a solid color `fill`/`stroke`.
|
|
3
|
+
*
|
|
4
|
+
* Coordinates are normalized to the node's bounding box (0..1), so a gradient is
|
|
5
|
+
* just an angle + color stops, independent of the node's size. The renderer maps
|
|
6
|
+
* 0..1 → the node's local box and the paint is applied in node-local space, so
|
|
7
|
+
* rotating/scaling the NODE moves the gradient with it (the "animated gradient"
|
|
8
|
+
* idiom — gradients themselves are static this version). Pure + deterministic.
|
|
9
|
+
*/
|
|
10
|
+
import type { ColorStop, Gradient, Paint } from "./ir.js";
|
|
11
|
+
/** True when a paint is a gradient object (vs a plain color string). */
|
|
12
|
+
export declare function isGradient(p: Paint | undefined): p is Gradient;
|
|
13
|
+
/** A linear gradient. `angle` in degrees: 0 = left→right, 90 = top→bottom. */
|
|
14
|
+
export declare function linearGradient(stops: (string | ColorStop)[], opts?: {
|
|
15
|
+
angle?: number;
|
|
16
|
+
}): Gradient;
|
|
17
|
+
/** A radial gradient. `cx/cy` centre (0..1 of the box, default 0.5), `r` radius (0..1, default 0.5). */
|
|
18
|
+
export declare function radialGradient(stops: (string | ColorStop)[], opts?: {
|
|
19
|
+
cx?: number;
|
|
20
|
+
cy?: number;
|
|
21
|
+
r?: number;
|
|
22
|
+
}): Gradient;
|
|
23
|
+
/** A conic (angular sweep) gradient. `angle` start in degrees, `cx/cy` centre (0..1). */
|
|
24
|
+
export declare function conicGradient(stops: (string | ColorStop)[], opts?: {
|
|
25
|
+
angle?: number;
|
|
26
|
+
cx?: number;
|
|
27
|
+
cy?: number;
|
|
28
|
+
}): Gradient;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export { composeScene, formatComposeReport, type OverlayDoc, type ComposeReport,
|
|
|
6
6
|
export { compileScene, type CompiledScene, type PropertySegment, type LabelSpan, type MotionDriver } from "./compile.js";
|
|
7
7
|
export { pathPoint, pathTangentAngle, type Pt } from "./path.js";
|
|
8
8
|
export { cameraTo, cameraMatrix, CAMERA_ID, CAMERA_PROPS } from "./camera.js";
|
|
9
|
+
export { linearGradient, radialGradient, conicGradient, isGradient } from "./gradient.js";
|
|
10
|
+
export { glow, dropShadow } from "./effects.js";
|
|
9
11
|
export { motionPreset, PRESET_NAMES, type PresetName, type PresetRig, type PresetOpts } from "./presets.js";
|
|
10
12
|
export { devicePreset, deviceScreen, deviceScreenCenter, deviceBounds, deviceScreenPoint, DEVICE_PRESET_NAMES, type DevicePresetName, type DevicePresetOpts } from "./devicePreset.js";
|
|
11
13
|
export { cursor, cursorTo, cursorPath, cursorClick, cursorDouble, type CursorStyle, type CursorOpts, type CursorToOpts, type CursorPathOpts, type CursorClickOpts } from "./cursor.js";
|
package/dist/types/ir.d.ts
CHANGED
|
@@ -36,20 +36,60 @@ export interface BaseProps {
|
|
|
36
36
|
* for HUD / titles / watermark layers. No-op when the scene has no camera.
|
|
37
37
|
*/
|
|
38
38
|
fixed?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Paint effects (animatable scalars, in screen pixels — not transformed by the
|
|
41
|
+
* node's rotation/scale or the camera, so a shadow keeps a consistent light
|
|
42
|
+
* direction). `shadowColor` enables a drop shadow / outer glow (`glow`/`dropShadow`
|
|
43
|
+
* helpers). No-op on a `group` (use a child; group/composite blur is a later add).
|
|
44
|
+
*/
|
|
45
|
+
blur?: number;
|
|
46
|
+
shadowColor?: string;
|
|
47
|
+
shadowBlur?: number;
|
|
48
|
+
shadowX?: number;
|
|
49
|
+
shadowY?: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A paint is a solid color string OR a gradient. Coordinates are normalized to the
|
|
53
|
+
* node's bounding box (0..1, SVG `objectBoundingBox` style) so a gradient is just an
|
|
54
|
+
* angle + stops, size-independent. Applied in node-local space, so animating the
|
|
55
|
+
* node's transform (rotation/scale) moves the gradient with it. Build with
|
|
56
|
+
* `linearGradient`/`radialGradient`/`conicGradient` (`gradient.ts`).
|
|
57
|
+
*/
|
|
58
|
+
export interface ColorStop {
|
|
59
|
+
offset: number;
|
|
60
|
+
color: string;
|
|
39
61
|
}
|
|
62
|
+
export type Gradient = {
|
|
63
|
+
kind: "linear";
|
|
64
|
+
angle?: number;
|
|
65
|
+
stops: ColorStop[];
|
|
66
|
+
} | {
|
|
67
|
+
kind: "radial";
|
|
68
|
+
cx?: number;
|
|
69
|
+
cy?: number;
|
|
70
|
+
r?: number;
|
|
71
|
+
stops: ColorStop[];
|
|
72
|
+
} | {
|
|
73
|
+
kind: "conic";
|
|
74
|
+
angle?: number;
|
|
75
|
+
cx?: number;
|
|
76
|
+
cy?: number;
|
|
77
|
+
stops: ColorStop[];
|
|
78
|
+
};
|
|
79
|
+
export type Paint = string | Gradient;
|
|
40
80
|
export interface RectProps extends BaseProps {
|
|
41
81
|
width: number;
|
|
42
82
|
height: number;
|
|
43
|
-
fill?:
|
|
44
|
-
stroke?:
|
|
83
|
+
fill?: Paint;
|
|
84
|
+
stroke?: Paint;
|
|
45
85
|
strokeWidth?: number;
|
|
46
86
|
radius?: number;
|
|
47
87
|
}
|
|
48
88
|
export interface EllipseProps extends BaseProps {
|
|
49
89
|
width: number;
|
|
50
90
|
height: number;
|
|
51
|
-
fill?:
|
|
52
|
-
stroke?:
|
|
91
|
+
fill?: Paint;
|
|
92
|
+
stroke?: Paint;
|
|
53
93
|
strokeWidth?: number;
|
|
54
94
|
}
|
|
55
95
|
export interface LineProps {
|
|
@@ -64,6 +104,12 @@ export interface LineProps {
|
|
|
64
104
|
progress?: number;
|
|
65
105
|
/** Pin to the screen so the scene `camera` does not move it (top-level only). */
|
|
66
106
|
fixed?: boolean;
|
|
107
|
+
/** Paint effects (px, screen-space) — see BaseProps. */
|
|
108
|
+
blur?: number;
|
|
109
|
+
shadowColor?: string;
|
|
110
|
+
shadowBlur?: number;
|
|
111
|
+
shadowX?: number;
|
|
112
|
+
shadowY?: number;
|
|
67
113
|
}
|
|
68
114
|
export interface TextProps extends BaseProps {
|
|
69
115
|
/** Numbers interpolate (count-up) and render via toFixed(contentDecimals). */
|
|
@@ -104,8 +150,8 @@ export interface GroupProps extends BaseProps {
|
|
|
104
150
|
export interface PathProps extends BaseProps {
|
|
105
151
|
/** SVG path data (the `d` attribute). Drawn as a true vector — crisp at any zoom. */
|
|
106
152
|
d: string;
|
|
107
|
-
fill?:
|
|
108
|
-
stroke?:
|
|
153
|
+
fill?: Paint;
|
|
154
|
+
stroke?: Paint;
|
|
109
155
|
strokeWidth?: number;
|
|
110
156
|
/**
|
|
111
157
|
* 0..1 — fraction of the OUTLINE drawn, for a self-drawing "draw-on" effect
|
package/dist/types/path.d.ts
CHANGED
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
* spacing ever overshoots.
|
|
10
10
|
*/
|
|
11
11
|
export type Pt = [number, number];
|
|
12
|
+
/**
|
|
13
|
+
* A loose bounding box `[x, y, w, h]` from a path `d`'s coordinate extents — used
|
|
14
|
+
* only to map a gradient across the shape. Exact for M/L/C/Q/S/T paths (every
|
|
15
|
+
* number is an x/y coord, control points included); loose for the rare H/V/A.
|
|
16
|
+
*/
|
|
17
|
+
export declare function pathBBox(d: string): [number, number, number, number];
|
|
12
18
|
/**
|
|
13
19
|
* Position on the spline at progress u in [0,1]. `curviness` scales the
|
|
14
20
|
* Catmull-Rom tangents (GSAP's idea): 1 = standard smooth (the default and the
|
package/guides/edsl-guide.md
CHANGED
|
@@ -150,6 +150,50 @@ scene({
|
|
|
150
150
|
|
|
151
151
|
See `examples/scenes/camera-demo.ts`.
|
|
152
152
|
|
|
153
|
+
## Gradients (fill / stroke)
|
|
154
|
+
|
|
155
|
+
`fill` and `stroke` on **rect / ellipse / path** accept a gradient as well as a
|
|
156
|
+
color string. Coordinates are normalized to the node's bounding box (0..1), so a
|
|
157
|
+
gradient is just an angle + stops:
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
rect({ id: "card", x, y, width: 300, height: 300, anchor: "center",
|
|
161
|
+
fill: linearGradient(["#FF5C3A", "#FFC24B"], { angle: 60 }) }) // 0=L→R, 90=T→B
|
|
162
|
+
ellipse({ id: "orb", /* … */ fill: radialGradient(["#9B7CFF", "#221A4A"], { cx: 0.4, cy: 0.4, r: 0.6 }) })
|
|
163
|
+
path({ id: "star", d, fill: conicGradient(["#00C2A8", "#3AA0FF", "#7C5CFF", "#00C2A8"], { angle: -90 }) })
|
|
164
|
+
ellipse({ id: "ring", /* … */ fill: "none", stroke: linearGradient(["#3AA0FF", "#46E5A0"]), strokeWidth: 10 })
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
- `linearGradient(stops, { angle })`, `radialGradient(stops, { cx, cy, r })`,
|
|
168
|
+
`conicGradient(stops, { angle, cx, cy })`. `stops` is a color array (even offsets)
|
|
169
|
+
or `[{ offset, color }]`. `cx/cy/r` are 0..1 of the box (centre defaults to 0.5).
|
|
170
|
+
- **Gradients are static** (not keyframed). The gradient lives in the node's local
|
|
171
|
+
space, so **animate the NODE** (`tween(id, { rotation: 360 })`, scale, move) and the
|
|
172
|
+
gradient sweeps/stretches with it. Color-string fills still tween as today.
|
|
173
|
+
- text fill and line stroke are color-only for now. See `examples/scenes/gradient-demo.ts`.
|
|
174
|
+
|
|
175
|
+
## Shadow, glow & blur
|
|
176
|
+
|
|
177
|
+
Drawable nodes (rect / ellipse / path / text / image / line) take animatable paint
|
|
178
|
+
effects, in **screen pixels** (not transformed by the node or camera, so a shadow
|
|
179
|
+
keeps a consistent light direction):
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
rect({ id: "card", /* … */, ...dropShadow("#000000", 64, 0, 34) }) // drop shadow
|
|
183
|
+
ellipse({ id: "orb", /* … */, fill: radialGradient([...]), shadowColor: "#FFC24B", shadowBlur: 22 })
|
|
184
|
+
oscillate("orb", "shadowBlur", { amplitude: 16, frequency: 0.9 }) // PULSING glow
|
|
185
|
+
rect({ id: "card", /* … */, blur: 18 }); tween("card", { blur: 0 }, { duration: 1 }) // focus pull
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
- Props: `blur` (gaussian blur of the shape), `shadowColor` (turns the shadow/glow
|
|
189
|
+
on), `shadowBlur`, `shadowX`, `shadowY`. All **animatable** — `tween`/`oscillate`
|
|
190
|
+
them for pulsing glows, focus pulls, etc. (set a base value first so there's
|
|
191
|
+
something to animate from).
|
|
192
|
+
- Sugar: `glow(color, blur)` (offset 0) and `dropShadow(color, blur, x, y)` return
|
|
193
|
+
a partial you spread into props (`...glow("#FFD24B", 28)`); still animatable.
|
|
194
|
+
- No-op on a `group` (apply to a child; group/composite blur is a later add). See
|
|
195
|
+
`examples/scenes/shadow-demo.ts`.
|
|
196
|
+
|
|
153
197
|
## Character rig (skeleton, poses, IK)
|
|
154
198
|
|
|
155
199
|
A first-class, declarative character rig that **compiles to plain IR** (nested
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reframe-video",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Declarative motion graphics that AI can write and humans can tweak — human edits survive AI regeneration. Deterministic mp4 renders from a plain-data scene format.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"motion-graphics",
|