@twick/visualizer 0.15.18 → 0.15.20

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.
@@ -1,176 +1,46 @@
1
1
  import { CaptionProps, ElementParams } from "../helpers/types";
2
- import { Color, createRef, Reference, waitFor } from "@twick/core";
3
- import { Rect, Txt } from "@twick/2d";
2
+ import { ThreadGenerator } from "@twick/core";
4
3
  import { splitPhraseTiming } from "../helpers/caption.utils";
5
- import { TRANSPARENT_COLOR } from "../helpers/constants";
6
- import { hexToRGB } from "../helpers/utils";
4
+ import { getCaptionStyleHandler, getDefaultCaptionStyleHandler } from "../caption-styles";
7
5
 
8
6
  /**
9
7
  * @group CaptionElement
10
8
  * CaptionElement creates and manages styled text overlays in the visualizer scene.
11
- * Handles caption rendering, text effects, background styling, and timing
12
- * for professional video presentations and content creation.
13
- *
14
- * Features:
15
- * - Styled text with custom fonts, colors, and backgrounds
16
- * - Word-by-word timing and animation
17
- * - Background highlighting and styling options
18
- * - Text effects and animations
19
- * - Automatic timing and synchronization
9
+ * Delegates rendering and animation to registered caption style handlers.
20
10
  *
21
11
  * @param containerRef - Reference to the container element
22
12
  * @param caption - Caption configuration including text, styling, and timing
23
- *
24
- * @example
25
- * ```js
26
- * // Basic caption
27
- * {
28
- * id: "welcome-caption",
29
- * type: "caption",
30
- * s: 2,
31
- * e: 8,
32
- * t: "Welcome to our presentation!",
33
- * props: {
34
- * colors: {
35
- * text: "#ffffff",
36
- * background: "rgba(0,0,0,0.7)"
37
- * },
38
- * font: {
39
- * family: "Arial",
40
- * size: 48,
41
- * weight: 600
42
- * }
43
- * }
44
- * }
45
- *
46
- * // Caption with background highlighting
47
- * {
48
- * id: "highlighted-caption",
49
- * type: "caption",
50
- * s: 3,
51
- * e: 10,
52
- * t: "Important information",
53
- * capStyle: "highlight_bg",
54
- * props: {
55
- * colors: {
56
- * text: "#ffffff",
57
- * background: "rgba(255,0,0,0.8)"
58
- * },
59
- * font: {
60
- * family: "Helvetica",
61
- * size: 36,
62
- * weight: 700
63
- * },
64
- * bgOpacity: 0.9,
65
- * bgOffsetWidth: 20,
66
- * bgOffsetHeight: 10,
67
- * bgMargin: [10, 5],
68
- * bgRadius: 15,
69
- * bgPadding: [20, 15]
70
- * }
71
- * }
72
- * ```
73
13
  */
