@mcut/compositor 0.1.0-alpha.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/LICENSE +187 -0
- package/README.md +11 -0
- package/dist/index.d.ts +507 -0
- package/dist/index.js +2647 -0
- package/package.json +53 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
import { AssetId, BlendMode, CaptionStyle, Effect, Project, TextBox, TextRun, TextStyle, TimelineElement, Track, Transform, TransitionPair } from "@mcut/timeline";
|
|
2
|
+
|
|
3
|
+
//#region src/geometry.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Element coordinates are center-origin: (0, 0) is the canvas center and the
|
|
6
|
+
* element is anchored at its own center. This converts to canvas pixels.
|
|
7
|
+
*/
|
|
8
|
+
declare function toCanvasPoint(project: Project, x: number, y: number): {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
};
|
|
12
|
+
declare function fromCanvasPoint(project: Project, x: number, y: number): {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
};
|
|
16
|
+
/** Oriented bounding box in canvas pixels. Rotation in degrees, clockwise. */
|
|
17
|
+
interface OBB {
|
|
18
|
+
cx: number;
|
|
19
|
+
cy: number;
|
|
20
|
+
width: number;
|
|
21
|
+
height: number;
|
|
22
|
+
rotation: number;
|
|
23
|
+
}
|
|
24
|
+
interface ElementSize {
|
|
25
|
+
width: number;
|
|
26
|
+
height: number;
|
|
27
|
+
}
|
|
28
|
+
declare const degToRad: (deg: number) => number;
|
|
29
|
+
interface SizeHelpers {
|
|
30
|
+
/** Natural pixel size of a media asset (probed metadata). */
|
|
31
|
+
getAssetSize?: (assetId: string) => ElementSize | null;
|
|
32
|
+
/** Measure a text block (unscaled). Required for text element bounds. */
|
|
33
|
+
measureText?: (text: string, style: TextStyle, box?: TextBox, runs?: readonly TextRun[]) => ElementSize;
|
|
34
|
+
}
|
|
35
|
+
declare function getElementNaturalSize(element: TimelineElement, helpers?: SizeHelpers): ElementSize | null;
|
|
36
|
+
declare function getElementDisplaySize(element: TimelineElement, helpers?: SizeHelpers): ElementSize | null;
|
|
37
|
+
interface DisplaySizePatch {
|
|
38
|
+
width?: number;
|
|
39
|
+
height?: number;
|
|
40
|
+
preserveAspect?: boolean;
|
|
41
|
+
}
|
|
42
|
+
declare function getTransformForDisplaySize(transform: Transform, natural: ElementSize, patch: DisplaySizePatch): Transform;
|
|
43
|
+
/**
|
|
44
|
+
* The element's oriented bounding box on the canvas, or `null` when its size
|
|
45
|
+
* is unknown (e.g. unprobed media without a frame yet). Captions are
|
|
46
|
+
* positioned by their style band and are not transformable; they have no OBB.
|
|
47
|
+
*/
|
|
48
|
+
declare function getElementOBB(project: Project, element: TimelineElement, helpers?: SizeHelpers): OBB | null;
|
|
49
|
+
/** Is the canvas-space point inside the (rotated) box? */
|
|
50
|
+
declare function hitTestOBB(obb: OBB, x: number, y: number): boolean;
|
|
51
|
+
type HandleId = 'nw' | 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' | 'rotate';
|
|
52
|
+
interface Handle {
|
|
53
|
+
id: HandleId;
|
|
54
|
+
/** Canvas-space position. */
|
|
55
|
+
x: number;
|
|
56
|
+
y: number;
|
|
57
|
+
}
|
|
58
|
+
/** Distance of the rotate handle above the box's top edge (canvas px). */
|
|
59
|
+
declare const ROTATE_HANDLE_OFFSET = 32;
|
|
60
|
+
/** The 8 resize handles plus the rotate handle, in canvas space. */
|
|
61
|
+
declare function getHandles(obb: OBB): Handle[];
|
|
62
|
+
/** Hit-test the handles (square hit area of `size` px around each). */
|
|
63
|
+
declare function hitTestHandles(obb: OBB, x: number, y: number, size?: number): HandleId | null;
|
|
64
|
+
/**
|
|
65
|
+
* "Contain" scale factor for fitting a `width`×`height` media into the
|
|
66
|
+
* project frame (used when inserting media elements).
|
|
67
|
+
*/
|
|
68
|
+
declare function getFitScale(project: Project, width: number, height: number): number;
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/types.d.ts
|
|
71
|
+
/** A 2D context we can composite into (on- or off-screen). */
|
|
72
|
+
type Canvas2D = CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
|
|
73
|
+
/**
|
|
74
|
+
* Supplies pixel data for media assets at a given source time. The preview
|
|
75
|
+
* implementation is allowed to be approximate (pooled `<video>` elements);
|
|
76
|
+
* the export implementation must be exact (decoded samples). Keeping this
|
|
77
|
+
* behind an interface is what lets one compositor serve both.
|
|
78
|
+
*/
|
|
79
|
+
interface FrameSource {
|
|
80
|
+
/**
|
|
81
|
+
* Image for `assetId` at `sourceTimeMs` (media-local time, after trim).
|
|
82
|
+
* Return `null` when no frame is available yet; the compositor skips it.
|
|
83
|
+
*/
|
|
84
|
+
getFrame(assetId: AssetId, sourceTimeMs: number): CanvasImageSource | null;
|
|
85
|
+
}
|
|
86
|
+
interface RenderFrameOptions {
|
|
87
|
+
/** Pixel source for video/image assets. Omit to render only vector elements. */
|
|
88
|
+
source?: FrameSource;
|
|
89
|
+
/** Canvas background. Default black. */
|
|
90
|
+
backgroundColor?: string;
|
|
91
|
+
/**
|
|
92
|
+
* Elements to leave out of the frame — e.g. a text element while a DOM
|
|
93
|
+
* inline editor is overlaid on it (the editor IS its WYSIWYG render).
|
|
94
|
+
*/
|
|
95
|
+
skipElementIds?: ReadonlySet<string>;
|
|
96
|
+
/**
|
|
97
|
+
* Sub-frame passes for per-element motion blur (see `motion-blur.ts`).
|
|
98
|
+
* Default 8; export passes 16.
|
|
99
|
+
*/
|
|
100
|
+
motionBlurSamples?: number;
|
|
101
|
+
/**
|
|
102
|
+
* Scratch surface factory for motion-blur accumulation; the context is
|
|
103
|
+
* cleared before each use. Defaults to a cached `OffscreenCanvas` —
|
|
104
|
+
* environments without one (and tests) inject this. Return null to
|
|
105
|
+
* disable motion blur.
|
|
106
|
+
*/
|
|
107
|
+
createScratchContext?: (width: number, height: number) => Canvas2D | null;
|
|
108
|
+
}
|
|
109
|
+
interface ElementRenderContext {
|
|
110
|
+
/**
|
|
111
|
+
* Frame-space canvas2d context for raster drawing (text, chrome, custom
|
|
112
|
+
* renderers). On the canvas2d backend this is the target itself; on GPU
|
|
113
|
+
* backends it is a scratch surface composited in z-order. Reading it marks
|
|
114
|
+
* the raster surface in use — renderers with a structured fast path (video
|
|
115
|
+
* /image quads) should draw via `backend` instead.
|
|
116
|
+
*/
|
|
117
|
+
ctx: Canvas2D;
|
|
118
|
+
/** The draw layer (see backend.ts) — structured fast paths live here. */
|
|
119
|
+
backend: RenderBackend;
|
|
120
|
+
project: Project;
|
|
121
|
+
track: Track;
|
|
122
|
+
/** Absolute timeline time being rendered. */
|
|
123
|
+
timeMs: number;
|
|
124
|
+
source: FrameSource | undefined;
|
|
125
|
+
}
|
|
126
|
+
type ElementRenderer<E extends TimelineElement = TimelineElement> = (element: E, context: ElementRenderContext) => void;
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/backend.d.ts
|
|
129
|
+
/**
|
|
130
|
+
* The draw layer renderFrame composites through. The track/element walk,
|
|
131
|
+
* transition pairing, and geometry in render-frame.ts are renderer-agnostic;
|
|
132
|
+
* backends own how pixels actually land:
|
|
133
|
+
*
|
|
134
|
+
* - {@link Canvas2DBackend} — the original canvas2d path. The reference
|
|
135
|
+
* implementation: tests run against it (FakeContext2D), and headless/Node
|
|
136
|
+
* environments use it. Not a runtime fallback once WebGPU is the default.
|
|
137
|
+
* - WebGPUBackend (webgpu/) — image quads composite as textured passes with
|
|
138
|
+
* WGSL effects; raster content (text, captions, multicam chrome, custom
|
|
139
|
+
* renderers) still paints through canvas2d in frame space and uploads as
|
|
140
|
+
* a texture layer, preserving output across backends.
|
|
141
|
+
*
|
|
142
|
+
* Determinism contract: for a given backend, the same project, time, and
|
|
143
|
+
* frame source produce the same pixels in preview and export. Across
|
|
144
|
+
* backends, output is perceptually identical (GPU float math differs at the
|
|
145
|
+
* ULP level), so golden tests compare with tolerance, not byte equality.
|
|
146
|
+
*/
|
|
147
|
+
/** Resolved element chrome: transform in frame coords + compositing state. */
|
|
148
|
+
interface LayerChrome {
|
|
149
|
+
/** Element center in canvas coordinates. */
|
|
150
|
+
centerX: number;
|
|
151
|
+
centerY: number;
|
|
152
|
+
rotationDeg: number;
|
|
153
|
+
scaleX: number;
|
|
154
|
+
scaleY: number;
|
|
155
|
+
/** 0..1, multiplied into the layer. */
|
|
156
|
+
opacity: number;
|
|
157
|
+
blendMode?: BlendMode | undefined;
|
|
158
|
+
effects?: readonly Effect[] | undefined;
|
|
159
|
+
}
|
|
160
|
+
/** A plain media draw: one image into a centered rect, optional crop/radius. */
|
|
161
|
+
interface ImageQuad {
|
|
162
|
+
image: CanvasImageSource;
|
|
163
|
+
/** Source crop rect in image pixels; null draws the whole image. */
|
|
164
|
+
src: {
|
|
165
|
+
sx: number;
|
|
166
|
+
sy: number;
|
|
167
|
+
sw: number;
|
|
168
|
+
sh: number;
|
|
169
|
+
} | null;
|
|
170
|
+
/** Destination size, centered on the chrome's origin. */
|
|
171
|
+
dw: number;
|
|
172
|
+
dh: number;
|
|
173
|
+
/** Rounded-corner radius in destination pixels (0 = sharp). */
|
|
174
|
+
cornerRadius: number;
|
|
175
|
+
}
|
|
176
|
+
interface RenderBackend {
|
|
177
|
+
readonly kind: 'canvas2d' | 'webgpu' | (string & {});
|
|
178
|
+
readonly width: number;
|
|
179
|
+
readonly height: number;
|
|
180
|
+
beginFrame(backgroundColor: string): void;
|
|
181
|
+
endFrame(): void;
|
|
182
|
+
/**
|
|
183
|
+
* The frame-space canvas2d context for raster content. Acquiring it marks
|
|
184
|
+
* the raster surface dirty so the backend composites it in z-order;
|
|
185
|
+
* renderers that only need it for text measurement still go through here
|
|
186
|
+
* (text rasterizes anyway).
|
|
187
|
+
*/
|
|
188
|
+
acquireRaster(): Canvas2D;
|
|
189
|
+
/**
|
|
190
|
+
* Composite a media quad with chrome — the GPU fast path. Backends without
|
|
191
|
+
* one (or layers a backend cannot run, e.g. raw `css` filters on WebGPU)
|
|
192
|
+
* draw it through the raster context instead.
|
|
193
|
+
*/
|
|
194
|
+
drawImageQuad(quad: ImageQuad, chrome: LayerChrome): void;
|
|
195
|
+
/**
|
|
196
|
+
* While a raster scope is open, every draw — including image quads — lands
|
|
197
|
+
* on the raster context, so canvas2d state (clips, alpha, transforms) set
|
|
198
|
+
* by the caller applies. Transition mixes and motion-blur accumulation
|
|
199
|
+
* need this. Scopes nest.
|
|
200
|
+
*/
|
|
201
|
+
pushRasterScope(): void;
|
|
202
|
+
popRasterScope(): void;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Transform + opacity + effect-stack filter + blend mode around a canvas2d
|
|
206
|
+
* draw (the original withTransform). `ctx.filter` is unsupported in some
|
|
207
|
+
* older engines; there the stack degrades to an unfiltered render rather
|
|
208
|
+
* than failing.
|
|
209
|
+
*/
|
|
210
|
+
declare function applyChrome(ctx: Canvas2D, chrome: LayerChrome, draw: () => void): void;
|
|
211
|
+
/** Draw an {@link ImageQuad} in local (chrome-applied) coordinates. */
|
|
212
|
+
declare function drawImageQuad2D(ctx: Canvas2D, quad: ImageQuad): void;
|
|
213
|
+
/** The canvas2d backend: draws straight into the target context. */
|
|
214
|
+
declare class Canvas2DBackend implements RenderBackend {
|
|
215
|
+
private readonly ctx;
|
|
216
|
+
readonly width: number;
|
|
217
|
+
readonly height: number;
|
|
218
|
+
readonly kind = "canvas2d";
|
|
219
|
+
constructor(ctx: Canvas2D, width: number, height: number);
|
|
220
|
+
beginFrame(backgroundColor: string): void;
|
|
221
|
+
endFrame(): void;
|
|
222
|
+
acquireRaster(): Canvas2D;
|
|
223
|
+
drawImageQuad(quad: ImageQuad, chrome: LayerChrome): void;
|
|
224
|
+
pushRasterScope(): void;
|
|
225
|
+
popRasterScope(): void;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Build the per-element render context. `ctx` is a getter so backends learn
|
|
229
|
+
* when raster content is actually being drawn (GPU backends flush the
|
|
230
|
+
* raster surface lazily, in z-order).
|
|
231
|
+
*/
|
|
232
|
+
declare function createElementContext(backend: RenderBackend, project: Project, track: Track, timeMs: number, source: FrameSource | undefined): ElementRenderContext;
|
|
233
|
+
//#endregion
|
|
234
|
+
//#region src/render-frame.d.ts
|
|
235
|
+
/**
|
|
236
|
+
* Render one frame of `project` at `timeMs` into `ctx` (canvas2d path).
|
|
237
|
+
*
|
|
238
|
+
* Pure with respect to inputs: the same project, time, and frame source
|
|
239
|
+
* produce the same pixels — which is what makes the export path
|
|
240
|
+
* deterministic. The context is expected to be in project coordinates
|
|
241
|
+
* (`project.width` × `project.height`); callers rendering at other sizes
|
|
242
|
+
* apply their own transform before calling.
|
|
243
|
+
*/
|
|
244
|
+
declare function renderFrame(ctx: Canvas2D, project: Project, timeMs: number, options?: RenderFrameOptions): void;
|
|
245
|
+
/**
|
|
246
|
+
* Render one frame through an explicit {@link RenderBackend}. The
|
|
247
|
+
* track/element walk, transition pairing, and animation resolution here are
|
|
248
|
+
* backend-agnostic; only the draws differ.
|
|
249
|
+
*
|
|
250
|
+
* Tracks render bottom-up (index 0 first), elements in start order. Clips
|
|
251
|
+
* joined by a transition render through the blend instead of the normal
|
|
252
|
+
* pass while the window (centered on their cut) is active.
|
|
253
|
+
*/
|
|
254
|
+
declare function renderFrameWith(backend: RenderBackend, project: Project, timeMs: number, options?: RenderFrameOptions): void;
|
|
255
|
+
//#endregion
|
|
256
|
+
//#region src/gpu-effects.d.ts
|
|
257
|
+
/** Idempotent; the compositor registers these at module load. */
|
|
258
|
+
declare function registerGpuEffectTypes(): void;
|
|
259
|
+
//#endregion
|
|
260
|
+
//#region src/webgpu/webgpu-backend.d.ts
|
|
261
|
+
/**
|
|
262
|
+
* The WebGPU compositor backend. Image quads (video/image elements)
|
|
263
|
+
* composite as full-frame passes over a ping-pong texture pair —
|
|
264
|
+
* `importExternalTexture` keeps `VideoFrame`s zero-copy — with effects as
|
|
265
|
+
* WGSL passes and every blend mode in one shader (mode uniform). Raster
|
|
266
|
+
* content (text, captions, multicam chrome, transitions, custom renderers)
|
|
267
|
+
* still paints through canvas2d in frame space and uploads as a texture
|
|
268
|
+
* layer in z-order, so output matches the reference Canvas2D backend.
|
|
269
|
+
*
|
|
270
|
+
* Lifecycle: `WebGPUBackend.create({ canvas, width, height })` once, then
|
|
271
|
+
* `renderFrameWith(backend, project, timeMs, options)` per frame, `resize`
|
|
272
|
+
* on project dimension changes, `dispose` when done. For export, pass the
|
|
273
|
+
* GPUTexture-backed canvas straight to Mediabunny's CanvasSource — no CPU
|
|
274
|
+
* readback.
|
|
275
|
+
*/
|
|
276
|
+
interface WebGPUBackendOptions {
|
|
277
|
+
/** Presentation canvas; the backend configures its 'webgpu' context. */
|
|
278
|
+
canvas: HTMLCanvasElement | OffscreenCanvas;
|
|
279
|
+
/** Project (composition) size — passes render at this resolution. */
|
|
280
|
+
width: number;
|
|
281
|
+
height: number;
|
|
282
|
+
/** Bring your own device (tests/sharing); otherwise adapter-requested. */
|
|
283
|
+
device?: GPUDevice;
|
|
284
|
+
}
|
|
285
|
+
/** Whether this runtime exposes WebGPU at all (Chrome 113+/Safari 26+/Firefox 141+). */
|
|
286
|
+
declare function isWebGPUSupported(): boolean;
|
|
287
|
+
declare class WebGPUBackend implements RenderBackend {
|
|
288
|
+
readonly kind = "webgpu";
|
|
289
|
+
readonly width: number;
|
|
290
|
+
readonly height: number;
|
|
291
|
+
private readonly device;
|
|
292
|
+
private readonly context;
|
|
293
|
+
private readonly presentationFormat;
|
|
294
|
+
private acc;
|
|
295
|
+
private accIndex;
|
|
296
|
+
private readonly rasterCanvas;
|
|
297
|
+
private readonly rasterCtx;
|
|
298
|
+
private rasterDirty;
|
|
299
|
+
private rasterScope;
|
|
300
|
+
private readonly sampler;
|
|
301
|
+
private readonly pipelines;
|
|
302
|
+
private readonly texturePool;
|
|
303
|
+
private frameBuffers;
|
|
304
|
+
private readonly identityCurves;
|
|
305
|
+
private readonly luts;
|
|
306
|
+
static create(options: WebGPUBackendOptions): Promise<WebGPUBackend>;
|
|
307
|
+
private constructor();
|
|
308
|
+
/**
|
|
309
|
+
* Register a 3D LUT for `lut3d` effects: `data` is size³ RGB triples
|
|
310
|
+
* (0..1, red fastest), flattened to a (size² × size) 2D texture.
|
|
311
|
+
*/
|
|
312
|
+
registerLut3D(lutId: string, size: number, data: Float32Array): void;
|
|
313
|
+
beginFrame(backgroundColor: string): void;
|
|
314
|
+
endFrame(): void;
|
|
315
|
+
acquireRaster(): Canvas2D;
|
|
316
|
+
pushRasterScope(): void;
|
|
317
|
+
popRasterScope(): void;
|
|
318
|
+
drawImageQuad(quad: ImageQuad, chrome: LayerChrome): void;
|
|
319
|
+
/** Free GPU resources. The backend is unusable afterwards. */
|
|
320
|
+
dispose(): void;
|
|
321
|
+
private createAccTexture;
|
|
322
|
+
private acquireTexture;
|
|
323
|
+
private releaseTexture;
|
|
324
|
+
private uniformBuffer;
|
|
325
|
+
private storageBuffer;
|
|
326
|
+
/** Upload the raster scratch and composite it as a normal full-frame layer. */
|
|
327
|
+
private flushRaster;
|
|
328
|
+
private drawQuadOnGpu;
|
|
329
|
+
/** Sample (and crop) the source into a fresh layer texture. */
|
|
330
|
+
private prepareLayer;
|
|
331
|
+
private runEffectPass;
|
|
332
|
+
private runColorPass;
|
|
333
|
+
private runBlurPasses;
|
|
334
|
+
private compositeFullFrame;
|
|
335
|
+
private renderFullscreen;
|
|
336
|
+
}
|
|
337
|
+
//#endregion
|
|
338
|
+
//#region src/webgpu/effect-plan.d.ts
|
|
339
|
+
interface ColorOp {
|
|
340
|
+
kind: number;
|
|
341
|
+
/** Up to 8 packed params, op-specific (vec4 a + vec4 b in the shader). */
|
|
342
|
+
params: number[];
|
|
343
|
+
}
|
|
344
|
+
type EffectPass = {
|
|
345
|
+
kind: 'color';
|
|
346
|
+
ops: ColorOp[]; /** Per-channel 256-entry LUTs when a curves op is in this run. */
|
|
347
|
+
curves: {
|
|
348
|
+
r: Float32Array;
|
|
349
|
+
g: Float32Array;
|
|
350
|
+
b: Float32Array;
|
|
351
|
+
} | null;
|
|
352
|
+
} | {
|
|
353
|
+
kind: 'blur';
|
|
354
|
+
radius: number;
|
|
355
|
+
} | {
|
|
356
|
+
kind: 'shadow';
|
|
357
|
+
offsetX: number;
|
|
358
|
+
offsetY: number;
|
|
359
|
+
blur: number;
|
|
360
|
+
color: [number, number, number, number];
|
|
361
|
+
} | {
|
|
362
|
+
kind: 'lut3d';
|
|
363
|
+
lutId: string;
|
|
364
|
+
intensity: number;
|
|
365
|
+
};
|
|
366
|
+
interface EffectPlan {
|
|
367
|
+
passes: EffectPass[];
|
|
368
|
+
/** The stack contains effects only canvas2d can run (`css`/unknown). */
|
|
369
|
+
unsupported: boolean;
|
|
370
|
+
}
|
|
371
|
+
interface CurvePoint {
|
|
372
|
+
x: number;
|
|
373
|
+
y: number;
|
|
374
|
+
}
|
|
375
|
+
/** Monotone-x piecewise-linear curve → 256-entry LUT (identity when empty). */
|
|
376
|
+
declare function curveToLut(points: readonly CurvePoint[] | undefined): Float32Array;
|
|
377
|
+
declare function planEffects(effects: readonly Effect[] | undefined): EffectPlan;
|
|
378
|
+
/** True when this stack can only render through the canvas2d raster path. */
|
|
379
|
+
declare function hasUnsupportedEffects(effects: readonly Effect[] | undefined): boolean;
|
|
380
|
+
//#endregion
|
|
381
|
+
//#region src/transition-renderers.d.ts
|
|
382
|
+
/**
|
|
383
|
+
* The renderer half of the transition registry (pair it with
|
|
384
|
+
* registerTransitionType in @mcut/timeline). A transition is a pure mixer:
|
|
385
|
+
* given draw thunks for both sides of a cut and the blend completion, paint
|
|
386
|
+
* the in-between frame. The same renderers serve clip-to-clip transitions
|
|
387
|
+
* (render-frame.ts) and multicam angle-cut transitions (renderers.ts).
|
|
388
|
+
*/
|
|
389
|
+
/** Everything a transition renderer needs to blend its pair. */
|
|
390
|
+
interface TransitionRenderContext {
|
|
391
|
+
ctx: Canvas2D;
|
|
392
|
+
project: Project;
|
|
393
|
+
pair: TransitionPair;
|
|
394
|
+
timeMs: number;
|
|
395
|
+
/** Blend completion 0→1 across the window (0.5 at the cut). */
|
|
396
|
+
completion: number;
|
|
397
|
+
/** Draw the outgoing clip (already extended past its out point). */
|
|
398
|
+
drawLeft: () => void;
|
|
399
|
+
/** Draw the incoming clip (already pre-rolling before its in point). */
|
|
400
|
+
drawRight: () => void;
|
|
401
|
+
}
|
|
402
|
+
type TransitionRenderer = (context: TransitionRenderContext) => void;
|
|
403
|
+
/**
|
|
404
|
+
* Register the renderer half of a transition type. Built-ins register below
|
|
405
|
+
* through the same call; re-registering overrides (e.g. to restyle a
|
|
406
|
+
* built-in wipe).
|
|
407
|
+
*/
|
|
408
|
+
declare function registerTransitionRenderer(type: string, renderer: TransitionRenderer): void;
|
|
409
|
+
/** The registered renderer for `type`, or undefined (degrade to a hard cut). */
|
|
410
|
+
declare function getTransitionRenderer(type: string): TransitionRenderer | undefined;
|
|
411
|
+
//#endregion
|
|
412
|
+
//#region src/text.d.ts
|
|
413
|
+
/**
|
|
414
|
+
* Width of `text` drawn with `font` and `letterSpacingPx` of tracking.
|
|
415
|
+
* Implementations should set `ctx.letterSpacing` when the engine supports it
|
|
416
|
+
* so measurement matches drawing; engines without it ignore the parameter
|
|
417
|
+
* (and the renderer draws without tracking — consistent both ways).
|
|
418
|
+
*/
|
|
419
|
+
type MeasureFn = (text: string, font: string, letterSpacingPx?: number) => number;
|
|
420
|
+
declare function buildFont(style: {
|
|
421
|
+
fontStyle?: 'normal' | 'italic';
|
|
422
|
+
fontWeight: number;
|
|
423
|
+
fontSize: number;
|
|
424
|
+
fontFamily: string;
|
|
425
|
+
}): string;
|
|
426
|
+
/** Render-time case transform; the stored text keeps the user's casing. */
|
|
427
|
+
declare function applyTextTransform(text: string, transform: TextStyle['textTransform']): string;
|
|
428
|
+
/** One same-style stretch of a visual line (rich-text runs; see rich-text.ts). */
|
|
429
|
+
interface TextSegment {
|
|
430
|
+
/** Case-transformed slice, ready to paint. */
|
|
431
|
+
text: string;
|
|
432
|
+
width: number;
|
|
433
|
+
font: string;
|
|
434
|
+
/** Fill override from the segment's run (base style color otherwise). */
|
|
435
|
+
color?: string;
|
|
436
|
+
}
|
|
437
|
+
interface TextBlockLayout {
|
|
438
|
+
/** `segments` present only when the element has style runs. */
|
|
439
|
+
lines: {
|
|
440
|
+
text: string;
|
|
441
|
+
width: number;
|
|
442
|
+
segments?: TextSegment[];
|
|
443
|
+
}[];
|
|
444
|
+
font: string;
|
|
445
|
+
lineHeight: number;
|
|
446
|
+
padding: number;
|
|
447
|
+
overflow: TextBox['overflow'] | null;
|
|
448
|
+
/** Box size including padding — matches what the renderer draws. */
|
|
449
|
+
width: number;
|
|
450
|
+
height: number;
|
|
451
|
+
}
|
|
452
|
+
interface TextBlockOptions {
|
|
453
|
+
box?: TextBox;
|
|
454
|
+
/** Per-range style overrides (character offsets into the text). */
|
|
455
|
+
runs?: readonly TextRun[];
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Lay out a (possibly multi-line) text element. Lines come from explicit
|
|
459
|
+
* newlines only unless a text box width is provided.
|
|
460
|
+
*/
|
|
461
|
+
declare function layoutTextBlock(measure: MeasureFn, text: string, style: TextStyle, options?: TextBlockOptions): TextBlockLayout;
|
|
462
|
+
interface CaptionWordBox {
|
|
463
|
+
text: string;
|
|
464
|
+
/** X offset from the line's left edge. */
|
|
465
|
+
x: number;
|
|
466
|
+
width: number;
|
|
467
|
+
/** Word timing relative to the element start, when known. */
|
|
468
|
+
startMs?: number;
|
|
469
|
+
endMs?: number;
|
|
470
|
+
}
|
|
471
|
+
interface CaptionLayout {
|
|
472
|
+
lines: {
|
|
473
|
+
words: CaptionWordBox[];
|
|
474
|
+
width: number;
|
|
475
|
+
}[];
|
|
476
|
+
font: string;
|
|
477
|
+
lineHeight: number;
|
|
478
|
+
spaceWidth: number;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Greedily wrap caption words into centered lines no wider than `maxWidth`.
|
|
482
|
+
* Falls back to whitespace-split text when word timings are absent.
|
|
483
|
+
*/
|
|
484
|
+
declare function layoutCaption(measure: MeasureFn, element: {
|
|
485
|
+
text: string;
|
|
486
|
+
words?: {
|
|
487
|
+
text: string;
|
|
488
|
+
startMs: number;
|
|
489
|
+
endMs: number;
|
|
490
|
+
}[];
|
|
491
|
+
}, style: CaptionStyle, maxWidth: number): CaptionLayout;
|
|
492
|
+
//#endregion
|
|
493
|
+
//#region src/renderers.d.ts
|
|
494
|
+
/**
|
|
495
|
+
* Register a renderer for an element type. Built-in types can be overridden;
|
|
496
|
+
* custom element types (added via custom commands) plug in here — the
|
|
497
|
+
* compositor side of the engine's command registry.
|
|
498
|
+
*/
|
|
499
|
+
declare function registerElementRenderer<E extends TimelineElement>(type: E['type'] | (string & {}), renderer: ElementRenderer<E>): void;
|
|
500
|
+
declare function getElementRenderer(type: string): ElementRenderer | undefined;
|
|
501
|
+
declare function measureWith(ctx: Canvas2D): MeasureFn;
|
|
502
|
+
declare function getImageSize(source: CanvasImageSource): {
|
|
503
|
+
width: number;
|
|
504
|
+
height: number;
|
|
505
|
+
};
|
|
506
|
+
//#endregion
|
|
507
|
+
export { type Canvas2D, Canvas2DBackend, type CaptionLayout, type CaptionWordBox, type ColorOp, type DisplaySizePatch, type EffectPass, type EffectPlan, type ElementRenderContext, type ElementRenderer, type ElementSize, type FrameSource, type Handle, type HandleId, type ImageQuad, type LayerChrome, type MeasureFn, type OBB, ROTATE_HANDLE_OFFSET, type RenderBackend, type RenderFrameOptions, type SizeHelpers, type TextBlockLayout, type TransitionRenderContext, type TransitionRenderer, WebGPUBackend, type WebGPUBackendOptions, applyChrome, applyTextTransform, buildFont, createElementContext, curveToLut, degToRad, drawImageQuad2D, fromCanvasPoint, getElementDisplaySize, getElementNaturalSize, getElementOBB, getElementRenderer, getFitScale, getHandles, getImageSize, getTransformForDisplaySize, getTransitionRenderer, hasUnsupportedEffects, hitTestHandles, hitTestOBB, isWebGPUSupported, layoutCaption, layoutTextBlock, measureWith, planEffects, registerElementRenderer, registerGpuEffectTypes, registerTransitionRenderer, renderFrame, renderFrameWith, toCanvasPoint };
|