reframe-video 0.3.0 → 0.4.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/bin.js +17 -0
- package/dist/index.js +522 -15
- package/dist/types/index.d.ts +1 -0
- package/dist/types/textFx.d.ts +91 -0
- package/dist/types/textMetrics.d.ts +3 -0
- package/guides/edsl-guide.md +32 -0
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -1070,6 +1070,22 @@ var init_figure = __esm({
|
|
|
1070
1070
|
}
|
|
1071
1071
|
});
|
|
1072
1072
|
|
|
1073
|
+
// ../core/src/textMetrics.ts
|
|
1074
|
+
var init_textMetrics = __esm({
|
|
1075
|
+
"../core/src/textMetrics.ts"() {
|
|
1076
|
+
"use strict";
|
|
1077
|
+
}
|
|
1078
|
+
});
|
|
1079
|
+
|
|
1080
|
+
// ../core/src/textFx.ts
|
|
1081
|
+
var init_textFx = __esm({
|
|
1082
|
+
"../core/src/textFx.ts"() {
|
|
1083
|
+
"use strict";
|
|
1084
|
+
init_dsl();
|
|
1085
|
+
init_textMetrics();
|
|
1086
|
+
}
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1073
1089
|
// ../core/src/motionOps.ts
|
|
1074
1090
|
var init_motionOps = __esm({
|
|
1075
1091
|
"../core/src/motionOps.ts"() {
|
|
@@ -1291,6 +1307,7 @@ var init_src = __esm({
|
|
|
1291
1307
|
init_rig();
|
|
1292
1308
|
init_characterPreset();
|
|
1293
1309
|
init_figure();
|
|
1310
|
+
init_textFx();
|
|
1294
1311
|
init_motionOps();
|
|
1295
1312
|
init_audio();
|
|
1296
1313
|
init_evaluate();
|
package/dist/index.js
CHANGED
|
@@ -884,14 +884,14 @@ function makeRng(seed) {
|
|
|
884
884
|
var clamp01 = (x) => Math.max(0, Math.min(1, x));
|
|
885
885
|
var SET = 1 / 120;
|
|
886
886
|
function ctx(o) {
|
|
887
|
-
const
|
|
887
|
+
const rand2 = makeRng((o.seed ?? 0) + 1);
|
|
888
888
|
return {
|
|
889
889
|
e: clamp01(o.energy ?? 0.5),
|
|
890
890
|
sp: Math.max(0.25, o.speed ?? 1),
|
|
891
891
|
it: clamp01(o.intensity ?? 0.5),
|
|
892
892
|
from: o.from,
|
|
893
|
-
rand,
|
|
894
|
-
jit: (amp) => (
|
|
893
|
+
rand: rand2,
|
|
894
|
+
jit: (amp) => (rand2() - 0.5) * 2 * amp,
|
|
895
895
|
g: o.target.group,
|
|
896
896
|
cx: o.target.center[0],
|
|
897
897
|
cy: o.target.center[1],
|
|
@@ -1378,7 +1378,7 @@ function makeRng2(seed) {
|
|
|
1378
1378
|
}
|
|
1379
1379
|
var dur2 = (base, sp) => base / sp;
|
|
1380
1380
|
function ctx2(o) {
|
|
1381
|
-
const
|
|
1381
|
+
const rand2 = makeRng2((o.seed ?? 0) + 1);
|
|
1382
1382
|
return {
|
|
1383
1383
|
g: o.target,
|
|
1384
1384
|
label: o.label,
|
|
@@ -1388,8 +1388,8 @@ function ctx2(o) {
|
|
|
1388
1388
|
facing: o.facing ?? 1,
|
|
1389
1389
|
at: o.at ?? [0, 0],
|
|
1390
1390
|
travel: o.travel,
|
|
1391
|
-
rand,
|
|
1392
|
-
jit: (amp) => (
|
|
1391
|
+
rand: rand2,
|
|
1392
|
+
jit: (amp) => (rand2() - 0.5) * 2 * amp
|
|
1393
1393
|
};
|
|
1394
1394
|
}
|
|
1395
1395
|
var round = (v) => Math.round(v * 1e3) / 1e3;
|
|
@@ -1688,9 +1688,511 @@ function figure(opts = {}) {
|
|
|
1688
1688
|
return rig(buildSkeleton(id, parts), rigOpts);
|
|
1689
1689
|
}
|
|
1690
1690
|
|
|
1691
|
+
// ../core/src/textMetrics.ts
|
|
1692
|
+
var INTER_ADVANCE = {
|
|
1693
|
+
"400": {
|
|
1694
|
+
"0": 63.09,
|
|
1695
|
+
"1": 40.67,
|
|
1696
|
+
"2": 60.99,
|
|
1697
|
+
"3": 61.77,
|
|
1698
|
+
"4": 64.6,
|
|
1699
|
+
"5": 59.33,
|
|
1700
|
+
"6": 62.01,
|
|
1701
|
+
"7": 56.59,
|
|
1702
|
+
"8": 61.87,
|
|
1703
|
+
"9": 62.01,
|
|
1704
|
+
" ": 28.13,
|
|
1705
|
+
"!": 28.76,
|
|
1706
|
+
'"': 46.58,
|
|
1707
|
+
"#": 63.33,
|
|
1708
|
+
"$": 64.16,
|
|
1709
|
+
"%": 98.19,
|
|
1710
|
+
"&": 64.4,
|
|
1711
|
+
"'": 29.98,
|
|
1712
|
+
"(": 36.47,
|
|
1713
|
+
")": 36.47,
|
|
1714
|
+
"*": 50.1,
|
|
1715
|
+
"+": 66.16,
|
|
1716
|
+
",": 28.81,
|
|
1717
|
+
"-": 46,
|
|
1718
|
+
".": 28.81,
|
|
1719
|
+
"/": 36.04,
|
|
1720
|
+
":": 28.81,
|
|
1721
|
+
";": 30.18,
|
|
1722
|
+
"<": 66.16,
|
|
1723
|
+
"=": 66.16,
|
|
1724
|
+
">": 66.16,
|
|
1725
|
+
"?": 51.12,
|
|
1726
|
+
"@": 96.58,
|
|
1727
|
+
"A": 68.99,
|
|
1728
|
+
"B": 65.43,
|
|
1729
|
+
"C": 73.05,
|
|
1730
|
+
"D": 72.17,
|
|
1731
|
+
"E": 60.11,
|
|
1732
|
+
"F": 59.03,
|
|
1733
|
+
"G": 74.61,
|
|
1734
|
+
"H": 74.32,
|
|
1735
|
+
"I": 26.86,
|
|
1736
|
+
"J": 57.08,
|
|
1737
|
+
"K": 67.19,
|
|
1738
|
+
"L": 56.54,
|
|
1739
|
+
"M": 90.33,
|
|
1740
|
+
"N": 75.34,
|
|
1741
|
+
"O": 76.46,
|
|
1742
|
+
"P": 63.87,
|
|
1743
|
+
"Q": 76.46,
|
|
1744
|
+
"R": 64.36,
|
|
1745
|
+
"S": 64.16,
|
|
1746
|
+
"T": 64.55,
|
|
1747
|
+
"U": 74.41,
|
|
1748
|
+
"V": 68.99,
|
|
1749
|
+
"W": 98.54,
|
|
1750
|
+
"X": 68.21,
|
|
1751
|
+
"Y": 67.87,
|
|
1752
|
+
"Z": 62.89,
|
|
1753
|
+
"[": 36.47,
|
|
1754
|
+
"\\": 36.04,
|
|
1755
|
+
"]": 36.47,
|
|
1756
|
+
"^": 47.12,
|
|
1757
|
+
"_": 45.61,
|
|
1758
|
+
"`": 32.28,
|
|
1759
|
+
"a": 56.15,
|
|
1760
|
+
"b": 61.23,
|
|
1761
|
+
"c": 57.13,
|
|
1762
|
+
"d": 61.23,
|
|
1763
|
+
"e": 58.3,
|
|
1764
|
+
"f": 37.01,
|
|
1765
|
+
"g": 61.33,
|
|
1766
|
+
"h": 59.13,
|
|
1767
|
+
"i": 24.22,
|
|
1768
|
+
"j": 24.22,
|
|
1769
|
+
"k": 54.88,
|
|
1770
|
+
"l": 24.22,
|
|
1771
|
+
"m": 87.6,
|
|
1772
|
+
"n": 59.08,
|
|
1773
|
+
"o": 59.96,
|
|
1774
|
+
"p": 61.23,
|
|
1775
|
+
"q": 61.23,
|
|
1776
|
+
"r": 37.65,
|
|
1777
|
+
"s": 52.78,
|
|
1778
|
+
"t": 32.71,
|
|
1779
|
+
"u": 59.13,
|
|
1780
|
+
"v": 56.2,
|
|
1781
|
+
"w": 81.84,
|
|
1782
|
+
"x": 54.59,
|
|
1783
|
+
"y": 56.2,
|
|
1784
|
+
"z": 55.22,
|
|
1785
|
+
"{": 42.63,
|
|
1786
|
+
"|": 33.25,
|
|
1787
|
+
"}": 42.63,
|
|
1788
|
+
"~": 66.16
|
|
1789
|
+
},
|
|
1790
|
+
"700": {
|
|
1791
|
+
"0": 67.43,
|
|
1792
|
+
"1": 43.12,
|
|
1793
|
+
"2": 62.94,
|
|
1794
|
+
"3": 64.55,
|
|
1795
|
+
"4": 67.63,
|
|
1796
|
+
"5": 62.21,
|
|
1797
|
+
"6": 64.94,
|
|
1798
|
+
"7": 58.15,
|
|
1799
|
+
"8": 65.09,
|
|
1800
|
+
"9": 64.94,
|
|
1801
|
+
" ": 23.68,
|
|
1802
|
+
"!": 33.79,
|
|
1803
|
+
'"': 55.13,
|
|
1804
|
+
"#": 64.89,
|
|
1805
|
+
"$": 65.48,
|
|
1806
|
+
"%": 101.56,
|
|
1807
|
+
"&": 67.19,
|
|
1808
|
+
"'": 33.89,
|
|
1809
|
+
"(": 37.7,
|
|
1810
|
+
")": 37.7,
|
|
1811
|
+
"*": 55.91,
|
|
1812
|
+
"+": 67.87,
|
|
1813
|
+
",": 33.4,
|
|
1814
|
+
"-": 46.78,
|
|
1815
|
+
".": 33.4,
|
|
1816
|
+
"/": 38.82,
|
|
1817
|
+
":": 33.4,
|
|
1818
|
+
";": 34.28,
|
|
1819
|
+
"<": 67.87,
|
|
1820
|
+
"=": 67.87,
|
|
1821
|
+
">": 67.87,
|
|
1822
|
+
"?": 55.96,
|
|
1823
|
+
"@": 101.61,
|
|
1824
|
+
"A": 74.66,
|
|
1825
|
+
"B": 66.16,
|
|
1826
|
+
"C": 73.97,
|
|
1827
|
+
"D": 72.22,
|
|
1828
|
+
"E": 60.74,
|
|
1829
|
+
"F": 58.69,
|
|
1830
|
+
"G": 75.05,
|
|
1831
|
+
"H": 74.71,
|
|
1832
|
+
"I": 28.08,
|
|
1833
|
+
"J": 58.45,
|
|
1834
|
+
"K": 71.92,
|
|
1835
|
+
"L": 56.54,
|
|
1836
|
+
"M": 93.16,
|
|
1837
|
+
"N": 76.22,
|
|
1838
|
+
"O": 77.05,
|
|
1839
|
+
"P": 64.79,
|
|
1840
|
+
"Q": 77.69,
|
|
1841
|
+
"R": 65.67,
|
|
1842
|
+
"S": 65.48,
|
|
1843
|
+
"T": 66.75,
|
|
1844
|
+
"U": 73.19,
|
|
1845
|
+
"V": 74.66,
|
|
1846
|
+
"W": 103.76,
|
|
1847
|
+
"X": 73.83,
|
|
1848
|
+
"Y": 73.1,
|
|
1849
|
+
"Z": 66.41,
|
|
1850
|
+
"[": 37.7,
|
|
1851
|
+
"\\": 38.82,
|
|
1852
|
+
"]": 37.7,
|
|
1853
|
+
"^": 48.68,
|
|
1854
|
+
"_": 47.61,
|
|
1855
|
+
"`": 36.52,
|
|
1856
|
+
"a": 58.06,
|
|
1857
|
+
"b": 63.04,
|
|
1858
|
+
"c": 58.84,
|
|
1859
|
+
"d": 63.04,
|
|
1860
|
+
"e": 59.57,
|
|
1861
|
+
"f": 39.79,
|
|
1862
|
+
"g": 63.18,
|
|
1863
|
+
"h": 62.26,
|
|
1864
|
+
"i": 27.1,
|
|
1865
|
+
"j": 27.1,
|
|
1866
|
+
"k": 58.01,
|
|
1867
|
+
"l": 27.1,
|
|
1868
|
+
"m": 91.26,
|
|
1869
|
+
"n": 62.26,
|
|
1870
|
+
"o": 61.33,
|
|
1871
|
+
"p": 63.04,
|
|
1872
|
+
"q": 63.04,
|
|
1873
|
+
"r": 40.72,
|
|
1874
|
+
"s": 56.01,
|
|
1875
|
+
"t": 36.62,
|
|
1876
|
+
"u": 62.26,
|
|
1877
|
+
"v": 59.96,
|
|
1878
|
+
"w": 85.01,
|
|
1879
|
+
"x": 58.01,
|
|
1880
|
+
"y": 60.21,
|
|
1881
|
+
"z": 57.28,
|
|
1882
|
+
"{": 46.88,
|
|
1883
|
+
"|": 37.16,
|
|
1884
|
+
"}": 46.88,
|
|
1885
|
+
"~": 67.87
|
|
1886
|
+
},
|
|
1887
|
+
"800": {
|
|
1888
|
+
"0": 69.19,
|
|
1889
|
+
"1": 44.14,
|
|
1890
|
+
"2": 63.77,
|
|
1891
|
+
"3": 65.67,
|
|
1892
|
+
"4": 68.85,
|
|
1893
|
+
"5": 63.38,
|
|
1894
|
+
"6": 66.16,
|
|
1895
|
+
"7": 58.79,
|
|
1896
|
+
"8": 66.41,
|
|
1897
|
+
"9": 66.16,
|
|
1898
|
+
" ": 21.88,
|
|
1899
|
+
"!": 35.84,
|
|
1900
|
+
'"': 58.64,
|
|
1901
|
+
"#": 65.53,
|
|
1902
|
+
"$": 66.02,
|
|
1903
|
+
"%": 102.93,
|
|
1904
|
+
"&": 68.31,
|
|
1905
|
+
"'": 35.45,
|
|
1906
|
+
"(": 38.23,
|
|
1907
|
+
")": 38.23,
|
|
1908
|
+
"*": 58.25,
|
|
1909
|
+
"+": 68.55,
|
|
1910
|
+
",": 35.25,
|
|
1911
|
+
"-": 47.12,
|
|
1912
|
+
".": 35.25,
|
|
1913
|
+
"/": 39.99,
|
|
1914
|
+
":": 35.25,
|
|
1915
|
+
";": 35.99,
|
|
1916
|
+
"<": 68.55,
|
|
1917
|
+
"=": 68.55,
|
|
1918
|
+
">": 68.55,
|
|
1919
|
+
"?": 57.91,
|
|
1920
|
+
"@": 103.61,
|
|
1921
|
+
"A": 76.95,
|
|
1922
|
+
"B": 66.46,
|
|
1923
|
+
"C": 74.37,
|
|
1924
|
+
"D": 72.27,
|
|
1925
|
+
"E": 60.99,
|
|
1926
|
+
"F": 58.54,
|
|
1927
|
+
"G": 75.24,
|
|
1928
|
+
"H": 74.85,
|
|
1929
|
+
"I": 28.56,
|
|
1930
|
+
"J": 58.98,
|
|
1931
|
+
"K": 73.83,
|
|
1932
|
+
"L": 56.54,
|
|
1933
|
+
"M": 94.34,
|
|
1934
|
+
"N": 76.56,
|
|
1935
|
+
"O": 77.29,
|
|
1936
|
+
"P": 65.19,
|
|
1937
|
+
"Q": 78.17,
|
|
1938
|
+
"R": 66.21,
|
|
1939
|
+
"S": 66.02,
|
|
1940
|
+
"T": 67.68,
|
|
1941
|
+
"U": 72.71,
|
|
1942
|
+
"V": 76.95,
|
|
1943
|
+
"W": 105.86,
|
|
1944
|
+
"X": 76.12,
|
|
1945
|
+
"Y": 75.2,
|
|
1946
|
+
"Z": 67.87,
|
|
1947
|
+
"[": 38.23,
|
|
1948
|
+
"\\": 39.99,
|
|
1949
|
+
"]": 38.23,
|
|
1950
|
+
"^": 49.32,
|
|
1951
|
+
"_": 48.44,
|
|
1952
|
+
"`": 38.23,
|
|
1953
|
+
"a": 58.84,
|
|
1954
|
+
"b": 63.77,
|
|
1955
|
+
"c": 59.52,
|
|
1956
|
+
"d": 63.77,
|
|
1957
|
+
"e": 60.06,
|
|
1958
|
+
"f": 40.97,
|
|
1959
|
+
"g": 63.92,
|
|
1960
|
+
"h": 63.53,
|
|
1961
|
+
"i": 28.32,
|
|
1962
|
+
"j": 28.32,
|
|
1963
|
+
"k": 59.28,
|
|
1964
|
+
"l": 28.32,
|
|
1965
|
+
"m": 92.72,
|
|
1966
|
+
"n": 63.53,
|
|
1967
|
+
"o": 61.91,
|
|
1968
|
+
"p": 63.77,
|
|
1969
|
+
"q": 63.77,
|
|
1970
|
+
"r": 41.99,
|
|
1971
|
+
"s": 57.32,
|
|
1972
|
+
"t": 38.18,
|
|
1973
|
+
"u": 63.53,
|
|
1974
|
+
"v": 61.52,
|
|
1975
|
+
"w": 86.28,
|
|
1976
|
+
"x": 59.42,
|
|
1977
|
+
"y": 61.82,
|
|
1978
|
+
"z": 58.11,
|
|
1979
|
+
"{": 48.63,
|
|
1980
|
+
"|": 38.77,
|
|
1981
|
+
"}": 48.63,
|
|
1982
|
+
"~": 68.55
|
|
1983
|
+
}
|
|
1984
|
+
};
|
|
1985
|
+
var INTER_FALLBACK = {
|
|
1986
|
+
"400": 56.16,
|
|
1987
|
+
"700": 58.74,
|
|
1988
|
+
"800": 59.79
|
|
1989
|
+
};
|
|
1990
|
+
|
|
1991
|
+
// ../core/src/textFx.ts
|
|
1992
|
+
var clamp013 = (v) => Math.max(0, Math.min(1, v));
|
|
1993
|
+
var fract = (v) => v - Math.floor(v);
|
|
1994
|
+
var rand = (i, salt) => fract(Math.sin(i * 127.1 + salt * 311.7) * 43758.5453);
|
|
1995
|
+
var dur3 = (base, sp) => base / sp;
|
|
1996
|
+
var SCRAMBLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#%&@";
|
|
1997
|
+
var advance = (ch, weight, fontSize) => (INTER_ADVANCE[weight]?.[ch] ?? INTER_FALLBACK[weight]) * (fontSize / 100);
|
|
1998
|
+
function splitText(textStr, opts) {
|
|
1999
|
+
const { id, x, y, fontSize } = opts;
|
|
2000
|
+
const weight = opts.fontWeight ?? 800;
|
|
2001
|
+
const fill = opts.fill ?? "#FFFFFF";
|
|
2002
|
+
const ls = opts.letterSpacing ?? 0;
|
|
2003
|
+
const align = opts.align ?? "center";
|
|
2004
|
+
const unit = opts.unit ?? "glyph";
|
|
2005
|
+
const opacity = opts.opacity ?? 0;
|
|
2006
|
+
const chars = [...textStr];
|
|
2007
|
+
let total = 0;
|
|
2008
|
+
chars.forEach((ch, i) => {
|
|
2009
|
+
total += advance(ch, weight, fontSize) + (i < chars.length - 1 ? ls : 0);
|
|
2010
|
+
});
|
|
2011
|
+
let cursor = align === "center" ? x - total / 2 : x;
|
|
2012
|
+
const glyphs = [];
|
|
2013
|
+
const nodes = [];
|
|
2014
|
+
const mk = (ch, cx, adv, lsProp) => {
|
|
2015
|
+
const g = { id: `${id}-${glyphs.length}`, ch, x: cx, y, advance: adv, i: glyphs.length };
|
|
2016
|
+
glyphs.push(g);
|
|
2017
|
+
nodes.push(
|
|
2018
|
+
text({
|
|
2019
|
+
id: g.id,
|
|
2020
|
+
x: cx,
|
|
2021
|
+
y,
|
|
2022
|
+
content: ch,
|
|
2023
|
+
fontFamily: "Inter",
|
|
2024
|
+
fontSize,
|
|
2025
|
+
fontWeight: weight,
|
|
2026
|
+
fill,
|
|
2027
|
+
anchor: "center",
|
|
2028
|
+
opacity,
|
|
2029
|
+
...lsProp ? { letterSpacing: lsProp } : {}
|
|
2030
|
+
})
|
|
2031
|
+
);
|
|
2032
|
+
};
|
|
2033
|
+
if (unit === "word") {
|
|
2034
|
+
let i = 0;
|
|
2035
|
+
while (i < chars.length) {
|
|
2036
|
+
if (chars[i] === " ") {
|
|
2037
|
+
cursor += advance(" ", weight, fontSize) + ls;
|
|
2038
|
+
i++;
|
|
2039
|
+
continue;
|
|
2040
|
+
}
|
|
2041
|
+
let word = "";
|
|
2042
|
+
let w = 0;
|
|
2043
|
+
const startCursor = cursor;
|
|
2044
|
+
while (i < chars.length && chars[i] !== " ") {
|
|
2045
|
+
const a = advance(chars[i], weight, fontSize);
|
|
2046
|
+
word += chars[i];
|
|
2047
|
+
w += a + (chars[i + 1] && chars[i + 1] !== " " ? ls : 0);
|
|
2048
|
+
i++;
|
|
2049
|
+
}
|
|
2050
|
+
mk(word, startCursor + w / 2, w, ls);
|
|
2051
|
+
cursor = startCursor + w + ls;
|
|
2052
|
+
}
|
|
2053
|
+
} else {
|
|
2054
|
+
chars.forEach((ch) => {
|
|
2055
|
+
const a = advance(ch, weight, fontSize);
|
|
2056
|
+
if (ch !== " ") mk(ch, cursor + a / 2, a);
|
|
2057
|
+
cursor += a + ls;
|
|
2058
|
+
});
|
|
2059
|
+
}
|
|
2060
|
+
return { nodes, glyphs, ids: glyphs.map((g) => g.id), width: total, x, y, fontSize };
|
|
2061
|
+
}
|
|
2062
|
+
var ctx3 = (o) => ({
|
|
2063
|
+
sp: Math.max(0.25, o.speed ?? 1),
|
|
2064
|
+
e: clamp013(o.energy ?? 0.5),
|
|
2065
|
+
seed: o.seed ?? 0,
|
|
2066
|
+
fs: 0,
|
|
2067
|
+
stag: o.stagger
|
|
2068
|
+
});
|
|
2069
|
+
var IN_STAGGER = { typewriter: 0.065, cascade: 0.04, rise: 0.03, bounce: 0.045, assemble: 0.05, decode: 0.05 };
|
|
2070
|
+
function glyphIn(name, g, c) {
|
|
2071
|
+
const set = (props) => tween(g.id, props, { duration: 1e-3 });
|
|
2072
|
+
const rs = (salt) => rand(g.i, salt + c.seed);
|
|
2073
|
+
switch (name) {
|
|
2074
|
+
case "typewriter":
|
|
2075
|
+
return tween(g.id, { opacity: 1 }, { duration: dur3(0.04, c.sp), ease: "linear" });
|
|
2076
|
+
case "cascade":
|
|
2077
|
+
return seq(
|
|
2078
|
+
set({ y: g.y + 56, opacity: 0 }),
|
|
2079
|
+
par(
|
|
2080
|
+
tween(g.id, { opacity: 1 }, { duration: dur3(0.22, c.sp), ease: "easeOutQuad" }),
|
|
2081
|
+
tween(g.id, { y: g.y }, { duration: dur3(0.34, c.sp), ease: "easeOutCubic" })
|
|
2082
|
+
)
|
|
2083
|
+
);
|
|
2084
|
+
case "rise":
|
|
2085
|
+
return seq(
|
|
2086
|
+
set({ y: g.y + 36, opacity: 0 }),
|
|
2087
|
+
par(
|
|
2088
|
+
tween(g.id, { opacity: 1 }, { duration: dur3(0.3, c.sp), ease: "easeOutQuad" }),
|
|
2089
|
+
tween(g.id, { y: g.y }, { duration: dur3(0.4, c.sp), ease: "easeOutQuad" })
|
|
2090
|
+
)
|
|
2091
|
+
);
|
|
2092
|
+
case "bounce":
|
|
2093
|
+
return seq(
|
|
2094
|
+
set({ y: g.y - 80 * (0.6 + c.e), opacity: 0, scale: 0.7 }),
|
|
2095
|
+
par(
|
|
2096
|
+
tween(g.id, { opacity: 1 }, { duration: dur3(0.2, c.sp), ease: "easeOutQuad" }),
|
|
2097
|
+
tween(g.id, { y: g.y, scale: 1 }, { duration: dur3(0.7, c.sp), ease: "easeOutBounce" })
|
|
2098
|
+
)
|
|
2099
|
+
);
|
|
2100
|
+
case "assemble":
|
|
2101
|
+
return seq(
|
|
2102
|
+
set({ x: g.x + (rs(11) - 0.5) * 1e3 * (0.5 + c.e), y: g.y + (rs(12) - 0.5) * 640, rotation: (rs(13) - 0.5) * 200, scale: 0.4, opacity: 0 }),
|
|
2103
|
+
par(
|
|
2104
|
+
tween(g.id, { opacity: 1 }, { duration: dur3(0.4, c.sp), ease: "easeOutQuad" }),
|
|
2105
|
+
tween(g.id, { x: g.x, y: g.y, rotation: 0, scale: 1 }, { duration: dur3(0.8, c.sp), ease: "easeOutExpo" })
|
|
2106
|
+
)
|
|
2107
|
+
);
|
|
2108
|
+
case "decode": {
|
|
2109
|
+
const steps = 4 + Math.floor(rs(7) * 3);
|
|
2110
|
+
const flicker = [set({ opacity: 1 })];
|
|
2111
|
+
for (let k = 0; k < steps; k++) {
|
|
2112
|
+
flicker.push(tween(g.id, { content: SCRAMBLE[Math.floor(rand(g.i, 20 + k + c.seed) * SCRAMBLE.length)] }, { duration: dur3(0.05, c.sp), ease: "linear" }));
|
|
2113
|
+
}
|
|
2114
|
+
flicker.push(tween(g.id, { content: g.ch }, { duration: dur3(0.05, c.sp), ease: "linear" }));
|
|
2115
|
+
return seq(...flicker);
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
function textIn(name, block, opts = {}) {
|
|
2120
|
+
const c = { ...ctx3(opts), fs: block.fontSize };
|
|
2121
|
+
const interval = (c.stag ?? IN_STAGGER[name]) / c.sp;
|
|
2122
|
+
return beat(opts.label ?? `text-in-${name}`, {}, [stagger(interval, ...block.glyphs.map((g) => glyphIn(name, g, c)))]);
|
|
2123
|
+
}
|
|
2124
|
+
function textLoop(name, block, opts = {}) {
|
|
2125
|
+
const win = { ...opts.from !== void 0 && { from: opts.from }, ...opts.until !== void 0 && { until: opts.until }, ...opts.ramp !== void 0 && { ramp: opts.ramp } };
|
|
2126
|
+
const f = opts.frequency ?? (name === "wave" ? 0.9 : name === "shimmer" ? 1.4 : 0.7);
|
|
2127
|
+
const ps = opts.phaseStep ?? 0.55;
|
|
2128
|
+
return block.glyphs.map((g, i) => {
|
|
2129
|
+
switch (name) {
|
|
2130
|
+
case "wave":
|
|
2131
|
+
return oscillate(g.id, "y", { amplitude: opts.amplitude ?? 9, frequency: f, phase: i * ps }, win);
|
|
2132
|
+
case "shimmer":
|
|
2133
|
+
return oscillate(g.id, "opacity", { amplitude: opts.amplitude ?? 0.25, frequency: f, phase: i * ps }, win);
|
|
2134
|
+
case "wobble":
|
|
2135
|
+
return oscillate(g.id, "rotation", { amplitude: opts.amplitude ?? 6, frequency: f, phase: i * ps }, win);
|
|
2136
|
+
case "float":
|
|
2137
|
+
return oscillate(g.id, "y", { amplitude: opts.amplitude ?? 5, frequency: f, phase: i * ps }, win);
|
|
2138
|
+
}
|
|
2139
|
+
});
|
|
2140
|
+
}
|
|
2141
|
+
var OUT_STAGGER = { shatter: 0.02, fly: 0.012, dissolve: 0, fall: 0.02, collapse: 0.02 };
|
|
2142
|
+
function glyphOut(name, g, c, block, dir) {
|
|
2143
|
+
const rs = (salt) => rand(g.i, salt + c.seed);
|
|
2144
|
+
switch (name) {
|
|
2145
|
+
case "shatter":
|
|
2146
|
+
return par(
|
|
2147
|
+
tween(g.id, { x: g.x + (rs(21) - 0.5) * 1100 * (0.6 + c.e), y: g.y + (rs(22) - 0.5) * 760 }, { duration: dur3(0.7, c.sp), ease: "easeInCubic" }),
|
|
2148
|
+
tween(g.id, { rotation: (rs(23) - 0.5) * 300, opacity: 0 }, { duration: dur3(0.7, c.sp), ease: "easeInQuad" })
|
|
2149
|
+
);
|
|
2150
|
+
case "fly":
|
|
2151
|
+
return par(
|
|
2152
|
+
tween(g.id, { x: g.x + dir[0] * 1200, y: g.y + dir[1] * 1200 }, { duration: dur3(0.6, c.sp), ease: "easeInCubic" }),
|
|
2153
|
+
tween(g.id, { opacity: 0 }, { duration: dur3(0.5, c.sp), ease: "easeInQuad" })
|
|
2154
|
+
);
|
|
2155
|
+
case "dissolve":
|
|
2156
|
+
return seq(wait(rs(31) * 0.5), par(
|
|
2157
|
+
tween(g.id, { opacity: 0 }, { duration: dur3(0.4, c.sp), ease: "easeInQuad" }),
|
|
2158
|
+
tween(g.id, { scale: 1.4 }, { duration: dur3(0.4, c.sp), ease: "easeOutQuad" })
|
|
2159
|
+
));
|
|
2160
|
+
case "fall":
|
|
2161
|
+
return par(
|
|
2162
|
+
tween(g.id, { y: g.y + 700 + rs(41) * 200 }, { duration: dur3(0.8, c.sp), ease: "easeInQuad" }),
|
|
2163
|
+
tween(g.id, { rotation: (rs(42) - 0.5) * 120, opacity: 0 }, { duration: dur3(0.8, c.sp), ease: "easeInQuad" })
|
|
2164
|
+
);
|
|
2165
|
+
case "collapse":
|
|
2166
|
+
return par(
|
|
2167
|
+
tween(g.id, { x: block.x, y: block.y, scale: 0.2 }, { duration: dur3(0.5, c.sp), ease: "easeInBack" }),
|
|
2168
|
+
tween(g.id, { opacity: 0 }, { duration: dur3(0.5, c.sp), ease: "easeInQuad" })
|
|
2169
|
+
);
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
function textOut(name, block, opts = {}) {
|
|
2173
|
+
const c = { ...ctx3(opts), fs: block.fontSize };
|
|
2174
|
+
const dir = opts.dir ?? [0, -1];
|
|
2175
|
+
const steps = block.glyphs.map((g) => glyphOut(name, g, c, block, dir));
|
|
2176
|
+
const interval = (c.stag ?? OUT_STAGGER[name]) / c.sp;
|
|
2177
|
+
const body = interval > 0 ? stagger(interval, ...steps) : par(...steps);
|
|
2178
|
+
return beat(opts.label ?? `text-out-${name}`, {}, [body]);
|
|
2179
|
+
}
|
|
2180
|
+
function textTypeCues(block, opts) {
|
|
2181
|
+
const interval = opts.interval ?? 0.065;
|
|
2182
|
+
const gain = opts.gain ?? 0.4;
|
|
2183
|
+
const off = opts.offset ?? 0;
|
|
2184
|
+
const KEYS = ["001", "004", "007", "010", "014"];
|
|
2185
|
+
return block.glyphs.map((g, i) => ({
|
|
2186
|
+
at: opts.at,
|
|
2187
|
+
offset: off + i * interval,
|
|
2188
|
+
file: `keypress-${KEYS[i % KEYS.length]}.wav`,
|
|
2189
|
+
gain: gain + 0.2 * rand(i, 31)
|
|
2190
|
+
}));
|
|
2191
|
+
}
|
|
2192
|
+
|
|
1691
2193
|
// ../core/src/motionOps.ts
|
|
1692
2194
|
var MOTION_OPS = ["rotate", "zoom", "ken-burns", "slide-in", "fade", "draw-on", "pulse"];
|
|
1693
|
-
var
|
|
2195
|
+
var clamp014 = (n3) => Math.max(0, Math.min(1, n3));
|
|
1694
2196
|
function settleEase2(e) {
|
|
1695
2197
|
return e < 0.34 ? "easeOutCubic" : e < 0.67 ? "easeOutBack" : "easeOutElastic";
|
|
1696
2198
|
}
|
|
@@ -1708,7 +2210,7 @@ function fromVec2(from, dist) {
|
|
|
1708
2210
|
}
|
|
1709
2211
|
var motionOpLabel = (name, target) => `op-${name}-${target}`;
|
|
1710
2212
|
function motionOp(name, target, opts = {}) {
|
|
1711
|
-
const e =
|
|
2213
|
+
const e = clamp014(opts.energy ?? 0.5);
|
|
1712
2214
|
const sp = Math.max(0.25, opts.speed ?? 1);
|
|
1713
2215
|
const amt = opts.amount ?? 1;
|
|
1714
2216
|
const b = { scale: 1, x: 0, y: 0, rotation: 0, ...opts.base };
|
|
@@ -2411,29 +2913,29 @@ function sketchToTimeline(sketch, nodeIds) {
|
|
|
2411
2913
|
const steps = [];
|
|
2412
2914
|
events.forEach((ev, i) => {
|
|
2413
2915
|
const node = nodeIds[i % nodeIds.length];
|
|
2414
|
-
const
|
|
2916
|
+
const dur4 = Math.max(0.05, ev.t1 - ev.t0);
|
|
2415
2917
|
const ease = easeFor(ev.easing);
|
|
2416
2918
|
let motion;
|
|
2417
2919
|
switch (ev.kind) {
|
|
2418
2920
|
case "enter":
|
|
2419
|
-
motion = tween(node, { opacity: 1 }, { duration:
|
|
2921
|
+
motion = tween(node, { opacity: 1 }, { duration: dur4, ease });
|
|
2420
2922
|
break;
|
|
2421
2923
|
case "exit":
|
|
2422
|
-
motion = tween(node, { opacity: 0 }, { duration:
|
|
2924
|
+
motion = tween(node, { opacity: 0 }, { duration: dur4, ease });
|
|
2423
2925
|
break;
|
|
2424
2926
|
case "emphasis": {
|
|
2425
2927
|
const peak = 1 + Math.max(0.08, Math.min(0.5, ev.magnitude));
|
|
2426
2928
|
motion = seq(
|
|
2427
|
-
tween(node, { scale: peak }, { duration:
|
|
2428
|
-
tween(node, { scale: 1 }, { duration:
|
|
2929
|
+
tween(node, { scale: peak }, { duration: dur4 / 2, ease: "easeOutCubic" }),
|
|
2930
|
+
tween(node, { scale: 1 }, { duration: dur4 / 2, ease: "easeInOutQuad" })
|
|
2429
2931
|
);
|
|
2430
2932
|
break;
|
|
2431
2933
|
}
|
|
2432
2934
|
case "scale":
|
|
2433
|
-
motion = tween(node, { scale: 1 + Math.max(-0.5, Math.min(0.5, ev.magnitude)) }, { duration:
|
|
2935
|
+
motion = tween(node, { scale: 1 + Math.max(-0.5, Math.min(0.5, ev.magnitude)) }, { duration: dur4, ease });
|
|
2434
2936
|
break;
|
|
2435
2937
|
case "move":
|
|
2436
|
-
motion = tween(node, { opacity: 1 }, { duration:
|
|
2938
|
+
motion = tween(node, { opacity: 1 }, { duration: dur4, ease });
|
|
2437
2939
|
break;
|
|
2438
2940
|
}
|
|
2439
2941
|
steps.push(ev.t0 > 0 ? seq(wait(ev.t0), motion) : motion);
|
|
@@ -2499,8 +3001,13 @@ export {
|
|
|
2499
3001
|
scene,
|
|
2500
3002
|
seq,
|
|
2501
3003
|
sketchToTimeline,
|
|
3004
|
+
splitText,
|
|
2502
3005
|
stagger,
|
|
2503
3006
|
text,
|
|
3007
|
+
textIn,
|
|
3008
|
+
textLoop,
|
|
3009
|
+
textOut,
|
|
3010
|
+
textTypeCues,
|
|
2504
3011
|
to,
|
|
2505
3012
|
tween,
|
|
2506
3013
|
validateComposition,
|
package/dist/types/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export { devicePreset, deviceScreen, deviceScreenCenter, deviceBounds, DEVICE_PR
|
|
|
10
10
|
export { rig, rigPose, poseTo, ikReach, humanoid, ovalPath, type Bone, type RigOpts, type Pose, type HumanoidOpts } from "./rig.js";
|
|
11
11
|
export { characterPreset, CHARACTER_PRESET_NAMES, type CharacterPresetName, type CharacterPresetOpts } from "./characterPreset.js";
|
|
12
12
|
export { figure, type FigureStyle, type FigureOpts, type FigurePalette } from "./figure.js";
|
|
13
|
+
export { splitText, textIn, textLoop, textOut, textTypeCues, type SplitOpts, type Glyph, type TextBlock, type FontWeight, type TextInName, type TextLoopName, type TextOutName, type TextLoopOpts, type TextOutOpts, type TypeCueOpts, } from "./textFx.js";
|
|
13
14
|
export { motionOp, motionOpLabel, MOTION_OPS, type MotionOpName, type MotionOpOpts, type MotionOpResult } from "./motionOps.js";
|
|
14
15
|
export { resolveAudioPlan, resolveCompositionAudioPlan, SFX_DURATION, type AudioPlan, type ResolvedCue, } from "./audio.js";
|
|
15
16
|
export { evaluate, sampleProp, nodeParentMatrix, type DisplayList, type DisplayOp, type Mat2D, type ClipRegion, type TextAlign, type TextBaseline, } from "./evaluate.js";
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kinetic text — a deterministic per-glyph text splitter plus a library of
|
|
3
|
+
* seeded effect generators (entrance / sustained / exit). The text analog of
|
|
4
|
+
* `motionPreset` / `characterPreset`: `splitText()` lays a phrase out as
|
|
5
|
+
* center-anchored `text` nodes (advances measured from the real font, so layout
|
|
6
|
+
* matches the render), then `textIn` / `textLoop` / `textOut` animate the glyphs.
|
|
7
|
+
*
|
|
8
|
+
* const T = splitText("MOTION IS DATA", { id: "t", x: 960, y: 470, fontSize: 130 });
|
|
9
|
+
* // nodes: [...T.nodes]
|
|
10
|
+
* // timeline: seq(textIn("typewriter", T), wait(2), textOut("shatter", T, { seed: 3 }))
|
|
11
|
+
* // behaviors: textLoop("wave", T, { from: 1.6, until: 3.6 })
|
|
12
|
+
*/
|
|
13
|
+
import { type BehaviorWindow } from "./dsl.js";
|
|
14
|
+
import type { AudioCueIR, BehaviorIR, NodeIR, TimelineIR } from "./ir.js";
|
|
15
|
+
export type FontWeight = 400 | 700 | 800;
|
|
16
|
+
export interface SplitOpts {
|
|
17
|
+
/** Id PREFIX → glyph ids `${id}-${i}`. */
|
|
18
|
+
id: string;
|
|
19
|
+
/** Anchor point of the line. */
|
|
20
|
+
x: number;
|
|
21
|
+
y: number;
|
|
22
|
+
fontSize: number;
|
|
23
|
+
fontWeight?: FontWeight;
|
|
24
|
+
fill?: string;
|
|
25
|
+
/** Extra px between glyphs (tracking). */
|
|
26
|
+
letterSpacing?: number;
|
|
27
|
+
/** Horizontal alignment about `x` (default "center"). */
|
|
28
|
+
align?: "left" | "center";
|
|
29
|
+
/** Animate per glyph or per word (default "glyph"). */
|
|
30
|
+
unit?: "glyph" | "word";
|
|
31
|
+
/** Starting opacity of the nodes (default 0, for entrances). */
|
|
32
|
+
opacity?: number;
|
|
33
|
+
}
|
|
34
|
+
export interface Glyph {
|
|
35
|
+
id: string;
|
|
36
|
+
/** The character (glyph unit) or word (word unit). */
|
|
37
|
+
ch: string;
|
|
38
|
+
/** Home centre (the laid-out resting position). */
|
|
39
|
+
x: number;
|
|
40
|
+
y: number;
|
|
41
|
+
/** This unit's advance width in px. */
|
|
42
|
+
advance: number;
|
|
43
|
+
/** Index in declaration order. */
|
|
44
|
+
i: number;
|
|
45
|
+
}
|
|
46
|
+
export interface TextBlock {
|
|
47
|
+
nodes: NodeIR[];
|
|
48
|
+
glyphs: Glyph[];
|
|
49
|
+
ids: string[];
|
|
50
|
+
/** Total laid-out width in px. */
|
|
51
|
+
width: number;
|
|
52
|
+
x: number;
|
|
53
|
+
y: number;
|
|
54
|
+
fontSize: number;
|
|
55
|
+
}
|
|
56
|
+
export declare function splitText(textStr: string, opts: SplitOpts): TextBlock;
|
|
57
|
+
interface FxOpts {
|
|
58
|
+
speed?: number;
|
|
59
|
+
energy?: number;
|
|
60
|
+
seed?: number;
|
|
61
|
+
stagger?: number;
|
|
62
|
+
label?: string;
|
|
63
|
+
}
|
|
64
|
+
export type TextInName = "typewriter" | "cascade" | "rise" | "bounce" | "assemble" | "decode";
|
|
65
|
+
export declare function textIn(name: TextInName, block: TextBlock, opts?: FxOpts): TimelineIR;
|
|
66
|
+
export type TextLoopName = "wave" | "shimmer" | "wobble" | "float";
|
|
67
|
+
export interface TextLoopOpts extends BehaviorWindow {
|
|
68
|
+
amplitude?: number;
|
|
69
|
+
frequency?: number;
|
|
70
|
+
/** phase offset per glyph (the travelling-wave speed). */
|
|
71
|
+
phaseStep?: number;
|
|
72
|
+
}
|
|
73
|
+
export declare function textLoop(name: TextLoopName, block: TextBlock, opts?: TextLoopOpts): BehaviorIR[];
|
|
74
|
+
export type TextOutName = "shatter" | "fly" | "dissolve" | "fall" | "collapse";
|
|
75
|
+
export interface TextOutOpts extends FxOpts {
|
|
76
|
+
/** direction for "fly" (default up). */
|
|
77
|
+
dir?: [number, number];
|
|
78
|
+
}
|
|
79
|
+
export declare function textOut(name: TextOutName, block: TextBlock, opts?: TextOutOpts): TimelineIR;
|
|
80
|
+
export interface TypeCueOpts {
|
|
81
|
+
/** the timeline label the typewriter `textIn` starts at. */
|
|
82
|
+
at: string | number;
|
|
83
|
+
/** seconds between keystrokes (match the textIn stagger / speed). */
|
|
84
|
+
interval?: number;
|
|
85
|
+
gain?: number;
|
|
86
|
+
/** offset of the first key from `at`. */
|
|
87
|
+
offset?: number;
|
|
88
|
+
}
|
|
89
|
+
/** Per-glyph CC0 keypress for `textIn("typewriter", …)`. */
|
|
90
|
+
export declare function textTypeCues(block: TextBlock, opts: TypeCueOpts): AudioCueIR[];
|
|
91
|
+
export {};
|
package/guides/edsl-guide.md
CHANGED
|
@@ -167,6 +167,38 @@ no new renderer concept, so overlays/preview/determinism all apply.
|
|
|
167
167
|
set it when the same preset is used more than once in a scene). Legs use
|
|
168
168
|
`ikReach`, arms FK; pure keyframes, so add continuous idle yourself with `oscillate`.
|
|
169
169
|
|
|
170
|
+
## Kinetic text (split + effect presets)
|
|
171
|
+
|
|
172
|
+
reframe's `text` node renders a whole string as one node, so per-glyph effects
|
|
173
|
+
need the string split into per-character nodes. `splitText` does that once;
|
|
174
|
+
seeded effect generators animate the glyphs (the text analog of `motionPreset`).
|
|
175
|
+
|
|
176
|
+
- `splitText(text, { id, x, y, fontSize, fontWeight?, fill?, letterSpacing?,
|
|
177
|
+
align?, unit?, opacity? }) → TextBlock` — lays the phrase out as center-anchored
|
|
178
|
+
`text` nodes using **real Inter advance widths** (so layout matches the render).
|
|
179
|
+
Returns `{ nodes, glyphs, ids, width, ... }`; put `...block.nodes` in `nodes`.
|
|
180
|
+
Glyph ids are `${id}-${i}` (stable regen addresses). `unit: "word"` animates
|
|
181
|
+
whole words instead of letters; `opacity: 0` (default) starts hidden for entrances.
|
|
182
|
+
- `textIn(name, block, { speed?, energy?, seed?, stagger?, label? }) → TimelineIR`
|
|
183
|
+
(a `beat`) — entrance: `typewriter`, `cascade`, `rise`, `bounce`, `assemble`
|
|
184
|
+
(fly in from a seeded scatter), `decode` (scramble through random glyphs then lock).
|
|
185
|
+
- `textLoop(name, block, { from?, until?, ramp?, amplitude?, frequency?, phaseStep? })
|
|
186
|
+
→ BehaviorIR[]` — sustained: `wave` (standing sine), `shimmer`, `wobble`, `float`.
|
|
187
|
+
Spread it into `behaviors`.
|
|
188
|
+
- `textOut(name, block, { …, dir? }) → TimelineIR` — exit: `shatter` (random
|
|
189
|
+
direction + spin + fade), `fly` (directional), `dissolve`, `fall`, `collapse`.
|
|
190
|
+
- `textTypeCues(block, { at, interval?, gain? }) → AudioCueIR[]` — per-glyph CC0
|
|
191
|
+
keypress for a typewriter entrance; spread into `audio.cues`.
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
const T = splitText("MOTION IS DATA", { id: "t", x: 960, y: 470, fontSize: 130 });
|
|
195
|
+
// nodes: [...T.nodes]
|
|
196
|
+
// timeline: seq(textIn("cascade", T), wait(2), textOut("shatter", T, { seed: 3 }))
|
|
197
|
+
// behaviors: textLoop("wave", T, { from: 1.6, until: 3.6 })
|
|
198
|
+
```
|
|
199
|
+
Every effect is seeded (same `seed` → identical) and pure keyframes. To time a
|
|
200
|
+
`textLoop` window, add up the `textIn` beat length (≈ `(n-1)·stagger + glyphDur`).
|
|
201
|
+
|
|
170
202
|
## Audio (optional)
|
|
171
203
|
|
|
172
204
|
Label-anchored sound design — cues follow retiming and regeneration:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reframe-video",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
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",
|