74
14
  export const CaptionElement = {
75
15
  name: "caption",
76
-
77
- /**
78
- * Generator function that creates and manages caption elements in the scene.
79
- * Handles caption creation, word timing, styling, and text effects.
80
- *
81
- * @param params - Element parameters including container reference and caption config
82
- * @returns Generator that controls the caption element lifecycle
83
- *
84
- * @example
85
- * ```js
86
- * yield* CaptionElement.create({
87
- * containerRef: mainContainer,
88
- * caption: captionConfig
89
- * });
90
- * ```
91
- */
92
- *create({ containerRef, caption, containerProps }: ElementParams) {
16
+
17
+ *create({ containerRef, caption, containerProps }: ElementParams): ThreadGenerator {
93
18
  const words = splitPhraseTiming(caption);
94
- let phraseStart = 0;
95
- if (words?.length) {
96
- phraseStart = words[0].s;
97
- }
98
- let wordsState: {
99
- refs: Array<{ bgRef?: Reference<any>; textRef: Reference<any> }>;
100
- props: CaptionProps[];
101
- idx: number;
102
- prevTime: number;
103
- } = {
104
- refs: [],
105
- props: [],
106
- idx: 0,
107
- prevTime: phraseStart,
108
- };
19
+ if (!words?.length) return;
20
+
21
+ const handler =
22
+ getCaptionStyleHandler(caption.capStyle ?? "") ?? getDefaultCaptionStyleHandler();
109
23
 
110
- // Set container properties
111
24
  containerRef().maxWidth(containerProps?.maxWidth ?? "95%");
112
25
  containerRef().wrap(containerProps?.wrap ?? "wrap");
113
26
  containerRef().justifyContent(containerProps?.justifyContent ?? "center");
114
27
  containerRef().alignItems(containerProps?.alignItems ?? "center");
115
28
 
116
- for (const word of words) {
117
- wordsState.props.push(caption.props);
118
- const textRef = createRef<Txt>();
119
- const captionProps = caption.props;
120
- containerRef().add(
121
- <Txt ref={textRef} {...captionProps} text={word.t} opacity={0} />
122
- );
123
- if (caption.capStyle == "highlight_bg") {
124
- const bgContainerRef = createRef();
125
- const childTextRef = createRef();
126
- const _color = new Color({...hexToRGB(captionProps.colors.bgColor), a: captionProps?.bgOpacity ?? 1});
127
- containerRef().add(
128
- <Rect
129
- ref={bgContainerRef}
130
- fill={_color}
131
- width={textRef().width() + (captionProps.bgOffsetWidth ?? 30)}
132
- height={textRef().height() + (captionProps.bgOffsetHeight ?? 10)}
133
- margin={captionProps.bgMargin ?? [0, -5]}
134
- radius={captionProps.bgRadius ?? 10}
135
- padding={captionProps.bgPadding ?? [0, 15]}
136
- opacity={0}
137
- alignItems={"center"}
138
- justifyContent={"center"}
139
- layout
140
- >
141
- <Txt ref={childTextRef} {...captionProps} text={word.t} />
142
- </Rect>
143
- );
144
- textRef().remove();
145
- wordsState.refs.push({
146
- bgRef: bgContainerRef,
147
- textRef: childTextRef,
148
- });
149
- } else {
150
- wordsState.refs.push({
151
- textRef: textRef,
152
- });
153
- }
154
- wordsState.prevTime = word.e;
155
- wordsState.idx = wordsState.idx + 1;
156
- }
29
+ const captionWithProps = {
30
+ ...caption,
31
+ props: caption.props ?? ({} as CaptionProps),
32
+ };
157
33
 
158
- wordsState.prevTime = phraseStart;
159
- wordsState.idx = 0;
34
+ const refs = handler.renderWords({
35
+ containerRef,
36
+ words,
37
+ caption: captionWithProps,
38
+ });
160
39
 
161
- for (const word of words) {
162
- if (caption.capStyle == "highlight_bg") {
163
- yield* wordsState.refs[wordsState.idx]?.bgRef?.().opacity(1, 0);
164
- yield* waitFor(Math.max(0, word.e - word.s));
165
- yield* wordsState.refs[wordsState.idx]
166
- ?.bgRef?.()
167
- .fill(TRANSPARENT_COLOR, 0);
168
- } else {
169
- yield* wordsState.refs[wordsState.idx]?.textRef?.().opacity(1, 0);
170
- yield* waitFor(Math.max(0, word.e - word.s));
171
- }
172
- wordsState.prevTime = word.e;
173
- wordsState.idx = wordsState.idx + 1;
174
- }
40
+ yield* handler.animateWords({
41
+ words,
42
+ refs,
43
+ caption: captionWithProps,
44
+ });
175
45
  },
176
46
  };
@@ -106,6 +106,122 @@ export const CAPTION_STYLE: Record<string, CaptionStyle> = {
106
106
  fontSize: 46,
107
107
  },
108
108
  },
