@motion-script/player 0.2.4 → 1.0.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/App.d.ts.map +1 -1
- package/dist/components/layout/scene-panel.d.ts.map +1 -1
- package/dist/components/timeline/constants.d.ts +1 -0
- package/dist/components/timeline/constants.d.ts.map +1 -1
- package/dist/components/timeline/timeline.d.ts.map +1 -1
- package/dist/motion-script-player.js +262 -30
- package/dist/motion-script-player.js.map +1 -1
- package/package.json +12 -11
package/dist/App.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../src/App.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAKnE,wBAAgB,SAAS,CAAC,KAAK,EAAE;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,aAAa,CAAA;CAAE,+
|
|
1
|
+
{"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../src/App.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAKnE,wBAAgB,SAAS,CAAC,KAAK,EAAE;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,aAAa,CAAA;CAAE,+BAgBlG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scene-panel.d.ts","sourceRoot":"","sources":["../../../src/components/layout/scene-panel.tsx"],"names":[],"mappings":"AAMA,wBAAgB,UAAU,
|
|
1
|
+
{"version":3,"file":"scene-panel.d.ts","sourceRoot":"","sources":["../../../src/components/layout/scene-panel.tsx"],"names":[],"mappings":"AAMA,wBAAgB,UAAU,gCA8HzB"}
|
|
@@ -9,6 +9,7 @@ export declare const RULER_HEIGHT = 28;
|
|
|
9
9
|
export declare const SCENE_ROW_HEIGHT = 28;
|
|
10
10
|
export declare const INDENT_PX = 16;
|
|
11
11
|
export declare const BAR_PADDING_Y = 5;
|
|
12
|
+
export declare const BAR_MARGIN_X = 2;
|
|
12
13
|
export declare const FRAME_MODE_PX_THRESHOLD = 8;
|
|
13
14
|
export declare const ROW_OVERSCAN = 4;
|
|
14
15
|
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/components/timeline/constants.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,mBAAmB,IAAI,CAAC;AACrC,eAAO,MAAM,oBAAoB,KAAK,CAAC;AACvC,eAAO,MAAM,oBAAoB,KAAK,CAAC;AACvC,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAC5C,eAAO,MAAM,iBAAiB,KAAK,CAAC;AACpC,eAAO,MAAM,eAAe,KAAK,CAAC;AAClC,eAAO,MAAM,eAAe,MAAM,CAAC;AACnC,eAAO,MAAM,YAAY,KAAK,CAAC;AAC/B,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,eAAO,MAAM,SAAS,KAAK,CAAC;AAC5B,eAAO,MAAM,aAAa,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/components/timeline/constants.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,mBAAmB,IAAI,CAAC;AACrC,eAAO,MAAM,oBAAoB,KAAK,CAAC;AACvC,eAAO,MAAM,oBAAoB,KAAK,CAAC;AACvC,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAC5C,eAAO,MAAM,iBAAiB,KAAK,CAAC;AACpC,eAAO,MAAM,eAAe,KAAK,CAAC;AAClC,eAAO,MAAM,eAAe,MAAM,CAAC;AACnC,eAAO,MAAM,YAAY,KAAK,CAAC;AAC/B,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,eAAO,MAAM,SAAS,KAAK,CAAC;AAC5B,eAAO,MAAM,aAAa,IAAI,CAAC;AAI/B,eAAO,MAAM,YAAY,IAAI,CAAC;AAG9B,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAGzC,eAAO,MAAM,YAAY,IAAI,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeline.d.ts","sourceRoot":"","sources":["../../../src/components/timeline/timeline.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"timeline.d.ts","sourceRoot":"","sources":["../../../src/components/timeline/timeline.tsx"],"names":[],"mappings":"AAkCA,KAAK,kBAAkB,GAAG;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,UAAgC,GACjC,EAAE,kBAAkB,+BAqgBpB"}
|
|
@@ -1563,6 +1563,17 @@ function resolveShadowArray(prop, previous) {
|
|
|
1563
1563
|
}
|
|
1564
1564
|
//#endregion
|
|
1565
1565
|
//#region ../core/dist/attributes/shape/stroke/mapper.js
|
|
1566
|
+
/** Resolve a loose `align` prop to a clamped number in [-1, 1]. */
|
|
1567
|
+
function resolveStrokeAlign(align, fallback) {
|
|
1568
|
+
if (align == null) return fallback;
|
|
1569
|
+
if (typeof align === "number") return Math.max(-1, Math.min(1, align));
|
|
1570
|
+
switch (align) {
|
|
1571
|
+
case "inside": return -1;
|
|
1572
|
+
case "center": return 0;
|
|
1573
|
+
case "outside": return 1;
|
|
1574
|
+
}
|
|
1575
|
+
return fallback;
|
|
1576
|
+
}
|
|
1566
1577
|
function resolveStroke(prop, previous) {
|
|
1567
1578
|
let dash;
|
|
1568
1579
|
if (prop.dash != null) {
|
|
@@ -1573,7 +1584,8 @@ function resolveStroke(prop, previous) {
|
|
|
1573
1584
|
weight: prop.weight ?? previous?.weight ?? 1,
|
|
1574
1585
|
fill: prop.fill != null ? resolveFill(prop.fill) : previous?.fill ?? resolveFill("transparent"),
|
|
1575
1586
|
dash,
|
|
1576
|
-
dashOffset: prop.dashOffset ?? previous?.dashOffset ?? 0
|
|
1587
|
+
dashOffset: prop.dashOffset ?? previous?.dashOffset ?? 0,
|
|
1588
|
+
align: resolveStrokeAlign(prop.align, previous?.align ?? -1)
|
|
1577
1589
|
};
|
|
1578
1590
|
}
|
|
1579
1591
|
function resolveStrokeArray(prop, previous) {
|
|
@@ -2125,6 +2137,10 @@ var AssetManager = class {
|
|
|
2125
2137
|
lastAudioRequestKey = "";
|
|
2126
2138
|
lastAudioSrcs = /* @__PURE__ */ new Set();
|
|
2127
2139
|
audioFetching = /* @__PURE__ */ new Map();
|
|
2140
|
+
/** Disposers for loaders whose load has resolved, keyed by loader key. */
|
|
2141
|
+
loaderDisposers = /* @__PURE__ */ new Map();
|
|
2142
|
+
/** In-flight loader runs, keyed by loader key, to dedupe concurrent calls. */
|
|
2143
|
+
loaderInFlight = /* @__PURE__ */ new Map();
|
|
2128
2144
|
constructor(precomp, storageAdapter, audioDevice) {
|
|
2129
2145
|
this.precomp = precomp;
|
|
2130
2146
|
this.storageAdapter = storageAdapter;
|
|
@@ -2136,7 +2152,14 @@ var AssetManager = class {
|
|
|
2136
2152
|
*/
|
|
2137
2153
|
async loadAt(frame) {
|
|
2138
2154
|
const jobs = [];
|
|
2139
|
-
for (const [key, track] of this.precomp.assets)
|
|
2155
|
+
for (const [key, track] of this.precomp.assets) {
|
|
2156
|
+
if (track.record.type === "loader") {
|
|
2157
|
+
const job = this.syncLoader(key, track, frame);
|
|
2158
|
+
if (job) jobs.push(job);
|
|
2159
|
+
continue;
|
|
2160
|
+
}
|
|
2161
|
+
if (track.cacheAt <= frame && track.record.endFrame >= frame) jobs.push(this.storageAdapter.loadAsset(key, track.record));
|
|
2162
|
+
}
|
|
2140
2163
|
await Promise.all(jobs);
|
|
2141
2164
|
this.syncAudio(frame);
|
|
2142
2165
|
}
|
|
@@ -2145,9 +2168,54 @@ var AssetManager = class {
|
|
|
2145
2168
|
* loads for assets whose cacheAt window has been reached without blocking.
|
|
2146
2169
|
*/
|
|
2147
2170
|
prefetch(frame) {
|
|
2148
|
-
for (const [key, track] of this.precomp.assets)
|
|
2149
|
-
|
|
2171
|
+
for (const [key, track] of this.precomp.assets) {
|
|
2172
|
+
if (track.record.type === "loader") {
|
|
2173
|
+
this.syncLoader(key, track, frame)?.catch((err) => {
|
|
2174
|
+
console.error(`[AssetManager] loader failed for ${key}:`, err);
|
|
2175
|
+
});
|
|
2176
|
+
continue;
|
|
2177
|
+
}
|
|
2178
|
+
if (track.cacheAt <= frame && track.record.endFrame >= frame) this.storageAdapter.loadAsset(key, track.record).catch((err) => {
|
|
2179
|
+
console.error(`[AssetManager] prefetch failed for ${key}:`, err);
|
|
2180
|
+
});
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
/**
|
|
2184
|
+
* Drive a single loader track toward the state `frame` requires. If `frame`
|
|
2185
|
+
* is inside the loader's `[cacheAt, discardAt]` window, run its callback
|
|
2186
|
+
* (deduped — at most one in-flight run, skipped once loaded) and return the
|
|
2187
|
+
* pending job, if any. If `frame` is outside the window and the loader was
|
|
2188
|
+
* loaded, dispose it. Returns the in-flight load promise so a blocking
|
|
2189
|
+
* caller can await it; `undefined` when there's nothing to wait for.
|
|
2190
|
+
*/
|
|
2191
|
+
syncLoader(key, track, frame) {
|
|
2192
|
+
if (track.record.type !== "loader") return void 0;
|
|
2193
|
+
const discardAt = track.discardAt ?? Infinity;
|
|
2194
|
+
if (!(track.cacheAt <= frame && frame <= discardAt)) {
|
|
2195
|
+
const dispose = this.loaderDisposers.get(key);
|
|
2196
|
+
if (dispose) {
|
|
2197
|
+
this.loaderDisposers.delete(key);
|
|
2198
|
+
dispose();
|
|
2199
|
+
}
|
|
2200
|
+
return;
|
|
2201
|
+
}
|
|
2202
|
+
if (this.loaderDisposers.has(key)) return void 0;
|
|
2203
|
+
const existing = this.loaderInFlight.get(key);
|
|
2204
|
+
if (existing) return existing;
|
|
2205
|
+
const load = track.record.load;
|
|
2206
|
+
const job = load().then((disposer) => {
|
|
2207
|
+
this.loaderDisposers.set(key, disposer);
|
|
2208
|
+
}).finally(() => {
|
|
2209
|
+
this.loaderInFlight.delete(key);
|
|
2150
2210
|
});
|
|
2211
|
+
this.loaderInFlight.set(key, job);
|
|
2212
|
+
return job;
|
|
2213
|
+
}
|
|
2214
|
+
/** Run every outstanding loader disposer and clear loader state. */
|
|
2215
|
+
dispose() {
|
|
2216
|
+
for (const dispose of this.loaderDisposers.values()) dispose();
|
|
2217
|
+
this.loaderDisposers.clear();
|
|
2218
|
+
this.loaderInFlight.clear();
|
|
2151
2219
|
}
|
|
2152
2220
|
/**
|
|
2153
2221
|
* Push the current audio working set to the AudioDevice. Called after each
|
|
@@ -2372,21 +2440,29 @@ var AssetTracker = class {
|
|
|
2372
2440
|
get catalog() {
|
|
2373
2441
|
return this._catalog;
|
|
2374
2442
|
}
|
|
2375
|
-
/**
|
|
2376
|
-
|
|
2377
|
-
|
|
2443
|
+
/**
|
|
2444
|
+
* Register an opaque async loader needed at the current frame, tracked on the
|
|
2445
|
+
* timeline by `key` so the {@link AssetManager} runs it once its cache window
|
|
2446
|
+
* opens and disposes it when the window closes. Deduped by `key` (like
|
|
2447
|
+
* {@link requestFont} by family): the first registration's `load` callback is
|
|
2448
|
+
* kept and its frame range extends as later frames re-request the same key.
|
|
2449
|
+
*/
|
|
2450
|
+
requestLoader(key, load) {
|
|
2451
|
+
this.upsertAsset(key, (frame) => ({
|
|
2452
|
+
type: "loader",
|
|
2453
|
+
src: key,
|
|
2454
|
+
startFrame: frame,
|
|
2455
|
+
endFrame: frame,
|
|
2456
|
+
load
|
|
2457
|
+
}));
|
|
2378
2458
|
}
|
|
2379
2459
|
/** Pending requests collected during the current frame's load pass. */
|
|
2380
2460
|
requestedAssets = /* @__PURE__ */ new Map();
|
|
2381
|
-
loaders = [];
|
|
2382
2461
|
_audioRequests = [];
|
|
2383
2462
|
_audioIds = /* @__PURE__ */ new Set();
|
|
2384
2463
|
get assets() {
|
|
2385
2464
|
return this.requestedAssets;
|
|
2386
2465
|
}
|
|
2387
|
-
get pendingLoaders() {
|
|
2388
|
-
return this.loaders;
|
|
2389
|
-
}
|
|
2390
2466
|
get audioRequests() {
|
|
2391
2467
|
return this._audioRequests;
|
|
2392
2468
|
}
|
|
@@ -2519,17 +2595,15 @@ var AssetTracker = class {
|
|
|
2519
2595
|
discardOutside(startFrame, endFrame) {
|
|
2520
2596
|
for (const [key, entry] of this.requestedAssets) if (entry.endFrame < startFrame || entry.startFrame > endFrame) this.requestedAssets.delete(key);
|
|
2521
2597
|
}
|
|
2522
|
-
/** Clear all tracked entries
|
|
2598
|
+
/** Clear all tracked entries (including loaders) without releasing the instance. */
|
|
2523
2599
|
clear() {
|
|
2524
2600
|
this.requestedAssets.clear();
|
|
2525
|
-
this.loaders.length = 0;
|
|
2526
2601
|
this._audioRequests.length = 0;
|
|
2527
2602
|
this._audioIds.clear();
|
|
2528
2603
|
}
|
|
2529
2604
|
/** Release all state; the instance should not be used after this call. */
|
|
2530
2605
|
dispose() {
|
|
2531
2606
|
this.requestedAssets.clear();
|
|
2532
|
-
this.loaders.length = 0;
|
|
2533
2607
|
this._audioRequests.length = 0;
|
|
2534
2608
|
this._audioIds.clear();
|
|
2535
2609
|
}
|
|
@@ -2717,6 +2791,13 @@ function buildAssetMap(registry, fps) {
|
|
|
2717
2791
|
discardAt: null
|
|
2718
2792
|
});
|
|
2719
2793
|
break;
|
|
2794
|
+
case "loader":
|
|
2795
|
+
out.set(key, {
|
|
2796
|
+
record: entry,
|
|
2797
|
+
cacheAt: Math.max(0, entry.startFrame - MAX_LEAD),
|
|
2798
|
+
discardAt: entry.endFrame + TAIL_FRAMES
|
|
2799
|
+
});
|
|
2800
|
+
break;
|
|
2720
2801
|
}
|
|
2721
2802
|
return out;
|
|
2722
2803
|
}
|
|
@@ -2745,6 +2826,8 @@ var PlaybackController = class {
|
|
|
2745
2826
|
stateEvaluator;
|
|
2746
2827
|
assetManager;
|
|
2747
2828
|
audioDevice;
|
|
2829
|
+
/** Set by dispose(); once true the controller must never touch its (now-freed) render context again. */
|
|
2830
|
+
disposed = false;
|
|
2748
2831
|
fps;
|
|
2749
2832
|
viewport;
|
|
2750
2833
|
precomp;
|
|
@@ -2787,6 +2870,7 @@ var PlaybackController = class {
|
|
|
2787
2870
|
const frame = this.fps * currentTime;
|
|
2788
2871
|
if (frame >= this.totalFrames) this.masterClock.pause();
|
|
2789
2872
|
await this.assetManager.loadAt(frame);
|
|
2873
|
+
if (this.disposed) return;
|
|
2790
2874
|
this.audioDevice.syncTo(currentTime);
|
|
2791
2875
|
this.renderAt(frame);
|
|
2792
2876
|
this.assetManager.prefetch(frame);
|
|
@@ -2821,6 +2905,7 @@ var PlaybackController = class {
|
|
|
2821
2905
|
* `screenshot` to ensure the surface is up-to-date.
|
|
2822
2906
|
*/
|
|
2823
2907
|
renderAt(frame) {
|
|
2908
|
+
if (this.disposed) return;
|
|
2824
2909
|
this.stateEvaluator.stateAt(frame);
|
|
2825
2910
|
this.stateEvaluator.layout(this.measureScope);
|
|
2826
2911
|
this.renderContext.render(() => {
|
|
@@ -2832,10 +2917,12 @@ var PlaybackController = class {
|
|
|
2832
2917
|
* load before rendering, then prefetches upcoming frames.
|
|
2833
2918
|
*/
|
|
2834
2919
|
async seek(frame) {
|
|
2920
|
+
if (this.disposed) return;
|
|
2835
2921
|
const clamped = Math.max(0, Math.min(frame, this.totalFrames));
|
|
2836
2922
|
this.masterClock.pause();
|
|
2837
2923
|
this.masterClock.seek(clamped / this.fps);
|
|
2838
2924
|
await this.assetManager.loadAt(clamped);
|
|
2925
|
+
if (this.disposed) return;
|
|
2839
2926
|
this.renderAt(clamped);
|
|
2840
2927
|
this.assetManager.prefetch(clamped);
|
|
2841
2928
|
}
|
|
@@ -2897,9 +2984,11 @@ var PlaybackController = class {
|
|
|
2897
2984
|
this.audioDevice.setMuted(muted);
|
|
2898
2985
|
}
|
|
2899
2986
|
dispose() {
|
|
2987
|
+
this.disposed = true;
|
|
2900
2988
|
this.masterClock.dispose();
|
|
2901
2989
|
this.audioDevice.dispose();
|
|
2902
2990
|
this.stateEvaluator.dispose();
|
|
2991
|
+
this.assetManager.dispose();
|
|
2903
2992
|
}
|
|
2904
2993
|
};
|
|
2905
2994
|
function nodeToTreeState(node, path, lifespans, sceneStart = 0) {
|
|
@@ -3084,6 +3173,7 @@ var StorageAdapter = class {
|
|
|
3084
3173
|
break;
|
|
3085
3174
|
}
|
|
3086
3175
|
case "audio": break;
|
|
3176
|
+
case "loader": throw new Error("Loader records must not be routed through StorageAdapter.loadAsset");
|
|
3087
3177
|
default: throw new Error(`Unsupported asset type: ${value.type}`);
|
|
3088
3178
|
}
|
|
3089
3179
|
this.cachedAssets.add(key);
|
|
@@ -3288,7 +3378,8 @@ function layoutParagraph(canvasKit, fontMgr, segments, opts) {
|
|
|
3288
3378
|
const paragraph = builder.build();
|
|
3289
3379
|
const wrapping = isFinite(opts.maxWidth) && opts.maxWidth > 0;
|
|
3290
3380
|
paragraph.layout(wrapping ? opts.maxWidth : 1e7);
|
|
3291
|
-
const
|
|
3381
|
+
const intrinsicWidth = Math.ceil(paragraph.getMaxIntrinsicWidth()) + 1;
|
|
3382
|
+
const layoutWidth = wrapping ? opts.maxWidth : Math.max(intrinsicWidth, opts.boxWidth ?? 0);
|
|
3292
3383
|
paragraph.layout(layoutWidth);
|
|
3293
3384
|
const blockWidth = paragraph.getLongestLine();
|
|
3294
3385
|
const blockHeight = paragraph.getHeight();
|
|
@@ -3381,6 +3472,7 @@ function layoutRichText(canvasKit, fontMgr, state) {
|
|
|
3381
3472
|
align,
|
|
3382
3473
|
lineHeight,
|
|
3383
3474
|
maxWidth: width > 0 ? width : Infinity,
|
|
3475
|
+
boxWidth: width,
|
|
3384
3476
|
originX: 0,
|
|
3385
3477
|
originY: 0
|
|
3386
3478
|
});
|
|
@@ -3983,7 +4075,7 @@ function computeImageMatrix(imgW, imgH, fill, bounds) {
|
|
|
3983
4075
|
return fill.transform.flat();
|
|
3984
4076
|
}
|
|
3985
4077
|
if (bounds) {
|
|
3986
|
-
const mode = fill.mode ?? "
|
|
4078
|
+
const mode = fill.mode ?? "fit";
|
|
3987
4079
|
const shapeW = bounds.right - bounds.left;
|
|
3988
4080
|
const shapeH = bounds.bottom - bounds.top;
|
|
3989
4081
|
let sx, sy, tx, ty;
|
|
@@ -4037,7 +4129,7 @@ function computeImageMatrix(imgW, imgH, fill, bounds) {
|
|
|
4037
4129
|
}
|
|
4038
4130
|
/** Shared image-shader builder used by both image and video renderers. */
|
|
4039
4131
|
function makeImageShader(img, fill, ck, bounds) {
|
|
4040
|
-
const mode = fill.mode ?? "
|
|
4132
|
+
const mode = fill.mode ?? "fit";
|
|
4041
4133
|
const tileMode = mode === "tile" ? ck.TileMode.Repeat : mode === "fit" ? ck.TileMode.Decal : ck.TileMode.Clamp;
|
|
4042
4134
|
const matrix = computeImageMatrix(img.width(), img.height(), fill, bounds);
|
|
4043
4135
|
return img.makeShaderOptions(tileMode, tileMode, ck.FilterMode.Linear, ck.MipmapMode.None, matrix);
|
|
@@ -6221,10 +6313,11 @@ var StrokeHandler = class {
|
|
|
6221
6313
|
}
|
|
6222
6314
|
const hasDash = !!(stroke.dash && stroke.dash.length > 0);
|
|
6223
6315
|
const dashFit = hasDash && shape.ckPath ? this.measureDashFit(shape.ckPath, stroke.dash) : null;
|
|
6316
|
+
const align = stroke.align ?? 0;
|
|
6224
6317
|
if (intDeviceWidth > 0 && shape.ckPath) {
|
|
6225
6318
|
const source = hasDash ? this.buildDashedPath(shape.ckPath, stroke.dash, stroke.dashOffset ?? 0, dashFit ?? void 0) : shape.ckPath;
|
|
6226
6319
|
if (source) {
|
|
6227
|
-
const filled =
|
|
6320
|
+
const filled = this.alignedStrokeBand(source, logicalWidth, align, shape.ckPath);
|
|
6228
6321
|
if (source !== shape.ckPath) source.delete();
|
|
6229
6322
|
if (filled) {
|
|
6230
6323
|
paint.setStyle(this.canvasKit.PaintStyle.Fill);
|
|
@@ -6235,11 +6328,22 @@ var StrokeHandler = class {
|
|
|
6235
6328
|
}
|
|
6236
6329
|
}
|
|
6237
6330
|
}
|
|
6331
|
+
if (align !== 0 && !hasDash && shape.ckPath) {
|
|
6332
|
+
const band = this.alignedStrokeBand(shape.ckPath, logicalWidth, align, shape.ckPath);
|
|
6333
|
+
if (band) {
|
|
6334
|
+
paint.setStyle(this.canvasKit.PaintStyle.Fill);
|
|
6335
|
+
canvas.drawPath(band, paint);
|
|
6336
|
+
paint.setStyle(this.canvasKit.PaintStyle.Stroke);
|
|
6337
|
+
band.delete();
|
|
6338
|
+
return;
|
|
6339
|
+
}
|
|
6340
|
+
}
|
|
6238
6341
|
if (hasDash && shape.ckPath) {
|
|
6239
6342
|
const effect = this.makeUniformDashEffect(shape.ckPath, stroke.dash, stroke.dashOffset ?? 0, dashFit ?? void 0);
|
|
6240
6343
|
if (effect) {
|
|
6241
6344
|
paint.setPathEffect(effect);
|
|
6242
|
-
|
|
6345
|
+
if (align !== 0 && intDeviceWidth <= 0 && this.isClosedPath(shape.ckPath)) this.drawAlignedDashedStroke(canvas, paint, shape.ckPath, align);
|
|
6346
|
+
else canvas.drawPath(shape.ckPath, paint);
|
|
6243
6347
|
paint.setPathEffect(null);
|
|
6244
6348
|
effect.delete();
|
|
6245
6349
|
return;
|
|
@@ -6247,6 +6351,42 @@ var StrokeHandler = class {
|
|
|
6247
6351
|
}
|
|
6248
6352
|
shape.draw(paint);
|
|
6249
6353
|
}
|
|
6354
|
+
alignedStrokeBand(source, width, align, interior) {
|
|
6355
|
+
if (align === 0 || !this.isClosedPath(interior)) return source.makeStroked({ width });
|
|
6356
|
+
const widened = width * (1 + Math.abs(align));
|
|
6357
|
+
const band = source.makeStroked({ width: widened });
|
|
6358
|
+
if (!band) return null;
|
|
6359
|
+
const op = align < 0 ? this.canvasKit.PathOp.Intersect : this.canvasKit.PathOp.Difference;
|
|
6360
|
+
const clipped = this.canvasKit.Path.MakeFromOp(band, interior, op);
|
|
6361
|
+
band.delete();
|
|
6362
|
+
return clipped;
|
|
6363
|
+
}
|
|
6364
|
+
isClosedPath(path) {
|
|
6365
|
+
const iter = new this.canvasKit.ContourMeasureIter(path, false, 1);
|
|
6366
|
+
let contour = iter.next();
|
|
6367
|
+
if (!contour) {
|
|
6368
|
+
iter.delete();
|
|
6369
|
+
return false;
|
|
6370
|
+
}
|
|
6371
|
+
let allClosed = true;
|
|
6372
|
+
while (contour) {
|
|
6373
|
+
if (!contour.isClosed()) allClosed = false;
|
|
6374
|
+
contour.delete();
|
|
6375
|
+
contour = iter.next();
|
|
6376
|
+
}
|
|
6377
|
+
iter.delete();
|
|
6378
|
+
return allClosed;
|
|
6379
|
+
}
|
|
6380
|
+
drawAlignedDashedStroke(canvas, paint, interior, align) {
|
|
6381
|
+
canvas.save();
|
|
6382
|
+
const baseWidth = paint.getStrokeWidth();
|
|
6383
|
+
paint.setStrokeWidth(baseWidth * 2);
|
|
6384
|
+
const op = align < 0 ? this.canvasKit.ClipOp.Intersect : this.canvasKit.ClipOp.Difference;
|
|
6385
|
+
canvas.clipPath(interior, op, true);
|
|
6386
|
+
canvas.drawPath(interior, paint);
|
|
6387
|
+
canvas.restore();
|
|
6388
|
+
paint.setStrokeWidth(baseWidth);
|
|
6389
|
+
}
|
|
6250
6390
|
drawTextUnionStroke(canvas, paint, shape, stroke, logicalWidth) {
|
|
6251
6391
|
const hasDash = !!(stroke.dash && stroke.dash.length > 0);
|
|
6252
6392
|
let dashEffect = null;
|
|
@@ -6372,6 +6512,7 @@ function buildText(canvasKit, canvas, fontMgr, state) {
|
|
|
6372
6512
|
align: fullState.align,
|
|
6373
6513
|
lineHeight: fullState.lineHeight,
|
|
6374
6514
|
maxWidth: wrap ? fullState.width : Infinity,
|
|
6515
|
+
boxWidth: fullState.width,
|
|
6375
6516
|
originX: x,
|
|
6376
6517
|
originY: y
|
|
6377
6518
|
});
|
|
@@ -6749,10 +6890,8 @@ var ShapeHandler = class {
|
|
|
6749
6890
|
});
|
|
6750
6891
|
}
|
|
6751
6892
|
}
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
if (shape.ckPath) this.boolean.contributeToPathCollection(shape.ckPath);
|
|
6755
|
-
}
|
|
6893
|
+
shape.ensurePath();
|
|
6894
|
+
if (!isolated && shape.ckPath) this.boolean.contributeToPathCollection(shape.ckPath);
|
|
6756
6895
|
this.shapes.push(shape.toCurrentShape(isolated));
|
|
6757
6896
|
}
|
|
6758
6897
|
rect(state) {
|
|
@@ -7059,6 +7198,7 @@ var WebRenderContext = class extends RenderContext {
|
|
|
7059
7198
|
}
|
|
7060
7199
|
pixelRatio = 1;
|
|
7061
7200
|
renderPass(callback) {
|
|
7201
|
+
if (!this.mounted || !this.surface) return;
|
|
7062
7202
|
this.currentCanvas = this.surface.getCanvas();
|
|
7063
7203
|
this.currentCanvas.clear(this.canvasKit.BLACK);
|
|
7064
7204
|
this.currentCanvas.save();
|
|
@@ -7824,6 +7964,10 @@ var WebImagePaintContext = class {
|
|
|
7824
7964
|
};
|
|
7825
7965
|
//#endregion
|
|
7826
7966
|
//#region ../web/dist/storage-adapter.js
|
|
7967
|
+
/** Whether a src points at an SVG (by extension, ignoring any query/hash). */
|
|
7968
|
+
function isSvgSrc(src) {
|
|
7969
|
+
return /\.svg(?:[?#]|$)/i.test(src);
|
|
7970
|
+
}
|
|
7827
7971
|
/**
|
|
7828
7972
|
* Browser implementation of {@link StorageAdapter} — owns all async asset
|
|
7829
7973
|
* decoding (images, video, audio, fonts) so the render loop can stay
|
|
@@ -7863,9 +8007,19 @@ var WebStorageAdapter = class extends StorageAdapter {
|
|
|
7863
8007
|
getCanvasKit() {
|
|
7864
8008
|
return this.canvasKit;
|
|
7865
8009
|
}
|
|
7866
|
-
/**
|
|
8010
|
+
/**
|
|
8011
|
+
* Fetches and decodes `src` to RGBA pixels (cached for later GPU upload via
|
|
8012
|
+
* {@link getCKImage}), no-op if already cached. SVGs are rasterized at the
|
|
8013
|
+
* on-screen target size so they stay crisp when scaled up (see
|
|
8014
|
+
* {@link rasterizeSvg}); raster formats decode via `createImageBitmap`,
|
|
8015
|
+
* downscaled to target when that saves memory.
|
|
8016
|
+
*/
|
|
7867
8017
|
async loadImage(src, width, height) {
|
|
7868
8018
|
if (this.imagePixels.has(src)) return;
|
|
8019
|
+
if (isSvgSrc(src)) {
|
|
8020
|
+
this.imagePixels.set(src, await this.rasterizeSvg(src, width, height));
|
|
8021
|
+
return;
|
|
8022
|
+
}
|
|
7869
8023
|
const target = this.imageTargetPixels(src, width, height);
|
|
7870
8024
|
const blob = await (await fetch(src)).blob();
|
|
7871
8025
|
const bitmap = target ? await createImageBitmap(blob, {
|
|
@@ -7888,6 +8042,35 @@ var WebStorageAdapter = class extends StorageAdapter {
|
|
|
7888
8042
|
pixels: new Uint8Array(data)
|
|
7889
8043
|
});
|
|
7890
8044
|
}
|
|
8045
|
+
/**
|
|
8046
|
+
* Rasterize an SVG to RGBA pixels. Unlike raster formats, SVG is vector, so
|
|
8047
|
+
* we render it at the size it's *drawn* on screen rather than its nominal
|
|
8048
|
+
* `viewBox` size — a 48×48 logo placed in a 512px box must rasterize at
|
|
8049
|
+
* ~512px (× devicePixelRatio) to stay sharp. `createImageBitmap` can't
|
|
8050
|
+
* decode SVG reliably across browsers (Chrome throws on an SVG Blob), so we
|
|
8051
|
+
* decode through an `HTMLImageElement`, which every browser supports.
|
|
8052
|
+
*/
|
|
8053
|
+
async rasterizeSvg(src, width, height) {
|
|
8054
|
+
const text = await (await fetch(src)).text();
|
|
8055
|
+
const url = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(text)}`;
|
|
8056
|
+
const img = new Image();
|
|
8057
|
+
img.decoding = "sync";
|
|
8058
|
+
await new Promise((resolve, reject) => {
|
|
8059
|
+
img.onload = () => resolve();
|
|
8060
|
+
img.onerror = () => reject(/* @__PURE__ */ new Error(`loadImage(${src}): failed to decode SVG`));
|
|
8061
|
+
img.src = url;
|
|
8062
|
+
});
|
|
8063
|
+
const target = this.svgTargetPixels(width, height, img.naturalWidth, img.naturalHeight);
|
|
8064
|
+
const ctx = new OffscreenCanvas(target.width, target.height).getContext("2d", { willReadFrequently: true });
|
|
8065
|
+
if (!ctx) throw new Error(`loadImage(${src}): could not get 2d context`);
|
|
8066
|
+
ctx.drawImage(img, 0, 0, target.width, target.height);
|
|
8067
|
+
const data = ctx.getImageData(0, 0, target.width, target.height).data;
|
|
8068
|
+
return {
|
|
8069
|
+
width: target.width,
|
|
8070
|
+
height: target.height,
|
|
8071
|
+
pixels: new Uint8Array(data)
|
|
8072
|
+
};
|
|
8073
|
+
}
|
|
7891
8074
|
imageTargetPixels(src, width, height) {
|
|
7892
8075
|
let meta;
|
|
7893
8076
|
try {
|
|
@@ -7908,6 +8091,33 @@ var WebStorageAdapter = class extends StorageAdapter {
|
|
|
7908
8091
|
height: Math.max(1, Math.round(meta.height * ratio))
|
|
7909
8092
|
};
|
|
7910
8093
|
}
|
|
8094
|
+
/**
|
|
8095
|
+
* Pick the raster size for an SVG, *preserving its intrinsic aspect ratio*.
|
|
8096
|
+
* The decoded image must keep the SVG's true proportions so the downstream
|
|
8097
|
+
* fit/crop/fill shader matrix (see computeImageMatrix) can position it like
|
|
8098
|
+
* any raster image — if we instead stretched the SVG to the layout box here,
|
|
8099
|
+
* its aspect ratio would already be baked in and `fit` would be a no-op.
|
|
8100
|
+
*
|
|
8101
|
+
* We compute one uniform scale: how big the SVG renders on screen (the
|
|
8102
|
+
* larger of the box's two dimensions, since `fit` may letterbox but `crop`
|
|
8103
|
+
* may overflow), times devicePixelRatio so it stays sharp when zoomed in,
|
|
8104
|
+
* clamped to the viewport so a huge box can't blow up memory. That factor is
|
|
8105
|
+
* applied to the intrinsic width/height, keeping proportions intact.
|
|
8106
|
+
*/
|
|
8107
|
+
svgTargetPixels(width, height, naturalWidth, naturalHeight) {
|
|
8108
|
+
const intrinsicW = naturalWidth > 0 ? naturalWidth : 300;
|
|
8109
|
+
const intrinsicH = naturalHeight > 0 ? naturalHeight : 300;
|
|
8110
|
+
const dpr = typeof devicePixelRatio === "number" && devicePixelRatio > 0 ? devicePixelRatio : 1;
|
|
8111
|
+
const boxW = width > 0 ? Math.min(width, this.viewport.width) : 0;
|
|
8112
|
+
const boxH = height > 0 ? Math.min(height, this.viewport.height) : 0;
|
|
8113
|
+
const onScreen = Math.max(boxW, boxH);
|
|
8114
|
+
const longestIntrinsic = Math.max(intrinsicW, intrinsicH);
|
|
8115
|
+
const scale = onScreen > 0 ? onScreen * dpr / longestIntrinsic : 1;
|
|
8116
|
+
return {
|
|
8117
|
+
width: Math.max(1, Math.round(intrinsicW * scale)),
|
|
8118
|
+
height: Math.max(1, Math.round(intrinsicH * scale))
|
|
8119
|
+
};
|
|
8120
|
+
}
|
|
7911
8121
|
/** Lazily uploads cached pixels for `url` to a GPU-resident CanvasKit image, memoizing the result; returns null until {@link loadImage} has completed. */
|
|
7912
8122
|
getCKImage(url) {
|
|
7913
8123
|
const cached = this.imagePixels.get(url);
|
|
@@ -37249,8 +37459,8 @@ function TrackRows({ nodes, window, fullContentWidth, effectiveScrollLeft, paddi
|
|
|
37249
37459
|
children: node.waveform.map((clip, ci) => {
|
|
37250
37460
|
const startFrame = sceneOffset + clip.startTime * fps;
|
|
37251
37461
|
const endFrame = clip.endTime != null ? sceneOffset + clip.endTime * fps : totalFrameCount;
|
|
37252
|
-
const startPx = paddingX + startFrame * computedPxPerUnit;
|
|
37253
|
-
const barWidth = Math.max(4, (endFrame - startFrame) * computedPxPerUnit);
|
|
37462
|
+
const startPx = paddingX + startFrame * computedPxPerUnit + 2;
|
|
37463
|
+
const barWidth = Math.max(4, (endFrame - startFrame) * computedPxPerUnit - 4);
|
|
37254
37464
|
return /* @__PURE__ */ jsx("div", {
|
|
37255
37465
|
className: "absolute rounded-xs overflow-hidden bg-card",
|
|
37256
37466
|
style: {
|
|
@@ -37273,8 +37483,8 @@ function TrackRows({ nodes, window, fullContentWidth, effectiveScrollLeft, paddi
|
|
|
37273
37483
|
const hasSpan = node.startFrame != null && node.endFrame != null;
|
|
37274
37484
|
const spanStart = hasSpan ? node.startFrame : 0;
|
|
37275
37485
|
const spanFrames = hasSpan ? node.endFrame - node.startFrame + 1 : totalFrameCount;
|
|
37276
|
-
const startPx = paddingX + spanStart * computedPxPerUnit;
|
|
37277
|
-
const barWidth = Math.max(4, spanFrames * computedPxPerUnit);
|
|
37486
|
+
const startPx = paddingX + spanStart * computedPxPerUnit + 2;
|
|
37487
|
+
const barWidth = Math.max(4, spanFrames * computedPxPerUnit - 4);
|
|
37278
37488
|
return /* @__PURE__ */ jsx("div", {
|
|
37279
37489
|
className: "border-b border-border/40",
|
|
37280
37490
|
style: {
|
|
@@ -37715,12 +37925,12 @@ function TimelineRuler({ width, minorTicks = 0 }) {
|
|
|
37715
37925
|
const endFrame = sceneStartFrames[i + 1] ?? totalFrameCount;
|
|
37716
37926
|
const isActive = i === activeSceneIndex;
|
|
37717
37927
|
const sceneName = scenes[i]?.name ?? `Scene ${i + 1}`;
|
|
37718
|
-
const barWidth = Math.max(4, (endFrame - startFrame) * computedPxPerUnit);
|
|
37928
|
+
const barWidth = Math.max(4, (endFrame - startFrame) * computedPxPerUnit - 4);
|
|
37719
37929
|
return /* @__PURE__ */ jsx("div", {
|
|
37720
37930
|
title: sceneName,
|
|
37721
37931
|
className: `group/scenebar absolute rounded-xs overflow-hidden flex items-center px-1.5 ${isActive ? "bg-primary/70" : "bg-card"}`,
|
|
37722
37932
|
style: {
|
|
37723
|
-
left: paddingX + startFrame * computedPxPerUnit - effectiveScrollLeft,
|
|
37933
|
+
left: paddingX + startFrame * computedPxPerUnit - effectiveScrollLeft + 2,
|
|
37724
37934
|
top: 5,
|
|
37725
37935
|
width: barWidth,
|
|
37726
37936
|
height: 18
|
|
@@ -38338,6 +38548,28 @@ function ScenePanel() {
|
|
|
38338
38548
|
className: "hover:underline",
|
|
38339
38549
|
children: "Documentation"
|
|
38340
38550
|
})]
|
|
38551
|
+
}),
|
|
38552
|
+
/* @__PURE__ */ jsxs("a", {
|
|
38553
|
+
href: "https://github.com/motion-script/motion-script/issues",
|
|
38554
|
+
target: "_blank",
|
|
38555
|
+
rel: "noopener noreferrer",
|
|
38556
|
+
className: "flex items-center gap-2.5 px-3 py-3 border-t shrink-0 text-sm font-medium text-muted-foreground bg-muted/30 hover:bg-muted/50 hover:text-foreground transition-colors",
|
|
38557
|
+
children: [/* @__PURE__ */ jsx("svg", {
|
|
38558
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
38559
|
+
fill: "none",
|
|
38560
|
+
viewBox: "0 0 24 24",
|
|
38561
|
+
strokeWidth: 1.5,
|
|
38562
|
+
stroke: "currentColor",
|
|
38563
|
+
className: "size-6 shrink-0",
|
|
38564
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
38565
|
+
strokeLinecap: "round",
|
|
38566
|
+
strokeLinejoin: "round",
|
|
38567
|
+
d: "M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z"
|
|
38568
|
+
})
|
|
38569
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
38570
|
+
className: "hover:underline",
|
|
38571
|
+
children: "Report New Issue"
|
|
38572
|
+
})]
|
|
38341
38573
|
})
|
|
38342
38574
|
]
|
|
38343
38575
|
}), /* @__PURE__ */ jsx(ErrorsDialog, {
|