reframe-video 0.6.1 → 0.6.3
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 +28 -3
- package/dist/browserEntry.js +32 -2
- package/dist/cli.js +19 -2
- package/dist/index.js +49 -4
- package/dist/labels.js +19 -2
- package/dist/renderer-canvas.js +11 -0
- package/dist/trace-cli.js +3 -2
- package/dist/types/effects.d.ts +12 -0
- package/dist/types/evaluate.d.ts +9 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/ir.d.ts +22 -0
- package/guides/edsl-guide.md +39 -0
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -377,6 +377,9 @@ function validateScene(ir) {
|
|
|
377
377
|
const props = node.props;
|
|
378
378
|
checkPaint(`node "${node.id}" fill`, props.fill);
|
|
379
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`);
|
|
382
|
+
if (typeof props.blend === "string" && !BLEND_MODES.has(props.blend)) problems.push(`node "${node.id}": unknown blend "${props.blend}" \u2014 use ${[...BLEND_MODES].join(", ")}`);
|
|
380
383
|
if (node.type === "group") {
|
|
381
384
|
const clip = node.props.clip;
|
|
382
385
|
if (clip) {
|
|
@@ -591,16 +594,30 @@ function validateComposition(comp) {
|
|
|
591
594
|
}
|
|
592
595
|
if (problems.length > 0) throw new SceneValidationError(problems);
|
|
593
596
|
}
|
|
594
|
-
var COMMON_PROPS, CAMERA_PROPS, PROPS_BY_TYPE, SceneValidationError, TRANSITIONS;
|
|
597
|
+
var FX_PROPS, BLEND_MODES, COMMON_PROPS, CAMERA_PROPS, PROPS_BY_TYPE, SceneValidationError, TRANSITIONS;
|
|
595
598
|
var init_validate = __esm({
|
|
596
599
|
"../core/src/validate.ts"() {
|
|
597
600
|
"use strict";
|
|
598
|
-
|
|
601
|
+
FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY", "blend"];
|
|
602
|
+
BLEND_MODES = /* @__PURE__ */ new Set([
|
|
603
|
+
"normal",
|
|
604
|
+
"multiply",
|
|
605
|
+
"screen",
|
|
606
|
+
"overlay",
|
|
607
|
+
"lighten",
|
|
608
|
+
"darken",
|
|
609
|
+
"add",
|
|
610
|
+
"color-dodge",
|
|
611
|
+
"soft-light",
|
|
612
|
+
"hard-light",
|
|
613
|
+
"difference"
|
|
614
|
+
]);
|
|
615
|
+
COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed", ...FX_PROPS];
|
|
599
616
|
CAMERA_PROPS = ["x", "y", "zoom", "rotation"];
|
|
600
617
|
PROPS_BY_TYPE = {
|
|
601
618
|
rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
|
|
602
619
|
ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
|
|
603
|
-
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress"],
|
|
620
|
+
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
|
|
604
621
|
text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
605
622
|
image: [...COMMON_PROPS, "src", "width", "height"],
|
|
606
623
|
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
@@ -928,6 +945,13 @@ var init_gradient = __esm({
|
|
|
928
945
|
}
|
|
929
946
|
});
|
|
930
947
|
|
|
948
|
+
// ../core/src/effects.ts
|
|
949
|
+
var init_effects = __esm({
|
|
950
|
+
"../core/src/effects.ts"() {
|
|
951
|
+
"use strict";
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
|
|
931
955
|
// ../core/src/presets.ts
|
|
932
956
|
function makeRng(seed) {
|
|
933
957
|
let a = seed >>> 0 || 2654435769;
|
|
@@ -1385,6 +1409,7 @@ var init_src = __esm({
|
|
|
1385
1409
|
init_path();
|
|
1386
1410
|
init_camera();
|
|
1387
1411
|
init_gradient();
|
|
1412
|
+
init_effects();
|
|
1388
1413
|
init_presets();
|
|
1389
1414
|
init_devicePreset();
|
|
1390
1415
|
init_cursor();
|
package/dist/browserEntry.js
CHANGED
|
@@ -334,11 +334,12 @@
|
|
|
334
334
|
}
|
|
335
335
|
|
|
336
336
|
// ../core/src/validate.ts
|
|
337
|
-
var
|
|
337
|
+
var FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY", "blend"];
|
|
338
|
+
var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed", ...FX_PROPS];
|
|
338
339
|
var PROPS_BY_TYPE = {
|
|
339
340
|
rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
|
|
340
341
|
ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
|
|
341
|
-
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress"],
|
|
342
|
+
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
|
|
342
343
|
text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
343
344
|
image: [...COMMON_PROPS, "src", "width", "height"],
|
|
344
345
|
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
@@ -679,9 +680,22 @@
|
|
|
679
680
|
const v = valueAt(target, prop, base ?? "");
|
|
680
681
|
return v === "" && base === void 0 ? void 0 : String(v);
|
|
681
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
|
+
if (p.blend !== void 0 && p.blend !== "normal") fx.blend = p.blend;
|
|
693
|
+
return fx;
|
|
694
|
+
};
|
|
682
695
|
const walk = (node, parent, parentOpacity, clips) => {
|
|
683
696
|
const id = node.id;
|
|
684
697
|
const clipSpread = clips.length > 0 ? { clips } : void 0;
|
|
698
|
+
const fx = effectFx(id, node.props);
|
|
685
699
|
if (node.type === "line") {
|
|
686
700
|
const opacity2 = parentOpacity * num(id, "opacity", node.props.opacity ?? 1);
|
|
687
701
|
if (opacity2 <= 0) return;
|
|
@@ -699,6 +713,7 @@
|
|
|
699
713
|
y2: y1 + (num(id, "y2", node.props.y2) - y1) * progress,
|
|
700
714
|
stroke: str(id, "stroke", node.props.stroke),
|
|
701
715
|
strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1),
|
|
716
|
+
...fx,
|
|
702
717
|
...clipSpread
|
|
703
718
|
});
|
|
704
719
|
return;
|
|
@@ -746,6 +761,7 @@
|
|
|
746
761
|
...fill !== void 0 && { fill },
|
|
747
762
|
...stroke !== void 0 && { stroke, strokeWidth },
|
|
748
763
|
...node.type === "rect" && { radius: num(id, "radius", node.props.radius ?? 0) },
|
|
764
|
+
...fx,
|
|
749
765
|
...clipSpread
|
|
750
766
|
});
|
|
751
767
|
return;
|
|
@@ -764,6 +780,7 @@
|
|
|
764
780
|
height,
|
|
765
781
|
offsetX: -width * ax,
|
|
766
782
|
offsetY: -height * ay,
|
|
783
|
+
...fx,
|
|
767
784
|
...clipSpread
|
|
768
785
|
});
|
|
769
786
|
return;
|
|
@@ -787,6 +804,7 @@
|
|
|
787
804
|
...fill !== void 0 && { fill },
|
|
788
805
|
...stroke !== void 0 && { stroke, strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1) },
|
|
789
806
|
...needsBox && { bbox: pathBBox(dStr) },
|
|
807
|
+
...fx,
|
|
790
808
|
...clipSpread
|
|
791
809
|
});
|
|
792
810
|
return;
|
|
@@ -811,6 +829,7 @@
|
|
|
811
829
|
letterSpacing: num(id, "letterSpacing", node.props.letterSpacing ?? 0),
|
|
812
830
|
align: TEXT_ALIGN[ax] ?? "left",
|
|
813
831
|
baseline: TEXT_BASELINE[ay] ?? "top",
|
|
832
|
+
...fx,
|
|
814
833
|
...clipSpread
|
|
815
834
|
});
|
|
816
835
|
return;
|
|
@@ -889,6 +908,14 @@
|
|
|
889
908
|
}
|
|
890
909
|
ctx2.setTransform(...op.transform);
|
|
891
910
|
ctx2.globalAlpha = Math.max(0, Math.min(1, op.opacity));
|
|
911
|
+
if (op.blur) ctx2.filter = `blur(${op.blur}px)`;
|
|
912
|
+
if (op.shadowColor) {
|
|
913
|
+
ctx2.shadowColor = op.shadowColor;
|
|
914
|
+
ctx2.shadowBlur = op.shadowBlur ?? 0;
|
|
915
|
+
ctx2.shadowOffsetX = op.shadowX ?? 0;
|
|
916
|
+
ctx2.shadowOffsetY = op.shadowY ?? 0;
|
|
917
|
+
}
|
|
918
|
+
if (op.blend) ctx2.globalCompositeOperation = mapBlend(op.blend);
|
|
892
919
|
switch (op.type) {
|
|
893
920
|
case "rect": {
|
|
894
921
|
const box = { x: op.offsetX, y: op.offsetY, w: op.width, h: op.height };
|
|
@@ -999,6 +1026,9 @@
|
|
|
999
1026
|
ctx2.restore();
|
|
1000
1027
|
}
|
|
1001
1028
|
}
|
|
1029
|
+
function mapBlend(blend) {
|
|
1030
|
+
return blend === "add" ? "lighter" : blend;
|
|
1031
|
+
}
|
|
1002
1032
|
function quoteFamily(family) {
|
|
1003
1033
|
return family.includes(" ") && !family.includes('"') ? `"${family}"` : family;
|
|
1004
1034
|
}
|
package/dist/cli.js
CHANGED
|
@@ -324,12 +324,26 @@ function compileScene(ir) {
|
|
|
324
324
|
}
|
|
325
325
|
|
|
326
326
|
// ../core/src/validate.ts
|
|
327
|
-
var
|
|
327
|
+
var FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY", "blend"];
|
|
328
|
+
var BLEND_MODES = /* @__PURE__ */ new Set([
|
|
329
|
+
"normal",
|
|
330
|
+
"multiply",
|
|
331
|
+
"screen",
|
|
332
|
+
"overlay",
|
|
333
|
+
"lighten",
|
|
334
|
+
"darken",
|
|
335
|
+
"add",
|
|
336
|
+
"color-dodge",
|
|
337
|
+
"soft-light",
|
|
338
|
+
"hard-light",
|
|
339
|
+
"difference"
|
|
340
|
+
]);
|
|
341
|
+
var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed", ...FX_PROPS];
|
|
328
342
|
var CAMERA_PROPS = ["x", "y", "zoom", "rotation"];
|
|
329
343
|
var PROPS_BY_TYPE = {
|
|
330
344
|
rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
|
|
331
345
|
ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
|
|
332
|
-
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress"],
|
|
346
|
+
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
|
|
333
347
|
text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
334
348
|
image: [...COMMON_PROPS, "src", "width", "height"],
|
|
335
349
|
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
@@ -375,6 +389,9 @@ function validateScene(ir) {
|
|
|
375
389
|
const props = node.props;
|
|
376
390
|
checkPaint(`node "${node.id}" fill`, props.fill);
|
|
377
391
|
checkPaint(`node "${node.id}" stroke`, props.stroke);
|
|
392
|
+
if (typeof props.blur === "number" && props.blur < 0) problems.push(`node "${node.id}": blur must be >= 0`);
|
|
393
|
+
if (typeof props.shadowBlur === "number" && props.shadowBlur < 0) problems.push(`node "${node.id}": shadowBlur must be >= 0`);
|
|
394
|
+
if (typeof props.blend === "string" && !BLEND_MODES.has(props.blend)) problems.push(`node "${node.id}": unknown blend "${props.blend}" \u2014 use ${[...BLEND_MODES].join(", ")}`);
|
|
378
395
|
if (node.type === "group") {
|
|
379
396
|
const clip = node.props.clip;
|
|
380
397
|
if (clip) {
|
package/dist/index.js
CHANGED
|
@@ -334,12 +334,26 @@ function compileScene(ir) {
|
|
|
334
334
|
}
|
|
335
335
|
|
|
336
336
|
// ../core/src/validate.ts
|
|
337
|
-
var
|
|
337
|
+
var FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY", "blend"];
|
|
338
|
+
var BLEND_MODES = /* @__PURE__ */ new Set([
|
|
339
|
+
"normal",
|
|
340
|
+
"multiply",
|
|
341
|
+
"screen",
|
|
342
|
+
"overlay",
|
|
343
|
+
"lighten",
|
|
344
|
+
"darken",
|
|
345
|
+
"add",
|
|
346
|
+
"color-dodge",
|
|
347
|
+
"soft-light",
|
|
348
|
+
"hard-light",
|
|
349
|
+
"difference"
|
|
350
|
+
]);
|
|
351
|
+
var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed", ...FX_PROPS];
|
|
338
352
|
var CAMERA_PROPS = ["x", "y", "zoom", "rotation"];
|
|
339
353
|
var PROPS_BY_TYPE = {
|
|
340
354
|
rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
|
|
341
355
|
ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
|
|
342
|
-
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress"],
|
|
356
|
+
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
|
|
343
357
|
text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
344
358
|
image: [...COMMON_PROPS, "src", "width", "height"],
|
|
345
359
|
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
@@ -385,6 +399,9 @@ function validateScene(ir) {
|
|
|
385
399
|
const props = node.props;
|
|
386
400
|
checkPaint(`node "${node.id}" fill`, props.fill);
|
|
387
401
|
checkPaint(`node "${node.id}" stroke`, props.stroke);
|
|
402
|
+
if (typeof props.blur === "number" && props.blur < 0) problems.push(`node "${node.id}": blur must be >= 0`);
|
|
403
|
+
if (typeof props.shadowBlur === "number" && props.shadowBlur < 0) problems.push(`node "${node.id}": shadowBlur must be >= 0`);
|
|
404
|
+
if (typeof props.blend === "string" && !BLEND_MODES.has(props.blend)) problems.push(`node "${node.id}": unknown blend "${props.blend}" \u2014 use ${[...BLEND_MODES].join(", ")}`);
|
|
388
405
|
if (node.type === "group") {
|
|
389
406
|
const clip = node.props.clip;
|
|
390
407
|
if (clip) {
|
|
@@ -987,6 +1004,14 @@ function conicGradient(stops, opts = {}) {
|
|
|
987
1004
|
};
|
|
988
1005
|
}
|
|
989
1006
|
|
|
1007
|
+
// ../core/src/effects.ts
|
|
1008
|
+
function glow(color, blur = 24) {
|
|
1009
|
+
return { shadowColor: color, shadowBlur: blur, shadowX: 0, shadowY: 0 };
|
|
1010
|
+
}
|
|
1011
|
+
function dropShadow(color, blur = 24, x = 0, y = 12) {
|
|
1012
|
+
return { shadowColor: color, shadowBlur: blur, shadowX: x, shadowY: y };
|
|
1013
|
+
}
|
|
1014
|
+
|
|
990
1015
|
// ../core/src/presets.ts
|
|
991
1016
|
var PRESET_NAMES = [
|
|
992
1017
|
"draw-bloom",
|
|
@@ -1526,11 +1551,11 @@ function ikReach(upper, lower, dx, dy, flip = false) {
|
|
|
1526
1551
|
function humanoid(opts = {}) {
|
|
1527
1552
|
const line2 = opts.color ?? DEFAULT_LINE;
|
|
1528
1553
|
const fill = opts.fill ?? DEFAULT_FILL;
|
|
1529
|
-
const
|
|
1554
|
+
const glow2 = opts.glow;
|
|
1530
1555
|
const blob = (jid, a, b, cy) => {
|
|
1531
1556
|
const d = ovalPath(a, b, 0, cy);
|
|
1532
1557
|
const nodes = [];
|
|
1533
|
-
if (
|
|
1558
|
+
if (glow2) nodes.push(path({ id: `${jid}-glow`, d, x: 0, y: 0, fill: "none", stroke: glow2, strokeWidth: GLOW_W, opacity: 0.18 }));
|
|
1534
1559
|
nodes.push(path({ id: `${jid}-shape`, d, x: 0, y: 0, fill, stroke: line2, strokeWidth: LINE_W }));
|
|
1535
1560
|
return nodes;
|
|
1536
1561
|
};
|
|
@@ -2938,9 +2963,22 @@ function evaluate(compiled, t) {
|
|
|
2938
2963
|
const v = valueAt(target, prop, base ?? "");
|
|
2939
2964
|
return v === "" && base === void 0 ? void 0 : String(v);
|
|
2940
2965
|
};
|
|
2966
|
+
const effectFx = (id, p) => {
|
|
2967
|
+
const fx = {};
|
|
2968
|
+
if (p.blur !== void 0) fx.blur = num(id, "blur", p.blur);
|
|
2969
|
+
if (p.shadowColor !== void 0) {
|
|
2970
|
+
fx.shadowColor = str(id, "shadowColor", p.shadowColor);
|
|
2971
|
+
fx.shadowBlur = num(id, "shadowBlur", p.shadowBlur ?? 0);
|
|
2972
|
+
fx.shadowX = num(id, "shadowX", p.shadowX ?? 0);
|
|
2973
|
+
fx.shadowY = num(id, "shadowY", p.shadowY ?? 0);
|
|
2974
|
+
}
|
|
2975
|
+
if (p.blend !== void 0 && p.blend !== "normal") fx.blend = p.blend;
|
|
2976
|
+
return fx;
|
|
2977
|
+
};
|
|
2941
2978
|
const walk = (node, parent, parentOpacity, clips) => {
|
|
2942
2979
|
const id = node.id;
|
|
2943
2980
|
const clipSpread = clips.length > 0 ? { clips } : void 0;
|
|
2981
|
+
const fx = effectFx(id, node.props);
|
|
2944
2982
|
if (node.type === "line") {
|
|
2945
2983
|
const opacity2 = parentOpacity * num(id, "opacity", node.props.opacity ?? 1);
|
|
2946
2984
|
if (opacity2 <= 0) return;
|
|
@@ -2958,6 +2996,7 @@ function evaluate(compiled, t) {
|
|
|
2958
2996
|
y2: y1 + (num(id, "y2", node.props.y2) - y1) * progress,
|
|
2959
2997
|
stroke: str(id, "stroke", node.props.stroke),
|
|
2960
2998
|
strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1),
|
|
2999
|
+
...fx,
|
|
2961
3000
|
...clipSpread
|
|
2962
3001
|
});
|
|
2963
3002
|
return;
|
|
@@ -3005,6 +3044,7 @@ function evaluate(compiled, t) {
|
|
|
3005
3044
|
...fill !== void 0 && { fill },
|
|
3006
3045
|
...stroke !== void 0 && { stroke, strokeWidth },
|
|
3007
3046
|
...node.type === "rect" && { radius: num(id, "radius", node.props.radius ?? 0) },
|
|
3047
|
+
...fx,
|
|
3008
3048
|
...clipSpread
|
|
3009
3049
|
});
|
|
3010
3050
|
return;
|
|
@@ -3023,6 +3063,7 @@ function evaluate(compiled, t) {
|
|
|
3023
3063
|
height,
|
|
3024
3064
|
offsetX: -width * ax,
|
|
3025
3065
|
offsetY: -height * ay,
|
|
3066
|
+
...fx,
|
|
3026
3067
|
...clipSpread
|
|
3027
3068
|
});
|
|
3028
3069
|
return;
|
|
@@ -3046,6 +3087,7 @@ function evaluate(compiled, t) {
|
|
|
3046
3087
|
...fill !== void 0 && { fill },
|
|
3047
3088
|
...stroke !== void 0 && { stroke, strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1) },
|
|
3048
3089
|
...needsBox && { bbox: pathBBox(dStr) },
|
|
3090
|
+
...fx,
|
|
3049
3091
|
...clipSpread
|
|
3050
3092
|
});
|
|
3051
3093
|
return;
|
|
@@ -3070,6 +3112,7 @@ function evaluate(compiled, t) {
|
|
|
3070
3112
|
letterSpacing: num(id, "letterSpacing", node.props.letterSpacing ?? 0),
|
|
3071
3113
|
align: TEXT_ALIGN[ax] ?? "left",
|
|
3072
3114
|
baseline: TEXT_BASELINE[ay] ?? "top",
|
|
3115
|
+
...fx,
|
|
3073
3116
|
...clipSpread
|
|
3074
3117
|
});
|
|
3075
3118
|
return;
|
|
@@ -3204,10 +3247,12 @@ export {
|
|
|
3204
3247
|
deviceScreen,
|
|
3205
3248
|
deviceScreenCenter,
|
|
3206
3249
|
deviceScreenPoint,
|
|
3250
|
+
dropShadow,
|
|
3207
3251
|
ellipse,
|
|
3208
3252
|
evaluate,
|
|
3209
3253
|
figure,
|
|
3210
3254
|
formatComposeReport,
|
|
3255
|
+
glow,
|
|
3211
3256
|
group,
|
|
3212
3257
|
humanoid,
|
|
3213
3258
|
ikReach,
|
package/dist/labels.js
CHANGED
|
@@ -318,12 +318,26 @@ function compileScene(ir) {
|
|
|
318
318
|
}
|
|
319
319
|
|
|
320
320
|
// ../core/src/validate.ts
|
|
321
|
-
var
|
|
321
|
+
var FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY", "blend"];
|
|
322
|
+
var BLEND_MODES = /* @__PURE__ */ new Set([
|
|
323
|
+
"normal",
|
|
324
|
+
"multiply",
|
|
325
|
+
"screen",
|
|
326
|
+
"overlay",
|
|
327
|
+
"lighten",
|
|
328
|
+
"darken",
|
|
329
|
+
"add",
|
|
330
|
+
"color-dodge",
|
|
331
|
+
"soft-light",
|
|
332
|
+
"hard-light",
|
|
333
|
+
"difference"
|
|
334
|
+
]);
|
|
335
|
+
var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed", ...FX_PROPS];
|
|
322
336
|
var CAMERA_PROPS = ["x", "y", "zoom", "rotation"];
|
|
323
337
|
var PROPS_BY_TYPE = {
|
|
324
338
|
rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
|
|
325
339
|
ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
|
|
326
|
-
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress"],
|
|
340
|
+
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
|
|
327
341
|
text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
328
342
|
image: [...COMMON_PROPS, "src", "width", "height"],
|
|
329
343
|
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
@@ -369,6 +383,9 @@ function validateScene(ir) {
|
|
|
369
383
|
const props = node.props;
|
|
370
384
|
checkPaint(`node "${node.id}" fill`, props.fill);
|
|
371
385
|
checkPaint(`node "${node.id}" stroke`, props.stroke);
|
|
386
|
+
if (typeof props.blur === "number" && props.blur < 0) problems.push(`node "${node.id}": blur must be >= 0`);
|
|
387
|
+
if (typeof props.shadowBlur === "number" && props.shadowBlur < 0) problems.push(`node "${node.id}": shadowBlur must be >= 0`);
|
|
388
|
+
if (typeof props.blend === "string" && !BLEND_MODES.has(props.blend)) problems.push(`node "${node.id}": unknown blend "${props.blend}" \u2014 use ${[...BLEND_MODES].join(", ")}`);
|
|
372
389
|
if (node.type === "group") {
|
|
373
390
|
const clip = node.props.clip;
|
|
374
391
|
if (clip) {
|
package/dist/renderer-canvas.js
CHANGED
|
@@ -55,6 +55,14 @@ function drawDisplayList(ctx, ops, images) {
|
|
|
55
55
|
}
|
|
56
56
|
ctx.setTransform(...op.transform);
|
|
57
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
|
+
}
|
|
65
|
+
if (op.blend) ctx.globalCompositeOperation = mapBlend(op.blend);
|
|
58
66
|
switch (op.type) {
|
|
59
67
|
case "rect": {
|
|
60
68
|
const box = { x: op.offsetX, y: op.offsetY, w: op.width, h: op.height };
|
|
@@ -165,6 +173,9 @@ function drawDisplayList(ctx, ops, images) {
|
|
|
165
173
|
ctx.restore();
|
|
166
174
|
}
|
|
167
175
|
}
|
|
176
|
+
function mapBlend(blend) {
|
|
177
|
+
return blend === "add" ? "lighter" : blend;
|
|
178
|
+
}
|
|
168
179
|
function quoteFamily(family) {
|
|
169
180
|
return family.includes(" ") && !family.includes('"') ? `"${family}"` : family;
|
|
170
181
|
}
|
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", "blend"];
|
|
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, Paint, PropValue } from "./ir.js";
|
|
7
|
+
import type { BlendMode, 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,14 @@ 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;
|
|
33
|
+
/** Compositing mode (discrete; present only when authored and not "normal"). */
|
|
34
|
+
blend?: BlendMode;
|
|
27
35
|
}
|
|
28
36
|
export type DisplayOp = (OpBase & {
|
|
29
37
|
type: "rect";
|
package/dist/types/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export { compileScene, type CompiledScene, type PropertySegment, type LabelSpan,
|
|
|
7
7
|
export { pathPoint, pathTangentAngle, type Pt } from "./path.js";
|
|
8
8
|
export { cameraTo, cameraMatrix, CAMERA_ID, CAMERA_PROPS } from "./camera.js";
|
|
9
9
|
export { linearGradient, radialGradient, conicGradient, isGradient } from "./gradient.js";
|
|
10
|
+
export { glow, dropShadow } from "./effects.js";
|
|
10
11
|
export { motionPreset, PRESET_NAMES, type PresetName, type PresetRig, type PresetOpts } from "./presets.js";
|
|
11
12
|
export { devicePreset, deviceScreen, deviceScreenCenter, deviceBounds, deviceScreenPoint, DEVICE_PRESET_NAMES, type DevicePresetName, type DevicePresetOpts } from "./devicePreset.js";
|
|
12
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,7 +36,23 @@ 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
|
+
/** How this node composites with what's already drawn (default "normal"). `screen`/
|
|
51
|
+
* `add` brighten (additive light/glow), `multiply` tints/deepens. No-op on a group. */
|
|
52
|
+
blend?: BlendMode;
|
|
39
53
|
}
|
|
54
|
+
/** Compositing modes (Canvas `globalCompositeOperation`; `add` maps to `lighter`). */
|
|
55
|
+
export type BlendMode = "normal" | "multiply" | "screen" | "overlay" | "lighten" | "darken" | "add" | "color-dodge" | "soft-light" | "hard-light" | "difference";
|
|
40
56
|
/**
|
|
41
57
|
* A paint is a solid color string OR a gradient. Coordinates are normalized to the
|
|
42
58
|
* node's bounding box (0..1, SVG `objectBoundingBox` style) so a gradient is just an
|
|
@@ -93,6 +109,12 @@ export interface LineProps {
|
|
|
93
109
|
progress?: number;
|
|
94
110
|
/** Pin to the screen so the scene `camera` does not move it (top-level only). */
|
|
95
111
|
fixed?: boolean;
|
|
112
|
+
/** Paint effects (px, screen-space) — see BaseProps. */
|
|
113
|
+
blur?: number;
|
|
114
|
+
shadowColor?: string;
|
|
115
|
+
shadowBlur?: number;
|
|
116
|
+
shadowX?: number;
|
|
117
|
+
shadowY?: number;
|
|
96
118
|
}
|
|
97
119
|
export interface TextProps extends BaseProps {
|
|
98
120
|
/** Numbers interpolate (count-up) and render via toFixed(contentDecimals). */
|
package/guides/edsl-guide.md
CHANGED
|
@@ -172,6 +172,45 @@ ellipse({ id: "ring", /* … */ fill: "none", stroke: linearGradient(["#3AA0FF",
|
|
|
172
172
|
gradient sweeps/stretches with it. Color-string fills still tween as today.
|
|
173
173
|
- text fill and line stroke are color-only for now. See `examples/scenes/gradient-demo.ts`.
|
|
174
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
|
+
|
|
197
|
+
### Blend modes (compositing)
|
|
198
|
+
|
|
199
|
+
`blend` selects how a shape composites with what's already drawn beneath it — the
|
|
200
|
+
primitive that makes light read.
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
ellipse({ id: "glow", fill: radialGradient(["#FF2D6A", "#FF2D6A00"]), blend: "screen" }) // additive light: brightens where blobs overlap
|
|
204
|
+
rect({ id: "tint", fill: "#1E5BFF", blend: "multiply" }) // tint/deepen the layer beneath
|
|
205
|
+
rect({ id: "neon", fill: linearGradient([...]), shadowColor: "#7A4DFF", blend: "screen" }) // compose with glow
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
- Modes: `normal` (default), `multiply`, `screen`, `overlay`, `lighten`, `darken`,
|
|
209
|
+
`add` (additive light), `color-dodge`, `soft-light`, `hard-light`, `difference`.
|
|
210
|
+
- **Discrete**, not interpolated — set per node (a static string). Default `normal`.
|
|
211
|
+
- Per-shape. A whole-group blend (composite the subtree, then blend) is a later add;
|
|
212
|
+
on a `group` the prop is a no-op. See `examples/scenes/blend-demo.ts`.
|
|
213
|
+
|
|
175
214
|
## Character rig (skeleton, poses, IK)
|
|
176
215
|
|
|
177
216
|
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.3",
|
|
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",
|