@agrhub/engine-pixi 0.0.0-stage → 1.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.
Files changed (93) hide show
  1. package/LICENSE +47 -0
  2. package/NOTICE.md +20 -0
  3. package/README.md +6 -2
  4. package/dist/animation/block-reveal-animation.d.ts +33 -0
  5. package/dist/animation/easings.d.ts +4 -0
  6. package/dist/animation/gsap-animation.d.ts +32 -0
  7. package/dist/animation/index.d.ts +9 -0
  8. package/dist/animation/keyframe-animation.d.ts +11 -0
  9. package/dist/animation/mask-animation.d.ts +170 -0
  10. package/dist/animation/pixi-animation-utils.d.ts +10 -0
  11. package/dist/animation/registry.d.ts +11 -0
  12. package/dist/animation/types.d.ts +211 -0
  13. package/dist/animation/wipe-animation.d.ts +53 -0
  14. package/dist/clips/audio-clip.d.ts +122 -0
  15. package/dist/clips/backdrop-clip.d.ts +56 -0
  16. package/dist/clips/base-clip.d.ts +150 -0
  17. package/dist/clips/base-text-clip.d.ts +228 -0
  18. package/dist/clips/caption-clip.d.ts +129 -0
  19. package/dist/clips/clip-registry.d.ts +56 -0
  20. package/dist/clips/effect-clip.d.ts +37 -0
  21. package/dist/clips/iclip.d.ts +235 -0
  22. package/dist/clips/image-clip.d.ts +116 -0
  23. package/dist/clips/index.d.ts +20 -0
  24. package/dist/clips/placeholder-clip.d.ts +22 -0
  25. package/dist/clips/shape-clip.d.ts +59 -0
  26. package/dist/clips/text-clip.d.ts +148 -0
  27. package/dist/clips/transition-clip.d.ts +45 -0
  28. package/dist/clips/video-clip.d.ts +216 -0
  29. package/dist/compositor/audio-mixer.d.ts +8 -0
  30. package/dist/compositor/av-encoder.d.ts +17 -0
  31. package/dist/compositor/clip-style-renderer.d.ts +23 -0
  32. package/dist/compositor/compositor-renderer.d.ts +29 -0
  33. package/dist/compositor/effect-manager.d.ts +30 -0
  34. package/dist/compositor/scheduler.d.ts +8 -0
  35. package/dist/compositor/transition-manager.d.ts +45 -0
  36. package/dist/compositor.d.ts +157 -0
  37. package/dist/effect/constant/index.d.ts +566 -0
  38. package/dist/effect/effect.d.ts +9 -0
  39. package/dist/effect/glsl/custom-glsl.d.ts +1043 -0
  40. package/dist/effect/glsl/gl-effect.d.ts +38 -0
  41. package/dist/effect/interface/index.d.ts +25 -0
  42. package/dist/effect/types.d.ts +26 -0
  43. package/dist/effect/vertex.d.ts +1 -0
  44. package/dist/event-emitter.d.ts +55 -0
  45. package/dist/filters/outline-filter.d.ts +28 -0
  46. package/dist/index.d.ts +27 -0
  47. package/dist/index.es.js +16144 -0
  48. package/dist/index.es.js.map +1 -0
  49. package/dist/index.umd.cjs +3335 -0
  50. package/dist/index.umd.cjs.map +1 -0
  51. package/dist/json-serialization.d.ts +312 -0
  52. package/dist/mp4-utils/index.d.ts +31 -0
  53. package/dist/mp4-utils/mp4box-utils.d.ts +44 -0
  54. package/dist/mp4-utils/sample-transform.d.ts +26 -0
  55. package/dist/sprite/base-sprite.d.ts +273 -0
  56. package/dist/sprite/pixi-sprite-renderer.d.ts +69 -0
  57. package/dist/standalone/index.es.js +94191 -0
  58. package/dist/standalone/index.es.js.map +1 -0
  59. package/dist/studio/history-manager.d.ts +46 -0
  60. package/dist/studio/resource-manager.d.ts +53 -0
  61. package/dist/studio/selection-manager.d.ts +66 -0
  62. package/dist/studio/timeline-model.d.ts +117 -0
  63. package/dist/studio/transport.d.ts +44 -0
  64. package/dist/studio-bridge.d.ts +50 -0
  65. package/dist/studio.d.ts +529 -0
  66. package/dist/transformer/handle.d.ts +17 -0
  67. package/dist/transformer/snapping.d.ts +45 -0
  68. package/dist/transformer/transformer.d.ts +38 -0
  69. package/dist/transformer/wireframe.d.ts +5 -0
  70. package/dist/transition/fragment.d.ts +1 -0
  71. package/dist/transition/glsl/custom-glsl.d.ts +231 -0
  72. package/dist/transition/glsl/gl-transition.d.ts +42 -0
  73. package/dist/transition/transition.d.ts +6 -0
  74. package/dist/transition/types.d.ts +31 -0
  75. package/dist/transition/uniforms.d.ts +35 -0
  76. package/dist/transition/vertex.d.ts +1 -0
  77. package/dist/utils/asset-manager.d.ts +15 -0
  78. package/dist/utils/audio-codec-detector.d.ts +39 -0
  79. package/dist/utils/audio.d.ts +81 -0
  80. package/dist/utils/chromakey.d.ts +24 -0
  81. package/dist/utils/color-adjustment.d.ts +53 -0
  82. package/dist/utils/color.d.ts +17 -0
  83. package/dist/utils/common.d.ts +7 -0
  84. package/dist/utils/dom.d.ts +48 -0
  85. package/dist/utils/effect.d.ts +1 -0
  86. package/dist/utils/fonts.d.ts +21 -0
  87. package/dist/utils/index.d.ts +6 -0
  88. package/dist/utils/log.d.ts +27 -0
  89. package/dist/utils/media-cache.d.ts +45 -0
  90. package/dist/utils/stream-utils.d.ts +4 -0
  91. package/dist/utils/video.d.ts +16 -0
  92. package/dist/utils/worker-timer.d.ts +1 -0
  93. package/package.json +63 -3