109
+ outline_only: {
110
+ rect: {
111
+ alignItems: "center",
112
+ justifyContent: "center",
113
+ gap: 8,
114
+ },
115
+ word: {
116
+ lineWidth: 0.5,
117
+ stroke: "#000000",
118
+ fontWeight: 600,
119
+ strokeFirst: true,
120
+ shadowOffset: [0, 0],
121
+ shadowBlur: 0,
122
+ fontFamily: "Arial",
123
+ fill: "#FFFFFF",
124
+ fontSize: 42,
125
+ },
126
+ },
127
+ soft_box: {
128
+ rect: {
129
+ alignItems: "center",
130
+ justifyContent: "center",
131
+ gap: 8,
132
+ padding: [12, 24],
133
+ radius: 12,
134
+ },
135
+ word: {
136
+ lineWidth: 0.2,
137
+ stroke: "#000000",
138
+ fontWeight: 600,
139
+ strokeFirst: true,
140
+ shadowOffset: [-1, 1],
141
+ shadowColor: "rgba(0,0,0,0.3)",
142
+ shadowBlur: 3,
143
+ fontFamily: "Montserrat",
144
+ fill: "#FFFFFF",
145
+ fontSize: 40,
146
+ },
147
+ },
148
+ lower_third: {
149
+ rect: {
150
+ alignItems: "center",
151
+ justifyContent: "center",
152
+ gap: 8,
153
+ padding: [14, 32],
154
+ radius: 0,
155
+ },
156
+ word: {
157
+ lineWidth: 0.2,
158
+ stroke: "#000000",
159
+ fontWeight: 600,
160
+ strokeFirst: true,
161
+ shadowOffset: [0, 1],
162
+ shadowColor: "rgba(0,0,0,0.5)",
163
+ shadowBlur: 2,
164
+ fontFamily: "Arial",
165
+ fill: "#FFFFFF",
166
+ fontSize: 38,
167
+ },
168
+ },
169
+ typewriter: {
170
+ rect: {
171
+ alignItems: "center",
172
+ justifyContent: "center",
173
+ gap: 8,
174
+ },
175
+ word: {
176
+ lineWidth: 0.3,
177
+ stroke: "#000000",
178
+ fontWeight: 600,
179
+ strokeFirst: true,
180
+ shadowOffset: [0, 0],
181
+ shadowBlur: 0,
182
+ fontFamily: "Monaco",
183
+ fill: "#FFFFFF",
184
+ fontSize: 40,
185
+ },
186
+ },
187
+ karaoke: {
188
+ rect: {
189
+ alignItems: "center",
190
+ justifyContent: "center",
191
+ gap: 8,
192
+ },
193
+ word: {
194
+ lineWidth: 0.35,
195
+ stroke: "#000000",
196
+ fontWeight: 700,
197
+ strokeFirst: true,
198
+ shadowOffset: [-2, 2],
199
+ shadowColor: "#000000",
200
+ shadowBlur: 4,
201
+ fontFamily: "Bangers",
202
+ fill: "#FFFFFF",
203
+ fontSize: 46,
204
+ },
205
+ },
206
+ pop_scale: {
207
+ rect: {
208
+ alignItems: "center",
209
+ justifyContent: "center",
210
+ gap: 8,
211
+ },
212
+ word: {
213
+ lineWidth: 0.35,
214
+ stroke: "#000000",
215
+ fontWeight: 700,
216
+ strokeFirst: true,
217
+ shadowOffset: [-2, 2],
218
+ shadowColor: "#000000",
219
+ shadowBlur: 5,
220
+ fontFamily: "Bangers",
221
+ fill: "#FFFFFF",
222
+ fontSize: 46,
223
+ },
224
+ },
109
225
  };
110
226
 
111
227
  export const DEFAULT_CAPTION_COLORS = {
@@ -114,9 +230,9 @@ export const DEFAULT_CAPTION_COLORS = {
114
230
  };
115
231
 
116
232
  export const DEFAULT_CAPTION_FONT = {
117
- family: "Poppins",
118
- size: 48,
119
- weight: 400,
233
+ family: "Bangers",
234
+ size: 46,
235
+ weight: 700,
120
236
  };
121
237
 
122
238
  export const TRANSPARENT_COLOR = "#FFFFFF00";
@@ -180,9 +180,14 @@ export type CaptionProps = {
180
180
  * Defines text color, background color, and highlight colors.
181
181
  */
182
182
  export type CaptionColors = {
183
+ /** Main text fill color */
183
184
  text?: string;
185
+ /** Background color behind words/boxes */
184
186
  bgColor?: string;
187
+ /** Color used for per-word highlights (e.g. karaoke-style) */
185
188
  highlight?: string;
189
+ /** Stroke/outline color around text (e.g. classic outline style) */
190
+ outlineColor?: string;
186
191
  };
187
192
 
188
193
  /**
@@ -52,6 +52,18 @@ export { AudioElement } from './elements/audio.element';
52
52
  export { TextElement } from './elements/text.element';
53
53
  export { CaptionElement } from './elements/caption.element';
54
54
 
55
+ /**
56
+ * @group Caption Styles
57
+ * @description Plugin-based caption style handlers for extensible caption rendering
58
+ */
59
+ export {
60
+ registerCaptionStyle,
61
+ registerCaptionStyles,
62
+ getCaptionStyleHandler,
63
+ getDefaultCaptionStyleHandler,
64
+ } from './caption-styles';
65
+ export type { CaptionStyleHandler, WordRefs, WordTiming } from './caption-styles';
66
+
55
67
  // Shape and UI elements
56
68
  export { RectElement } from './elements/rect.element';
57
69
  export { CircleElement } from './elements/circle.element';