@@ -0,0 +1,122 @@
1
+ import { BaseClip } from './base-clip';
2
+ import { IClip, IPlaybackCapable } from './iclip';
3
+ import { AudioJSON } from '../json-serialization';
4
+ interface IAudioOpts {
5
+ loop?: boolean;
6
+ volume?: number;
7
+ }
8
+ /**
9
+ * Audio clip providing audio data for creating and editing audio/video
10
+ *
11
+ * @example
12
+ * // Load audio clip asynchronously
13
+ * const audioClip = await Audio.fromUrl('path/to/audio.mp3', {
14
+ * loop: true,
15
+ * });
16
+ *
17
+ * @example
18
+ * // Traditional approach (for advanced use)
19
+ * new Audio((await fetch('<mp3 url>')).body, {
20
+ * loop: true,
21
+ * }),
22
+ */
23
+ export declare class Audio extends BaseClip implements IPlaybackCapable {
24
+ readonly type = "Audio";
25
+ static ctx: AudioContext | null;
26
+ ready: IClip["ready"];
27
+ private _meta;
28
+ /**
29
+ * Audio metadata
30
+ *
31
+ * ⚠️ Note, these are converted (normalized) metadata, not original audio metadata
32
+ */
33
+ get meta(): {
34
+ sampleRate: number;
35
+ chanCount: number;
36
+ duration: number;
37
+ width: number;
38
+ height: number;
39
+ };
40
+ private chan0Buf;
41
+ private chan1Buf;
42
+ /**
43
+ * Get complete PCM data from audio clip
44
+ */
45
+ getPCMData(): Float32Array[];
46
+ private opts;
47
+ /**
48
+ * Whether to loop the audio (hybrid JSON structure)
49
+ */
50
+ loop: boolean;
51
+ /**
52
+ * Load an audio clip from a URL
53
+ * @param url Audio URL
54
+ * @param opts Audio configuration (loop, volume)
55
+ * @returns Promise that resolves to an audio clip
56
+ *
57
+ * @example
58
+ * const audioClip = await Audio.fromUrl('path/to/audio.mp3', {
59
+ * loop: true,
60
+ * volume: 0.8,
61
+ * });
62
+ */
63
+ static fromUrl(url: string, opts?: IAudioOpts): Promise<Audio>;
64
+ /**
65
+ * Create an Audio instance from a JSON object (fabric.js pattern)
66
+ * @param json The JSON object representing the clip
67
+ * @returns Promise that resolves to an Audio instance
68
+ */
69
+ static fromObject(json: AudioJSON): Promise<Audio>;
70
+ /**
71
+ *
72
+ * @param dataSource Audio file stream
73
+ * @param opts Audio configuration, controls volume and whether to loop
74
+ */
75
+ constructor(dataSource: ReadableStream<Uint8Array> | Float32Array[], opts?: IAudioOpts, src?: string);
76
+ private init;
77
+ /**
78
+ * Intercept data returned by {@link Audio.tick} method for secondary processing of audio data
79
+ * @param time Time when tick was called
80
+ * @param tickRet Data returned by tick
81
+ *
82
+ */
83
+ tickInterceptor: <T extends Awaited<ReturnType<Audio["tick"]>>>(time: number, tickRet: T) => Promise<T>;
84
+ private timestamp;
85
+ private frameOffset;
86
+ /**
87
+ * Return audio PCM data corresponding to the time difference between last and current moments
88
+ *
89
+ * If the difference exceeds 3s or current time is less than last time, reset state
90
+ * @example
91
+ * tick(0) // => []
92
+ * tick(1e6) // => [leftChanPCM(1s), rightChanPCM(1s)]
93
+ *
94
+ */
95
+ tick(time: number): Promise<{
96
+ audio: Float32Array[];
97
+ state: "success" | "done";
98
+ }>;
99
+ /**
100
+ * Split is handled by core state, not implemented here
101
+ */
102
+ split(_time: number): Promise<[this, this]>;
103
+ clone(): Promise<this>;
104
+ /**
105
+ * Destroy instance and release resources
106
+ */
107
+ destroy(): void;
108
+ toJSON(_main?: boolean): AudioJSON;
109
+ /**
110
+ * Create HTMLAudioElement for playback
111
+ */
112
+ createPlaybackElement(): Promise<{
113
+ element: HTMLAudioElement;
114
+ objectUrl?: string;
115
+ }>;
116
+ play(element: HTMLVideoElement | HTMLAudioElement, timeSeconds: number): Promise<void>;
117
+ pause(element: HTMLVideoElement | HTMLAudioElement): void;
118
+ seek(element: HTMLVideoElement | HTMLAudioElement, timeSeconds: number): Promise<void>;
119
+ syncPlayback(element: HTMLVideoElement | HTMLAudioElement, isPlaying: boolean, timeSeconds: number): void;
120
+ cleanupPlayback(element: HTMLVideoElement | HTMLAudioElement, objectUrl?: string): void;
121
+ }
122
+ export {};
@@ -0,0 +1,56 @@
1
+ import { Container, Application } from 'pixi.js';
2
+ import { BaseClip } from './base-clip';
3
+ import { IClipMeta } from './iclip';
4
+ export interface BackdropClipOptions {
5
+ backdropType: "solid" | "gradient" | "meshGradient" | "noise" | "floatingBlobs";
6
+ width?: number;
7
+ height?: number;
8
+ colors?: string[];
9
+ gradientType?: "linear" | "radial";
10
+ speed?: number;
11
+ scale?: number;
12
+ density?: number;
13
+ renderer?: Application["renderer"];
14
+ }
15
+ /**
16
+ * Backdrop: procedural, endlessly playing background (solid, gradient, animated mesh gradient,
17
+ * floating blobs, film noise) rendered with Pixi into a texture.
18
+ */
19
+ export declare class Backdrop extends BaseClip {
20
+ readonly type = "Backdrop";
21
+ backdropType: BackdropClipOptions["backdropType"];
22
+ container: Container;
23
+ private _meta;
24
+ private renderTexture;
25
+ private externalRenderer;
26
+ private bgGraphics;
27
+ private meshContainer;
28
+ private meshCircles;
29
+ private blobsContainer;
30
+ private blobCircles;
31
+ private noiseSprite;
32
+ private noiseTexture;
33
+ constructor(options: BackdropClipOptions);
34
+ setRenderer(renderer: Application["renderer"]): void;
35
+ private getRenderer;
36
+ private getRenderTexture;
37
+ get meta(): IClipMeta;
38
+ /**
39
+ * Rebuilds the static structure of the backdrop based on current styles.
40
+ */
41
+ private rebuildBackdrop;
42
+ tick(time: number): Promise<{
43
+ video: ImageBitmap;
44
+ state: "success";
45
+ }>;
46
+ clone(): Promise<this>;
47
+ split(_time: number): Promise<[this, this]>;
48
+ toJSON(main?: boolean): any;
49
+ set style(style: any);
50
+ get style(): any;
51
+ set width(width: number);
52
+ get width(): number;
53
+ set height(height: number);
54
+ get height(): number;
55
+ static fromObject(json: any): Promise<Backdrop>;
56
+ }
@@ -0,0 +1,150 @@
1
+ import { BaseSprite, BaseSpriteEvents } from '../sprite/base-sprite';
2
+ import { IClip, IClipMeta, ITransitionInfo } from './iclip';
3
+ import { ClipJSON } from '../json-serialization';
4
+ /**
5
+ * Base class for all clips that extends BaseSprite
6
+ * Provides common functionality for sprite operations (position, animation, timing)
7
+ * and frame management
8
+ */
9
+ export declare abstract class BaseClip<T extends BaseSpriteEvents = BaseSpriteEvents> extends BaseSprite<T> implements IClip<T> {
10
+ abstract readonly type: string;
11
+ private lastVf;
12
+ protected destroyed: boolean;
13
+ /**
14
+ * Source URL or identifier for this clip
15
+ * Used for serialization and reloading from JSON
16
+ */
17
+ src: string;
18
+ /**
19
+ * User-defined metadata
20
+ */
21
+ metadata: Record<string, any>;
22
+ /**
23
+ * Transition info (optional)
24
+ */
25
+ transition?: ITransitionInfo;
26
+ /**
27
+ * When set, the clip's RenderTexture is larger than its logical bounds by this many pixels
28
+ * on each side. Provides overflow room for animations (slide, zoom) without visual clipping.
29
+ */
30
+ renderTexturePadding?: number;
31
+ abstract tick(time: number): Promise<{
32
+ video?: VideoFrame | ImageBitmap | null;
33
+ audio?: Float32Array[];
34
+ state: "done" | "success";
35
+ }>;
36
+ ready: Promise<IClipMeta>;
37
+ abstract readonly meta: IClipMeta;
38
+ abstract clone(): Promise<this>;
39
+ abstract split?(time: number): Promise<[this, this]>;
40
+ constructor();
41
+ /**
42
+ * Get video frame and audio at specified time without rendering to canvas
43
+ * Useful for Pixi.js rendering where canvas context is not needed
44
+ * @param time Specified time in microseconds
45
+ */
46
+ getFrame(time: number): Promise<{
47
+ video: VideoFrame | ImageBitmap | null;
48
+ audio: Float32Array[];
49
+ done: boolean;
50
+ }>;
51
+ /**
52
+ * Draw image at specified time to canvas context and return corresponding audio data
53
+ * @param time Specified time in microseconds
54
+ */
55
+ offscreenRender(ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, time: number): Promise<{
56
+ audio: Float32Array[];
57
+ done: boolean;
58
+ }>;
59
+ /**
60
+ * Set clip properties (position, size, display timeline)
61
+ * @param props Properties to set
62
+ * @param fps Optional FPS for frame-to-time conversion (default: 30)
63
+ * @returns this for method chaining
64
+ *
65
+ * @example
66
+ * // Using frames (will be converted to microseconds)
67
+ * clip.set({
68
+ * display: {
69
+ * from: 150, // frames
70
+ * to: 450, // frames (10 seconds at 30fps)
71
+ * },
72
+ * }, 30);
73
+ *
74
+ * // Using microseconds directly
75
+ * clip.set({
76
+ * display: {
77
+ * from: 5000000, // microseconds
78
+ * to: 15000000, // microseconds
79
+ * },
80
+ * });
81
+ */
82
+ set(props: {
83
+ display?: {
84
+ from?: number;
85
+ to?: number;
86
+ };
87
+ x?: number;
88
+ y?: number;
89
+ width?: number;
90
+ height?: number;
91
+ duration?: number;
92
+ timing?: {
93
+ display?: {
94
+ from?: number;
95
+ to?: number;
96
+ };
97
+ trim?: {
98
+ from?: number;
99
+ to?: number;
100
+ };
101
+ duration?: number;
102
+ playbackRate?: number;
103
+ fadeIn?: {
104
+ duration: number;
105
+ curve?: "linear" | "ease-in" | "ease-out" | "ease-in-out";
106
+ };
107
+ fadeOut?: {
108
+ duration: number;
109
+ curve?: "linear" | "ease-in" | "ease-out" | "ease-in-out";
110
+ };
111
+ };
112
+ }, _fps?: number): this;
113
+ /**
114
+ * Base implementation of toJSON that handles common clip properties
115
+ * Subclasses should override to add their specific options
116
+ * @param main Whether this is the main clip (for Compositor)
117
+ */
118
+ toJSON(main?: boolean): ClipJSON;
119
+ /**
120
+ * Get the list of visible transformer handles for this clip type
121
+ * Default implementation returns all handles
122
+ * Override in subclasses to customize handle visibility (e.g., TextClip)
123
+ */
124
+ getVisibleHandles(): Array<"tl" | "tr" | "bl" | "br" | "ml" | "mr" | "mt" | "mb" | "rot">;
125
+ /**
126
+ * Scale clip to fit within the scene dimensions while maintaining aspect ratio
127
+ * @param sceneWidth Scene width
128
+ * @param sceneHeight Scene height
129
+ */
130
+ scaleToFit(sceneWidth: number, sceneHeight: number): Promise<void>;
131
+ /**
132
+ * Scale clip to fill the scene dimensions while maintaining aspect ratio
133
+ * May crop parts of the clip.
134
+ * @param sceneWidth Scene width
135
+ * @param sceneHeight Scene height
136
+ */
137
+ scaleToFill(sceneWidth: number, sceneHeight: number): Promise<void>;
138
+ /**
139
+ * Center the clip within the scene dimensions
140
+ * @param sceneWidth Scene width
141
+ * @param sceneHeight Scene height
142
+ */
143
+ centerInScene(sceneWidth: number, sceneHeight: number): void;
144
+ destroy(): void;
145
+ /**
146
+ * Applies all common properties from JSON to a clip instance.
147
+ * This handles transform, timing, style, animation, transitions, chroma key, color adjustment, and generic metadata.
148
+ */
149
+ static deserializeBaseProperties(clip: BaseClip, json: any): void;
150
+ }
@@ -0,0 +1,228 @@
1
+ import { Application, SplitBitmapText, TextStyle, RenderTexture, Texture, Container, Graphics } from 'pixi.js';
2
+ import { OutlineFilter } from '../filters/outline-filter';
3
+ import { DropShadowFilter } from 'pixi-filters';
4
+ import { BaseClip } from './base-clip';
5
+ import { IClip } from './iclip';
6
+ import { BaseSpriteEvents } from '../sprite/base-sprite';
7
+ /**
8
+ * Shared style options used by both Text and Caption clips.
9
+ */
10
+ export interface IBaseTextOpts {
11
+ fontSize?: number;
12
+ fontFamily?: string;
13
+ fontWeight?: string;
14
+ fontStyle?: string;
15
+ fontUrl?: string;
16
+ color?: string | number | {
17
+ type: "gradient";
18
+ x0: number;
19
+ y0: number;
20
+ x1: number;
21
+ y1: number;
22
+ colors: Array<{
23
+ ratio: number;
24
+ color: string | number;
25
+ }>;
26
+ };
27
+ stroke?: string | number | {
28
+ color: string | number;
29
+ width: number;
30
+ join?: "miter" | "round" | "bevel";
31
+ cap?: "butt" | "round" | "square";
32
+ miterLimit?: number;
33
+ };
34
+ strokeWidth?: number;
35
+ align?: "left" | "center" | "right";
36
+ textAlign?: "left" | "center" | "right";
37
+ verticalAlign?: "top" | "center" | "bottom" | "underline" | "overline" | "strikethrough";
38
+ shadow?: {
39
+ color?: string | number;
40
+ alpha?: number;
41
+ blur?: number;
42
+ offsetX?: number;
43
+ offsetY?: number;
44
+ };
45
+ wordWrapWidth?: number;
46
+ wordWrap?: boolean;
47
+ lineHeight?: number;
48
+ letterSpacing?: number;
49
+ textCase?: "none" | "uppercase" | "lowercase" | "title";
50
+ background?: {
51
+ color?: string;
52
+ opacity?: number;
53
+ borderRadius?: number;
54
+ paddingX?: number;
55
+ paddingY?: number;
56
+ };
57
+ }
58
+ export declare function getOrInstallFont(styleOptions: any): string;
59
+ /**
60
+ * Abstract base class for text-based clips (Text and Caption).
61
+ * Contains all shared PixiJS rendering infrastructure.
62
+ * Subclasses must implement: `get text()`, `get textAlign()`, and `refreshContent()`.
63
+ */
64
+ export declare abstract class BaseTextClip<TEvents extends BaseSpriteEvents = BaseSpriteEvents> extends BaseClip<TEvents> {
65
+ ready: IClip["ready"];
66
+ protected _meta: {
67
+ duration: number;
68
+ width: number;
69
+ height: number;
70
+ };
71
+ get meta(): {
72
+ duration: number;
73
+ width: number;
74
+ height: number;
75
+ };
76
+ protected pixiTextContainer: Container | null;
77
+ protected wordTexts: SplitBitmapText[];
78
+ protected textStyle: TextStyle;
79
+ protected textStyleBase: TextStyle;
80
+ protected renderTexture: RenderTexture | null;
81
+ protected outlineFilter: OutlineFilter | null;
82
+ protected dropShadowFilter: DropShadowFilter | null;
83
+ protected _refreshing: boolean;
84
+ protected _needsRefresh: boolean;
85
+ /** Settles after the last queued refresh pass (see refreshText) */
86
+ protected _refreshPromise: Promise<void> | null;
87
+ protected _spaceWidth: number;
88
+ /** Per-line boxes from the last layout (read by BlockRevealAnimation) */
89
+ _lineRects?: {
90
+ x: number;
91
+ y: number;
92
+ w: number;
93
+ h: number;
94
+ }[];
95
+ protected _text: string;
96
+ private externalRenderer;
97
+ private pixiApp;
98
+ originalOpts: IBaseTextOpts;
99
+ id: string;
100
+ effects: Array<{
101
+ id: string;
102
+ key: string;
103
+ startTime: number;
104
+ duration: number;
105
+ }>;
106
+ /**
107
+ * The text string to render. Implemented by subclasses.
108
+ */
109
+ abstract get text(): string;
110
+ /**
111
+ * Horizontal text alignment. Implemented by subclasses.
112
+ */
113
+ abstract get textAlign(): "left" | "center" | "right";
114
+ setRenderer(renderer: Application["renderer"]): void;
115
+ protected getRenderer(): Promise<Application["renderer"]>;
116
+ getTexture(): Promise<Texture | null>;
117
+ tick(_time: number): Promise<{
118
+ video: ImageBitmap;
119
+ state: "success";
120
+ }>;
121
+ split(_time: number): Promise<[this, this]>;
122
+ abstract clone(): Promise<this>;
123
+ /**
124
+ * Returns true if any animation in the list requires a per-line mask (i.e. has params.mask === true).
125
+ */
126
+ private _hasMaskAnim;
127
+ /**
128
+ * Override the animations setter so that when the mask-mode changes
129
+ * (e.g. slideMaskWord → slideByWord or vice-versa), the PIXI scene graph
130
+ * is rebuilt via refreshText(). This removes stale LineMask graphics that
131
+ * would otherwise cause GSAP to access `.y` on null/destroyed nodes.
132
+ */
133
+ get animations(): any[];
134
+ set animations(v: any[]);
135
+ animate(time: number): void;
136
+ addEffect(effect: {
137
+ id: string;
138
+ key: string;
139
+ startTime: number;
140
+ duration: number;
141
+ }): void;
142
+ editEffect(effectId: string, newEffectData: Partial<{
143
+ key: string;
144
+ startTime: number;
145
+ duration: number;
146
+ }>): void;
147
+ removeEffect(effectId: string): void;
148
+ /**
149
+ * Rebuilds the text layout. Calls made while a rebuild runs are coalesced into one more pass, and
150
+ * every caller's promise resolves only after that final pass — previously they resolved
151
+ * immediately, so "refresh then emit propsChange" re-rendered the stale layout.
152
+ */
153
+ protected refreshText(): Promise<void>;
154
+ protected _doRefreshText(): Promise<void>;
155
+ /**
156
+ * Hook called at the end of _doRefreshText. Subclasses can override to run
157
+ * post-layout logic (e.g. populate word cache, apply initial word colors).
158
+ */
159
+ protected _onAfterRefresh(_words: string[], _textOnlyContainer: Container, _spaceWidth: number, _measuredTextHeight: number, _lineHeightMultiplier: number, _fontSize: number): void;
160
+ protected _applyTextCase(text: string): string;
161
+ protected _buildTextStyles(): {
162
+ fontName: string;
163
+ baseFontName: string;
164
+ };
165
+ protected _buildWordObjects(textToRender: string): {
166
+ textOnlyContainer: Container;
167
+ words: string[];
168
+ };
169
+ protected _applyStrokeFilter(): number;
170
+ protected _applyShadowFilter(textOnlyContainer: Container): void;
171
+ protected _measureSpaceAndHeight(): Promise<{
172
+ spaceWidth: number;
173
+ measuredTextHeight: number;
174
+ }>;
175
+ protected _breakIntoLines(wrapWidth: number, lineHeight: number): {
176
+ words: SplitBitmapText[];
177
+ width: number;
178
+ height: number;
179
+ }[];
180
+ protected _calculateDimensions(lines: {
181
+ words: SplitBitmapText[];
182
+ width: number;
183
+ height: number;
184
+ }[], measuredTextHeight: number, snapshotWidth: number, snapshotWordWrap: boolean | undefined, snapshotWordWrapWidth: number | undefined, snapshotBackground: IBaseTextOpts["background"], bgPadX: number, bgPadY: number): {
185
+ containerWidth: number;
186
+ containerHeight: number;
187
+ contentWidth: number;
188
+ contentHeight: number;
189
+ };
190
+ protected _positionWords(lines: {
191
+ words: SplitBitmapText[];
192
+ width: number;
193
+ height: number;
194
+ }[], containerWidth: number, containerHeight: number, contentHeight: number, measuredTextHeight: number, bgPadX: number, textOnlyContainer: Container): {
195
+ x: number;
196
+ y: number;
197
+ w: number;
198
+ h: number;
199
+ }[];
200
+ protected _drawDecoration(lines: {
201
+ words: SplitBitmapText[];
202
+ width: number;
203
+ height: number;
204
+ }[], containerWidth: number, bgPadX: number): Graphics | null;
205
+ protected _buildBackground(lineRects: {
206
+ x: number;
207
+ y: number;
208
+ w: number;
209
+ h: number;
210
+ }[], bgColorOpt: string | undefined, bgOpacity: number, bgBorderRadius: number): Graphics | null;
211
+ protected _createRenderTexture(containerWidth: number, containerHeight: number, strokeWidth: number): void;
212
+ protected _normalizeStyleOpts(opts: Partial<IBaseTextOpts>): Partial<IBaseTextOpts>;
213
+ protected _cloneColorOpts(colorToNumber: (c: any) => number | undefined): IBaseTextOpts["color"];
214
+ protected _cloneStrokeOpts(colorToNumber: (c: any) => number | undefined): {
215
+ stroke: IBaseTextOpts["stroke"];
216
+ strokeWidth: number;
217
+ };
218
+ protected _cloneShadowOpts(colorToNumber: (c: any) => number | undefined): IBaseTextOpts["shadow"] | undefined;
219
+ protected createStyleFromOpts(opts: IBaseTextOpts): any;
220
+ protected drawRoundedMultilinePath(graphics: Graphics, rects: {
221
+ x: number;
222
+ y: number;
223
+ w: number;
224
+ h: number;
225
+ }[], radius: number, color: number, alpha?: number): void;
226
+ destroy(): void;
227
+ toJSON(main?: boolean): any;
228
+ }
@@ -0,0 +1,129 @@
1
+ import { Application, Container } from 'pixi.js';
2
+ import { ICaptionWord } from '@agrhub/core';
3
+ import { IClip } from './iclip';
4
+ import { ClipContext } from './clip-registry';
5
+ import { CaptionJSON, ICaptionWordAnimation } from '../json-serialization';
6
+ import { BaseTextClip, IBaseTextOpts } from './base-text-clip';
7
+ import { BaseSpriteEvents } from '../sprite/base-sprite';
8
+ export type { ICaptionWord, ICaptionWordAnimation };
9
+ /** Style applied to a single word state (active / future / keyword) */
10
+ export interface ICaptionWordStyle {
11
+ color?: string;
12
+ border?: {
13
+ color?: string;
14
+ width?: number;
15
+ };
16
+ background?: string;
17
+ }
18
+ /** Caption color configuration */
19
+ export interface ICaptionColors {
20
+ /** Currently spoken word — null/undefined to disable */
21
+ active?: ICaptionWordStyle | null;
22
+ /** Upcoming / future words — null/undefined to disable */
23
+ future?: ICaptionWordStyle | null;
24
+ /** Keyword words (isKeyWord=true) — null/undefined to disable */
25
+ keyword?: {
26
+ color?: string;
27
+ preserveAfterSpoken?: boolean;
28
+ } | null;
29
+ }
30
+ /**
31
+ * Options for constructing a Caption clip.
32
+ *
33
+ * Color model:
34
+ * - `style.color` → appeared (already-spoken) words
35
+ * - `caption.colors.active` → currently spoken word (color + background + border)
36
+ * - `caption.colors.future` → upcoming words (color + border)
37
+ * - `caption.colors.keyword` → isKeyWord=true override
38
+ */
39
+ export interface ICaptionOpts extends IBaseTextOpts {
40
+ words?: ICaptionWord[];
41
+ wordAnimation?: ICaptionWordAnimation;
42
+ wordsPerLine?: "single" | "multiple";
43
+ mediaId?: string;
44
+ videoWidth?: number;
45
+ videoHeight?: number;
46
+ bottomOffset?: number;
47
+ /** Nested caption data (flattened into opts on construction) */
48
+ caption?: {
49
+ words?: ICaptionWord[];
50
+ colors?: ICaptionColors;
51
+ wordAnimation?: ICaptionWordAnimation;
52
+ positioning?: {
53
+ videoWidth?: number;
54
+ videoHeight?: number;
55
+ bottomOffset?: number;
56
+ };
57
+ };
58
+ /** Internal: skip initial auto-positioning when restoring from JSON */
59
+ initialLayoutApplied?: boolean;
60
+ /** Resolved color objects (flat access after construction) */
61
+ activeStyle?: ICaptionWordStyle;
62
+ futureStyle?: ICaptionWordStyle;
63
+ keywordColor?: string;
64
+ keywordPreserveAfterSpoken?: boolean;
65
+ }
66
+ export interface ICaptionEvents extends BaseSpriteEvents {
67
+ propsChange: Partial<{
68
+ left: number;
69
+ top: number;
70
+ width: number;
71
+ height: number;
72
+ angle: number;
73
+ text: string;
74
+ style: any;
75
+ } & ICaptionOpts>;
76
+ }
77
+ export declare class Caption extends BaseTextClip<ICaptionEvents> implements IClip {
78
+ readonly type = "Caption";
79
+ ready: IClip["ready"];
80
+ private _lastTickTime;
81
+ protected _isWidthConstrained: boolean;
82
+ private _words;
83
+ originalOpts: ICaptionOpts;
84
+ private _activeStyle;
85
+ private _futureStyle;
86
+ private _keywordColor;
87
+ private _keywordPreserveAfterSpoken;
88
+ private _wordAnimation;
89
+ private _wordsPerLine;
90
+ protected _videoWidth: number;
91
+ protected _videoHeight: number;
92
+ protected _bottomOffset: number;
93
+ get text(): string;
94
+ set text(v: string);
95
+ get textAlign(): "left" | "center" | "right";
96
+ get caption(): ICaptionOpts["caption"];
97
+ set caption(v: ICaptionOpts["caption"]);
98
+ get words(): ICaptionWord[];
99
+ set words(v: ICaptionWord[]);
100
+ get mediaId(): string | undefined;
101
+ set mediaId(v: string | undefined);
102
+ get wordsPerLine(): "single" | "multiple";
103
+ set wordsPerLine(v: "single" | "multiple");
104
+ get style(): any;
105
+ set style(opts: Partial<ICaptionOpts>);
106
+ updateStyle(opts: Partial<ICaptionOpts>): Promise<void>;
107
+ get width(): number;
108
+ set width(v: number);
109
+ get height(): number;
110
+ set height(v: number);
111
+ animate(time: number): void;
112
+ constructor(text: string, opts?: ICaptionOpts, renderer?: Application["renderer"]);
113
+ protected createStyleFromOpts(opts: any): any;
114
+ private _syncOpts;
115
+ protected _buildWordObjects(textToRender: string): {
116
+ textOnlyContainer: Container;
117
+ words: string[];
118
+ };
119
+ protected _onAfterRefresh(): void;
120
+ tick(time: number): Promise<{
121
+ video: ImageBitmap;
122
+ state: "success";
123
+ }>;
124
+ updateState(currentTimeUs: number): void;
125
+ clone(): Promise<this>;
126
+ toJSON(main?: boolean): CaptionJSON;
127
+ static fromObject(json: CaptionJSON, context?: ClipContext): Promise<Caption>;
128
+ getVisibleHandles(): Array<"tl" | "tr" | "bl" | "br" | "ml" | "mr" | "mt" | "mb" | "rot">;
129
+ }