@cueframe/kernel 0.2.52 → 0.2.54

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 (71) hide show
  1. package/dist/_members/composition/fonts/bundledFaces.d.ts +9 -15
  2. package/dist/_members/composition/fonts/captionFaces.d.ts +2 -3
  3. package/dist/_members/composition/ops/captionOps.d.ts +11 -0
  4. package/dist/_members/composition/ops/compositionOps.d.ts +20 -19
  5. package/dist/_members/composition/ops/compositionOps.shared.d.ts +1 -0
  6. package/dist/_members/composition/validators/composition.d.ts +724 -0
  7. package/dist/_members/composition/validators/registry.d.ts +1 -1
  8. package/dist/_members/composition/validators/render.d.ts +332 -0
  9. package/dist/_members/composition-ops/index.d.ts +89 -0
  10. package/dist/_members/core/render/compositionAudioAndDepth.d.ts +3 -2
  11. package/dist/_members/core/render/index.d.ts +1 -1
  12. package/dist/_members/core/render/types.d.ts +3 -1
  13. package/dist/_members/plane/contract.d.ts +1 -1
  14. package/dist/_members/primitives/captions/Captions.d.ts +18 -1
  15. package/dist/_members/primitives/captions/index.d.ts +1 -1
  16. package/dist/_members/primitives/lib/text/spec.d.ts +1 -0
  17. package/dist/animate.js +2 -2
  18. package/dist/authoring/build-tools.js +6 -6
  19. package/dist/authoring/context.js +5 -5
  20. package/dist/authoring/tools.js +5 -5
  21. package/dist/authoring.js +9 -9
  22. package/dist/{chunk-MYBK6DKF.js → chunk-2BVKNYRB.js} +1 -1
  23. package/dist/{chunk-AVTEPAWI.js → chunk-7RGDRDAR.js} +17 -13
  24. package/dist/{chunk-7XZW26U2.js → chunk-AONWKKXK.js} +1 -1
  25. package/dist/{chunk-WZ22ZYXD.js → chunk-C3ARYFN6.js} +4 -4
  26. package/dist/{chunk-4B4V3HYW.js → chunk-C6ONUJIO.js} +1 -1
  27. package/dist/{chunk-CZOVEX2D.js → chunk-CR3G2JQP.js} +1 -1
  28. package/dist/{chunk-2PU2TSUG.js → chunk-D5E5XFHK.js} +1 -1
  29. package/dist/{chunk-D2EG7J7X.js → chunk-DFB7UFZD.js} +1 -1
  30. package/dist/{chunk-FSEB4LSU.js → chunk-FYMXVYWS.js} +1 -1
  31. package/dist/{chunk-6WQHVYJD.js → chunk-G7ZSFSQG.js} +1 -1
  32. package/dist/{chunk-5ZQLMPDY.js → chunk-GAACUQAB.js} +1 -1
  33. package/dist/{chunk-3I43W7DP.js → chunk-GZMKTLO3.js} +52 -21
  34. package/dist/{chunk-QVBUYWW6.js → chunk-Q2FWFTDZ.js} +1 -1
  35. package/dist/{chunk-MBSQKRDF.js → chunk-QCGT3M7M.js} +2 -2
  36. package/dist/{chunk-KMDZDH5E.js → chunk-QCJUGKRC.js} +4 -4
  37. package/dist/{chunk-Z6NY6MCL.js → chunk-RMLGMYEJ.js} +1 -1
  38. package/dist/{chunk-IHXMBXXL.js → chunk-SIGKJOSF.js} +30 -4
  39. package/dist/{chunk-5JCLZPMV.js → chunk-T7WKIMTB.js} +63 -44
  40. package/dist/{chunk-ACURZCPN.js → chunk-UKKY7HWN.js} +1 -1
  41. package/dist/{chunk-2M65KQNG.js → chunk-V6GL37RZ.js} +394 -54
  42. package/dist/{chunk-CV3QQUMK.js → chunk-VKZTGD5Z.js} +128 -5
  43. package/dist/{chunk-JWW54N24.js → chunk-VYSBZUXZ.js} +1 -1
  44. package/dist/{chunk-3OG2674K.js → chunk-ZDE5S3DE.js} +1 -1
  45. package/dist/component-runtime/render.js +2 -2
  46. package/dist/component-runtime.js +3 -3
  47. package/dist/compose.js +5 -5
  48. package/dist/composition/fonts.js +1 -1
  49. package/dist/composition/graphics-plan.js +3 -3
  50. package/dist/composition/ops.js +4 -4
  51. package/dist/composition/projection.js +3 -3
  52. package/dist/composition/validators.js +13 -3
  53. package/dist/composition-ops.js +8 -8
  54. package/dist/composition.js +15 -5
  55. package/dist/{context-2MBCDTCZ.js → context-I7SPXR2Z.js} +5 -5
  56. package/dist/core/render.js +4 -4
  57. package/dist/core.js +4 -4
  58. package/dist/mcp/adapter.js +2 -2
  59. package/dist/mcp/core.js +1 -1
  60. package/dist/mcp/scene-shot.js +2 -2
  61. package/dist/mcp/server.js +12 -12
  62. package/dist/mcp.js +12 -12
  63. package/dist/primitives/captions.js +276 -44
  64. package/dist/primitives/effects.js +3 -3
  65. package/dist/primitives/layouts.js +3 -3
  66. package/dist/primitives/lib/text/marker.js +1 -1
  67. package/dist/primitives/scenes.js +3 -3
  68. package/dist/primitives.js +5 -5
  69. package/dist/render-harness/cardFonts.js +2 -2
  70. package/dist/render-harness.js +7 -7
  71. package/package.json +10 -10
@@ -8,26 +8,26 @@ import {
8
8
  registerRawTools,
9
9
  registerSkillPrompts,
10
10
  titleCase
11
- } from "../chunk-WZ22ZYXD.js";
12
- import "../chunk-3OG2674K.js";
13
- import "../chunk-JWW54N24.js";
11
+ } from "../chunk-C3ARYFN6.js";
12
+ import "../chunk-ZDE5S3DE.js";
13
+ import "../chunk-VYSBZUXZ.js";
14
14
  import "../chunk-4WBSXHQA.js";
15
- import "../chunk-2M65KQNG.js";
16
- import "../chunk-IHXMBXXL.js";
17
- import "../chunk-3I43W7DP.js";
15
+ import "../chunk-V6GL37RZ.js";
16
+ import "../chunk-SIGKJOSF.js";
17
+ import "../chunk-GZMKTLO3.js";
18
18
  import "../chunk-QH3YI3ND.js";
19
19
  import "../chunk-JPMVNMF3.js";
20
20
  import "../chunk-IMIUHG2E.js";
21
21
  import "../chunk-VZ7TYX6Z.js";
22
22
  import "../chunk-T56BK7NS.js";
23
- import "../chunk-5JCLZPMV.js";
24
- import "../chunk-MBSQKRDF.js";
25
- import "../chunk-CV3QQUMK.js";
23
+ import "../chunk-T7WKIMTB.js";
24
+ import "../chunk-QCGT3M7M.js";
25
+ import "../chunk-VKZTGD5Z.js";
26
26
  import "../chunk-WPZKOVYF.js";
27
27
  import "../chunk-B7J5UUUV.js";
28
28
  import "../chunk-KYRDKK2D.js";
29
- import "../chunk-6WQHVYJD.js";
30
- import "../chunk-Z6NY6MCL.js";
29
+ import "../chunk-G7ZSFSQG.js";
30
+ import "../chunk-RMLGMYEJ.js";
31
31
  import "../chunk-WREYUH2E.js";
32
32
  import "../chunk-ILO4NEZ3.js";
33
33
  import "../chunk-E63UEO5C.js";
@@ -35,7 +35,7 @@ import "../chunk-7PA7FGLD.js";
35
35
  import "../chunk-VG2ND6PX.js";
36
36
  import "../chunk-CL5Y6V6K.js";
37
37
  import "../chunk-2L7PYYJK.js";
38
- import "../chunk-AVTEPAWI.js";
38
+ import "../chunk-7RGDRDAR.js";
39
39
  import "../chunk-2ESYSVXG.js";
40
40
  export {
41
41
  RAW_EXCLUDED_OPERATION_IDS,
package/dist/mcp.js CHANGED
@@ -8,26 +8,26 @@ import {
8
8
  buildServer,
9
9
  registerCuratedTools,
10
10
  registerRawTools
11
- } from "./chunk-WZ22ZYXD.js";
12
- import "./chunk-3OG2674K.js";
13
- import "./chunk-JWW54N24.js";
11
+ } from "./chunk-C3ARYFN6.js";
12
+ import "./chunk-ZDE5S3DE.js";
13
+ import "./chunk-VYSBZUXZ.js";
14
14
  import "./chunk-4WBSXHQA.js";
15
- import "./chunk-2M65KQNG.js";
16
- import "./chunk-IHXMBXXL.js";
17
- import "./chunk-3I43W7DP.js";
15
+ import "./chunk-V6GL37RZ.js";
16
+ import "./chunk-SIGKJOSF.js";
17
+ import "./chunk-GZMKTLO3.js";
18
18
  import "./chunk-QH3YI3ND.js";
19
19
  import "./chunk-JPMVNMF3.js";
20
20
  import "./chunk-IMIUHG2E.js";
21
21
  import "./chunk-VZ7TYX6Z.js";
22
22
  import "./chunk-T56BK7NS.js";
23
- import "./chunk-5JCLZPMV.js";
24
- import "./chunk-MBSQKRDF.js";
25
- import "./chunk-CV3QQUMK.js";
23
+ import "./chunk-T7WKIMTB.js";
24
+ import "./chunk-QCGT3M7M.js";
25
+ import "./chunk-VKZTGD5Z.js";
26
26
  import "./chunk-WPZKOVYF.js";
27
27
  import "./chunk-B7J5UUUV.js";
28
28
  import "./chunk-KYRDKK2D.js";
29
- import "./chunk-6WQHVYJD.js";
30
- import "./chunk-Z6NY6MCL.js";
29
+ import "./chunk-G7ZSFSQG.js";
30
+ import "./chunk-RMLGMYEJ.js";
31
31
  import "./chunk-WREYUH2E.js";
32
32
  import "./chunk-ILO4NEZ3.js";
33
33
  import "./chunk-E63UEO5C.js";
@@ -35,7 +35,7 @@ import "./chunk-7PA7FGLD.js";
35
35
  import "./chunk-VG2ND6PX.js";
36
36
  import "./chunk-CL5Y6V6K.js";
37
37
  import "./chunk-2L7PYYJK.js";
38
- import "./chunk-AVTEPAWI.js";
38
+ import "./chunk-7RGDRDAR.js";
39
39
  import "./chunk-2ESYSVXG.js";
40
40
  export {
41
41
  API_URLS,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Marker
3
- } from "../chunk-D2EG7J7X.js";
3
+ } from "../chunk-DFB7UFZD.js";
4
4
  import {
5
5
  fillStyle
6
6
  } from "../chunk-XLIPPLG3.js";
@@ -13,13 +13,13 @@ import {
13
13
  } from "../chunk-W2245F6G.js";
14
14
  import {
15
15
  resolveCaptionFaces
16
- } from "../chunk-AVTEPAWI.js";
16
+ } from "../chunk-7RGDRDAR.js";
17
17
  import "../chunk-2ESYSVXG.js";
18
18
 
19
19
  // ../execution/primitives/src/captions/Captions.tsx
20
20
  import { useMemo } from "react";
21
21
  import { AbsoluteFill, useCurrentFrame, useVideoConfig, interpolate, Easing } from "remotion";
22
- import { fillTextBox } from "@remotion/layout-utils";
22
+ import { fillTextBox, measureText } from "@remotion/layout-utils";
23
23
 
24
24
  // ../execution/primitives/src/captions/paginate.ts
25
25
  import { createTikTokStyleCaptions } from "@remotion/captions";
@@ -154,7 +154,32 @@ function plateStyleFor(kind, backgroundColor) {
154
154
  }
155
155
  var COMBINE_TOKENS_MS = 1500;
156
156
  var CAPTION_LINGER_MS = 600;
157
- function addTokenToBox(box, token, fontFamily, fontSize, fontWeight, emphasis) {
157
+ function measuredTextBox(maxWidth, maxLines, gapPx) {
158
+ const lineWidths = [0];
159
+ return {
160
+ add: (word) => {
161
+ const widthOf = (text) => measureText({ ...word, text }).width;
162
+ const current = lineWidths.length - 1;
163
+ const width = widthOf(lineWidths[current] === 0 ? word.text.trimStart() : word.text);
164
+ const nextWidth = lineWidths[current] + (lineWidths[current] ? gapPx : 0) + width;
165
+ if (Math.ceil(nextWidth) < maxWidth) {
166
+ lineWidths[current] = nextWidth;
167
+ return { exceedsBox: false, newLine: false };
168
+ }
169
+ const firstWidth = widthOf(word.text.trimStart());
170
+ if (current + 1 >= maxLines || Math.ceil(firstWidth) >= maxWidth) {
171
+ return { exceedsBox: true, newLine: false };
172
+ }
173
+ lineWidths.push(firstWidth);
174
+ return { exceedsBox: false, newLine: true };
175
+ }
176
+ };
177
+ }
178
+ function tokenBox(maxWidth, maxLines, fontSize, letterSpacing, wordGapEm, textTransform, emphasis) {
179
+ const needsMeasured = emphasis?.emphasisFontStyle === "italic" || letterSpacing !== 0 || (emphasis?.emphasisLetterSpacing ?? 0) !== 0 || wordGapEm !== void 0 || textTransform !== void 0 && textTransform !== "none" || emphasis?.emphasisTextTransform !== void 0 && emphasis.emphasisTextTransform !== "none";
180
+ return needsMeasured ? measuredTextBox(maxWidth, maxLines, fontSize * (wordGapEm ?? 0.35)) : fillTextBox({ maxBoxWidth: maxWidth, maxLines });
181
+ }
182
+ function addTokenToBox(box, token, fontFamily, fontSize, fontWeight, letterSpacing, textTransform, emphasis) {
158
183
  const isEmp = emphasis?.emphasisSet.has(token.fromMs) ?? false;
159
184
  const tokenSize = isEmp ? Math.round(fontSize * (emphasis?.emphasisScale ?? 1.2)) : fontSize;
160
185
  const tokenWeight = emphasis && isEmp ? emphasis.emphasisFontWeight : fontWeight;
@@ -163,22 +188,25 @@ function addTokenToBox(box, token, fontFamily, fontSize, fontWeight, emphasis) {
163
188
  text: token.text,
164
189
  fontFamily: tokenFamily,
165
190
  fontSize: tokenSize,
166
- fontWeight: String(tokenWeight)
191
+ fontWeight: String(tokenWeight),
192
+ ...(isEmp ? emphasis?.emphasisLetterSpacing : letterSpacing) ? { letterSpacing: `${isEmp ? emphasis?.emphasisLetterSpacing : letterSpacing}px` } : {},
193
+ ...isEmp ? { textTransform: emphasis?.emphasisTextTransform } : { textTransform },
194
+ ...isEmp && emphasis?.emphasisFontStyle === "italic" ? { additionalStyles: { fontStyle: "italic" } } : {}
167
195
  });
168
196
  }
169
- function makeBoxFactory(maxWidth, maxLines, fontFamily, fontSize, fontWeight, emphasis) {
197
+ function makeBoxFactory(maxWidth, maxLines, fontFamily, fontSize, fontWeight, letterSpacing, wordGapEm, textTransform, emphasis) {
170
198
  return () => {
171
- const box = fillTextBox({ maxBoxWidth: maxWidth, maxLines });
199
+ const box = tokenBox(maxWidth, maxLines, fontSize, letterSpacing, wordGapEm, textTransform, emphasis);
172
200
  return {
173
- add: (token) => !addTokenToBox(box, token, fontFamily, fontSize, fontWeight, emphasis).exceedsBox
201
+ add: (token) => !addTokenToBox(box, token, fontFamily, fontSize, fontWeight, letterSpacing, textTransform, emphasis).exceedsBox
174
202
  };
175
203
  };
176
204
  }
177
- function layoutTokensIntoLines(tokens, maxWidth, maxLines, fontFamily, fontSize, fontWeight, emphasis) {
178
- const box = fillTextBox({ maxBoxWidth: maxWidth, maxLines });
205
+ function layoutTokensIntoLines(tokens, maxWidth, maxLines, fontFamily, fontSize, fontWeight, letterSpacing, wordGapEm, textTransform, emphasis) {
206
+ const box = tokenBox(maxWidth, maxLines, fontSize, letterSpacing, wordGapEm, textTransform, emphasis);
179
207
  const lines = [[]];
180
208
  for (const token of tokens) {
181
- const result = addTokenToBox(box, token, fontFamily, fontSize, fontWeight, emphasis);
209
+ const result = addTokenToBox(box, token, fontFamily, fontSize, fontWeight, letterSpacing, textTransform, emphasis);
182
210
  if (result.newLine) {
183
211
  lines.push([token]);
184
212
  } else {
@@ -187,10 +215,73 @@ function layoutTokensIntoLines(tokens, maxWidth, maxLines, fontFamily, fontSize,
187
215
  }
188
216
  return lines.filter((line) => line.length > 0);
189
217
  }
218
+ function layoutFitError(label, detail) {
219
+ const error = new Error(`Caption layout${label ? ` ${label}` : " word range"} does not fit one page in its region${detail ? ` (${detail})` : ""}; enlarge the region or split the range`);
220
+ error.name = "CaptionLayoutFitError";
221
+ return error;
222
+ }
223
+ function tokenRuns(tokens, emphasisSet, emphasisPlate) {
224
+ const runs = [];
225
+ for (const token of tokens) {
226
+ const emph = emphasisSet.has(token.fromMs);
227
+ const last = runs[runs.length - 1];
228
+ if (emphasisPlate && emph && last?.emph) last.tokens.push(token);
229
+ else runs.push({ tokens: [token], emph });
230
+ }
231
+ return runs;
232
+ }
233
+ function measureVisualRun(run, fontSize, style, faces) {
234
+ const plate = style.emphasisPlate ?? false;
235
+ const emph = run.emph;
236
+ const text = emph && plate ? run.tokens.map((token) => token.text.trim()).join(" ") : plate ? run.tokens[0].text.trim() : run.tokens[0].text;
237
+ const size = emph ? Math.round(fontSize * (style.emphasisScale ?? 1.08)) : fontSize;
238
+ const transform = emph ? style.emphasisTextTransform ?? style.textTransform : style.textTransform;
239
+ const letterSpacing = emph ? style.emphasisLetterSpacing ?? style.letterSpacing : style.letterSpacing;
240
+ return measureText({
241
+ text,
242
+ fontFamily: emph ? faces.emphasis.stack : faces.base.stack,
243
+ fontSize: size,
244
+ fontWeight: String(emph ? faces.emphasis.weight : faces.base.weight),
245
+ ...transform ? { textTransform: transform } : {},
246
+ ...letterSpacing !== void 0 ? { letterSpacing: `${letterSpacing}px` } : {},
247
+ ...emph && faces.emphasis.style === "italic" ? { additionalStyles: { fontStyle: "italic" } } : {}
248
+ }).width + (emph && plate ? 2 * Math.round(size * (style.emphasisPlatePadX ?? 0.16)) : 0);
249
+ }
250
+ function layoutAuthoredTokensIntoLines(tokens, maxWidth, maxLines, fontSize, style, emphasisSet, faces, label) {
251
+ const lines = [[]];
252
+ let lineWidth = 0;
253
+ const plate = style.emphasisPlate ?? false;
254
+ const runGap = fontSize * (style.wordGapEm ?? 0.35);
255
+ for (const run of tokenRuns(tokens, emphasisSet, plate)) {
256
+ const width = measureVisualRun(run, fontSize, style, faces);
257
+ if (Math.ceil(width) > maxWidth) {
258
+ throw layoutFitError(label, `run needs ${Math.ceil(width)}px, box has ${Math.floor(maxWidth)}px`);
259
+ }
260
+ const current = lines[lines.length - 1];
261
+ const nextWidth = lineWidth + (current.length ? runGap : 0) + width;
262
+ if (Math.ceil(nextWidth) > maxWidth) {
263
+ if (lines.length >= maxLines) {
264
+ throw layoutFitError(label, `needs ${lines.length + 1} lines, box allows ${maxLines}`);
265
+ }
266
+ lines.push([...run.tokens]);
267
+ lineWidth = width;
268
+ } else {
269
+ current.push(...run.tokens);
270
+ lineWidth = nextWidth;
271
+ }
272
+ }
273
+ return lines.filter((line) => line.length > 0);
274
+ }
190
275
  function Captions({
191
276
  segments,
192
277
  style: userStyle,
193
- region
278
+ region,
279
+ holdUntilMs,
280
+ scale,
281
+ rotation,
282
+ syncWordVisibility,
283
+ singlePageRequired,
284
+ layoutLabel
194
285
  }) {
195
286
  const frame = useCurrentFrame();
196
287
  const { fps, width, height } = useVideoConfig();
@@ -206,7 +297,8 @@ function Captions({
206
297
  const emphasisFontFamily = faces.emphasis.stack;
207
298
  const emphasisFontWeight = faces.emphasis.weight;
208
299
  const emphasisTextTransform = style.emphasisTextTransform ?? style.textTransform;
209
- const emphasisLetterSpacing = style.emphasisLetterSpacing ?? 0;
300
+ const letterSpacing = style.letterSpacing ?? 0;
301
+ const emphasisLetterSpacing = style.emphasisLetterSpacing ?? letterSpacing;
210
302
  const emphasisFontStyle = faces.emphasis.style;
211
303
  const entrance = style.entrance ?? "none";
212
304
  const entranceFrames = Math.max(1, Math.round((style.entranceMs ?? 160) / 1e3 * fps));
@@ -220,17 +312,25 @@ function Captions({
220
312
  const plateRadius = style.emphasisPlateRadius ?? 6;
221
313
  const platePadX = style.emphasisPlatePadX ?? 0.16;
222
314
  const platePadY = style.emphasisPlatePadY ?? 0.04;
315
+ const plateReveal = style.emphasisPlateReveal ?? false;
223
316
  const annotationColor = style.annotationColor ?? VIOLET;
317
+ const annotationStrokeWidth = style.annotationStrokeWidth ?? 4;
224
318
  const maxWidthPercent = style.maxWidthPercent ?? 80;
225
319
  const plateStyle = plateStyleFor(style.plate ?? "glass", style.backgroundColor);
226
320
  const fontSize = Math.round(height * (style.fontSize / 100));
321
+ const wordGapPx = fontSize * (style.wordGapEm ?? 0.35);
322
+ const lineGapPx = fontSize * (style.lineGapEm ?? 0.25);
227
323
  const authoredRegion = userStyle?.region;
228
324
  const regionWidth = authoredRegion ? width * authoredRegion.w : width;
229
325
  const maxTextWidth = authoredRegion ? Math.min(width * (maxWidthPercent / 100), regionWidth - fontSize * 1.6) : width * (maxWidthPercent / 100);
230
326
  const availableHeight = authoredRegion ? height * authoredRegion.h - fontSize * 0.8 : Infinity;
231
327
  const lineHeight = fontSize * Math.max(1, emphasisScale) * 1.2;
232
- const maxLines = authoredRegion ? Math.min(3, Math.floor((availableHeight + fontSize * 0.25) / (lineHeight + fontSize * 0.25))) : 3;
328
+ const maxLines = authoredRegion ? Math.min(3, Math.floor((availableHeight + lineGapPx) / (lineHeight + lineGapPx))) : 3;
233
329
  if (authoredRegion && (maxTextWidth <= 0 || maxLines < 1)) {
330
+ if (singlePageRequired) throw layoutFitError(
331
+ layoutLabel,
332
+ maxTextWidth <= 0 ? `box has ${Math.floor(maxTextWidth)}px text width` : "box allows no complete line"
333
+ );
234
334
  throw new Error("Caption style.region is too small for one caption line at the authored font size");
235
335
  }
236
336
  const { emphasisStartMs, annotateKind } = useMemo(() => {
@@ -243,16 +343,78 @@ function Captions({
243
343
  return { emphasisStartMs: emph, annotateKind: ann };
244
344
  }, [segments]);
245
345
  const emphasisLayout = useMemo(
246
- () => emphasisStartMs.size > 0 ? { emphasisSet: emphasisStartMs, emphasisScale, emphasisFontFamily, emphasisFontWeight } : void 0,
247
- [emphasisStartMs, emphasisScale, emphasisFontFamily, emphasisFontWeight]
346
+ () => emphasisStartMs.size > 0 ? {
347
+ emphasisSet: emphasisStartMs,
348
+ emphasisScale,
349
+ emphasisFontFamily,
350
+ emphasisFontWeight,
351
+ emphasisFontStyle,
352
+ emphasisLetterSpacing,
353
+ emphasisTextTransform
354
+ } : void 0,
355
+ [
356
+ emphasisStartMs,
357
+ emphasisScale,
358
+ emphasisFontFamily,
359
+ emphasisFontWeight,
360
+ emphasisFontStyle,
361
+ emphasisLetterSpacing,
362
+ emphasisTextTransform
363
+ ]
248
364
  );
249
365
  const pages = useMemo(
250
- () => paginateCaptionSegments(segments, {
251
- combineMs: COMBINE_TOKENS_MS,
252
- makeBox: makeBoxFactory(maxTextWidth, maxLines, style.fontFamily, fontSize, style.fontWeight, emphasisLayout),
253
- lingerMs: CAPTION_LINGER_MS
254
- }),
255
- [segments, maxTextWidth, maxLines, fontSize, style.fontFamily, style.fontWeight, emphasisLayout]
366
+ () => {
367
+ const makeBox = makeBoxFactory(
368
+ maxTextWidth,
369
+ maxLines,
370
+ style.fontFamily,
371
+ fontSize,
372
+ style.fontWeight,
373
+ letterSpacing,
374
+ style.wordGapEm,
375
+ style.textTransform,
376
+ emphasisLayout
377
+ );
378
+ const paginated = paginateCaptionSegments(segments, {
379
+ combineMs: singlePageRequired ? Number.MAX_SAFE_INTEGER : COMBINE_TOKENS_MS,
380
+ makeBox: singlePageRequired ? () => ({ add: () => true }) : makeBox,
381
+ lingerMs: CAPTION_LINGER_MS
382
+ });
383
+ if (singlePageRequired) {
384
+ if (paginated.length !== 1) {
385
+ throw layoutFitError(layoutLabel);
386
+ }
387
+ layoutAuthoredTokensIntoLines(
388
+ paginated[0].tokens,
389
+ maxTextWidth,
390
+ maxLines,
391
+ fontSize,
392
+ style,
393
+ emphasisStartMs,
394
+ faces,
395
+ layoutLabel
396
+ );
397
+ }
398
+ if (holdUntilMs !== void 0 && paginated.length > 0) {
399
+ const last = paginated[paginated.length - 1];
400
+ last.durationMs = Math.max(1, holdUntilMs - last.startMs);
401
+ }
402
+ return paginated;
403
+ },
404
+ [
405
+ segments,
406
+ maxTextWidth,
407
+ maxLines,
408
+ fontSize,
409
+ style,
410
+ emphasisStartMs,
411
+ faces,
412
+ emphasisLayout,
413
+ letterSpacing,
414
+ holdUntilMs,
415
+ singlePageRequired,
416
+ layoutLabel
417
+ ]
256
418
  );
257
419
  if (pages.length === 0) return null;
258
420
  const currentTimeMs = frame / fps * 1e3;
@@ -277,16 +439,32 @@ function Captions({
277
439
  extrapolateLeft: "clamp",
278
440
  extrapolateRight: "clamp"
279
441
  }) : 1;
280
- const lines = layoutTokensIntoLines(
442
+ const lines = singlePageRequired ? layoutAuthoredTokensIntoLines(
443
+ currentPage.tokens,
444
+ maxTextWidth,
445
+ maxLines,
446
+ fontSize,
447
+ style,
448
+ emphasisStartMs,
449
+ faces,
450
+ layoutLabel
451
+ ) : layoutTokensIntoLines(
281
452
  currentPage.tokens,
282
453
  maxTextWidth,
283
454
  maxLines,
284
455
  style.fontFamily,
285
456
  fontSize,
286
457
  style.fontWeight,
458
+ letterSpacing,
459
+ style.wordGapEm,
460
+ style.textTransform,
287
461
  emphasisLayout
288
462
  );
289
463
  const wrapperStyle = resolveCaptionPlacement(userStyle?.position, region, authoredRegion);
464
+ if (scale !== void 0 || rotation !== void 0) {
465
+ wrapperStyle.transform = `${wrapperStyle.transform ?? ""} rotate(${rotation ?? 0}deg) scale(${scale ?? 1})`.trim();
466
+ wrapperStyle.transformOrigin = "top left";
467
+ }
290
468
  return /* @__PURE__ */ React.createElement(AbsoluteFill, { style: { pointerEvents: "none" } }, /* @__PURE__ */ React.createElement("div", { style: wrapperStyle }, /* @__PURE__ */ React.createElement(
291
469
  "div",
292
470
  {
@@ -295,7 +473,7 @@ function Captions({
295
473
  display: "flex",
296
474
  flexDirection: "column",
297
475
  alignItems: authoredRegion ? "flex-start" : "center",
298
- gap: `${fontSize * 0.25}px`,
476
+ gap: `${lineGapPx}px`,
299
477
  padding: `${fontSize * 0.4}px ${fontSize * 0.8}px`,
300
478
  borderRadius: fontSize * 0.5,
301
479
  ...plateStyle,
@@ -315,8 +493,8 @@ function Captions({
315
493
  if (entrance === "none") return { opacity: 1, scale: 1, y: 0 };
316
494
  const start = tokenStartFrame.get(first) ?? 0;
317
495
  if (entrance === "word-pop") {
318
- const { opacity, scale } = wordPop(frame, fps, start, entranceFrames, isEmph ? 0.86 : 0.92);
319
- return { opacity, scale, y: 0 };
496
+ const { opacity, scale: scale2 } = wordPop(frame, fps, start, entranceFrames, isEmph ? 0.86 : 0.92);
497
+ return { opacity, scale: scale2, y: 0 };
320
498
  }
321
499
  if (entrance === "stagger-up") {
322
500
  const y = interpolate(frame, [start, start + entranceFrames], [RISE_PX, 0], {
@@ -332,21 +510,18 @@ function Captions({
332
510
  return { opacity: 1, scale: 1, y: 0 };
333
511
  };
334
512
  return lines.map((line, lineIdx) => {
335
- const segs = [];
336
- for (const t of line) {
337
- const e = emphasisStartMs.has(t.fromMs);
338
- const last = segs[segs.length - 1];
339
- if (emphasisPlate && e && last && last.emph) last.tokens.push(t);
340
- else segs.push({ tokens: [t], emph: e });
341
- }
342
- return /* @__PURE__ */ React.createElement("div", { key: lineIdx, style: { display: "flex", justifyContent: authoredRegion ? "flex-start" : "center", alignItems: "baseline", gap: `${fontSize * 0.35}px` } }, segs.map((seg, si) => {
513
+ const segs = tokenRuns(line, emphasisStartMs, emphasisPlate);
514
+ return /* @__PURE__ */ React.createElement("div", { key: lineIdx, style: { display: "flex", justifyContent: authoredRegion ? "flex-start" : "center", alignItems: "baseline", gap: `${wordGapPx}px` } }, segs.map((seg, si) => {
343
515
  const first = seg.tokens[0];
344
- const { opacity, scale, y } = entranceOf(first, seg.emph);
345
- const entranceTransform = `translateY(${y}px) scale(${scale})`;
346
- const annKind = seg.tokens.map((t) => annotateKind.get(t.fromMs)).find(Boolean);
516
+ const entered = entranceOf(first, seg.emph);
517
+ const opacity = syncWordVisibility && currentTimeMs < first.fromMs ? 0 : entered.opacity;
518
+ const { scale: scale2, y } = entered;
519
+ const entranceTransform = `translateY(${y}px) scale(${scale2})`;
520
+ const annotatedToken = seg.tokens.find((token2) => annotateKind.has(token2.fromMs));
521
+ const annKind = annotatedToken ? annotateKind.get(annotatedToken.fromMs) : void 0;
347
522
  let markerP = 1;
348
- if (annKind && entrance !== "none") {
349
- const ms = (tokenStartFrame.get(first) ?? 0) + Math.round(fps * 0.14);
523
+ if (annKind && annotatedToken && entrance !== "none") {
524
+ const ms = (tokenStartFrame.get(annotatedToken) ?? 0) + Math.round(fps * 0.14);
350
525
  markerP = interpolate(frame, [ms, ms + Math.round(fps * 0.32)], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
351
526
  }
352
527
  if (seg.emph && emphasisPlate) {
@@ -354,7 +529,16 @@ function Captions({
354
529
  const bg = idx % 2 === 0 ? plateColor : plateAltColor;
355
530
  const fg = idx % 2 === 0 ? plateTextColor : plateAltTextColor;
356
531
  const efs = Math.round(fontSize * emphasisScale);
357
- return /* @__PURE__ */ React.createElement("span", { key: si, style: { position: "relative", display: "inline-block", opacity, transform: entranceTransform, transformOrigin: "center" } }, /* @__PURE__ */ React.createElement(
532
+ const plateTokens = plateReveal ? seg.tokens.filter((token2) => currentTimeMs >= token2.fromMs) : seg.tokens;
533
+ const reservedWidth = plateReveal ? measureVisualRun(seg, fontSize, style, faces) : void 0;
534
+ return /* @__PURE__ */ React.createElement("span", { key: si, style: {
535
+ position: "relative",
536
+ display: "inline-block",
537
+ ...reservedWidth !== void 0 ? { width: reservedWidth } : {},
538
+ opacity,
539
+ transform: entranceTransform,
540
+ transformOrigin: "center"
541
+ } }, /* @__PURE__ */ React.createElement(
358
542
  "span",
359
543
  {
360
544
  style: {
@@ -373,8 +557,10 @@ function Captions({
373
557
  whiteSpace: "nowrap"
374
558
  }
375
559
  },
376
- seg.tokens.map((t) => t.text.trim()).join(" ")
377
- ), annKind ? /* @__PURE__ */ React.createElement(Marker, { spec: { kind: annKind, color: annotationColor }, progress: markerP }) : null);
560
+ plateTokens.map((token2, tokenIndex) => /* @__PURE__ */ React.createElement("span", { key: token2.fromMs, style: {
561
+ opacity: syncWordVisibility && tokenIndex > 0 ? currentTimeMs < token2.fromMs ? 0 : entranceOf(token2, true).opacity : 1
562
+ } }, tokenIndex > 0 ? " " : "", token2.text.trim()))
563
+ ), annKind && annotatedToken && currentTimeMs >= annotatedToken.fromMs ? /* @__PURE__ */ React.createElement(Marker, { spec: { kind: annKind, color: annotationColor, strokeWidth: annotationStrokeWidth }, progress: markerP }) : null);
378
564
  }
379
565
  const token = first;
380
566
  const isActive = token === activeToken;
@@ -383,7 +569,7 @@ function Captions({
383
569
  const tokenWeight = isEmphasis ? emphasisFontWeight : style.fontWeight;
384
570
  const tokenFamily = isEmphasis ? emphasisFontFamily : style.fontFamily;
385
571
  const tokenTransform = isEmphasis ? emphasisTextTransform : style.textTransform;
386
- const tokenLetterSpacing = isEmphasis && emphasisLetterSpacing ? `${emphasisLetterSpacing}px` : void 0;
572
+ const tokenLetterSpacing = isEmphasis ? emphasisLetterSpacing ? `${emphasisLetterSpacing}px` : void 0 : letterSpacing ? `${letterSpacing}px` : void 0;
387
573
  const tokenFontStyle = isEmphasis ? emphasisFontStyle : "normal";
388
574
  let tokenColor = style.color;
389
575
  if (isActive && !emphasisPlate) tokenColor = style.highlightColor;
@@ -406,12 +592,58 @@ function Captions({
406
592
  },
407
593
  emphasisPlate ? token.text.trim() : token.text
408
594
  );
409
- return /* @__PURE__ */ React.createElement("span", { key: si, style: { position: "relative", display: "inline-block", opacity, transform: entranceTransform, transformOrigin: "center" } }, inner, annKind ? /* @__PURE__ */ React.createElement(Marker, { spec: { kind: annKind, color: annotationColor }, progress: markerP }) : null);
595
+ return /* @__PURE__ */ React.createElement("span", { key: si, style: { position: "relative", display: "inline-block", opacity, transform: entranceTransform, transformOrigin: "center" } }, inner, annKind && annotatedToken && currentTimeMs >= annotatedToken.fromMs ? /* @__PURE__ */ React.createElement(Marker, { spec: { kind: annKind, color: annotationColor, strokeWidth: annotationStrokeWidth }, progress: markerP }) : null);
410
596
  }));
411
597
  });
412
598
  })()
413
599
  )));
414
600
  }
601
+ function CaptionLayoutLayer({
602
+ segments,
603
+ layouts,
604
+ style,
605
+ region
606
+ }) {
607
+ if (!layouts?.length) return /* @__PURE__ */ React.createElement(Captions, { segments, style, region });
608
+ const placed = /* @__PURE__ */ new Set();
609
+ for (const layout of layouts) {
610
+ for (let word = layout.wordStartIndex; word < layout.wordEndIndex; word++) {
611
+ placed.add(`${layout.segmentIndex}:${word}`);
612
+ }
613
+ }
614
+ const remaining = segments.map((words, segmentIndex) => words.filter((_, wordIndex) => !placed.has(`${segmentIndex}:${wordIndex}`)));
615
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Captions, { segments: remaining, style, region }), layouts.map((layout) => {
616
+ const source = segments[layout.segmentIndex];
617
+ if (!source) return null;
618
+ const marks = new Map((layout.wordMarks ?? []).map((mark) => [mark.wordIndex, mark]));
619
+ const words = source.slice(layout.wordStartIndex, layout.wordEndIndex).map((word, offset) => {
620
+ const mark = marks.get(layout.wordStartIndex + offset);
621
+ if (!mark) return word;
622
+ const updated = { ...word };
623
+ if (mark.emphasis !== void 0) updated.emphasis = mark.emphasis;
624
+ if (mark.annotate === null) delete updated.annotate;
625
+ else if (mark.annotate !== void 0) updated.annotate = mark.annotate;
626
+ return updated;
627
+ });
628
+ const hold = source[layout.holdThroughWordIndex ?? layout.wordEndIndex - 1];
629
+ return /* @__PURE__ */ React.createElement(
630
+ Captions,
631
+ {
632
+ key: `${layout.segmentIndex}:${layout.wordStartIndex}:${layout.wordEndIndex}`,
633
+ segments: [words],
634
+ style: { ...style, ...layout.style, region: layout.region },
635
+ region,
636
+ holdUntilMs: hold?.endMs,
637
+ scale: layout.scale,
638
+ rotation: layout.rotation,
639
+ syncWordVisibility: true,
640
+ singlePageRequired: true,
641
+ layoutLabel: `segment ${layout.segmentIndex} words ${layout.wordStartIndex}..${layout.wordEndIndex - 1}`
642
+ }
643
+ );
644
+ }));
645
+ }
415
646
  export {
647
+ CaptionLayoutLayer,
416
648
  Captions
417
649
  };
@@ -12,15 +12,15 @@ import {
12
12
  Vignette,
13
13
  catalog,
14
14
  laneDepths
15
- } from "../chunk-5ZQLMPDY.js";
15
+ } from "../chunk-GAACUQAB.js";
16
16
  import "../chunk-MFHXCHRI.js";
17
17
  import "../chunk-XHGZ2E77.js";
18
18
  import "../chunk-W2245F6G.js";
19
- import "../chunk-2PU2TSUG.js";
19
+ import "../chunk-D5E5XFHK.js";
20
20
  import "../chunk-XV2K3AQT.js";
21
21
  import "../chunk-CL5Y6V6K.js";
22
22
  import "../chunk-2L7PYYJK.js";
23
- import "../chunk-AVTEPAWI.js";
23
+ import "../chunk-7RGDRDAR.js";
24
24
  import "../chunk-2ESYSVXG.js";
25
25
  export {
26
26
  Blur,
@@ -8,14 +8,14 @@ import {
8
8
  SplitScreen,
9
9
  StaggeredBentoGrid,
10
10
  catalog
11
- } from "../chunk-CZOVEX2D.js";
11
+ } from "../chunk-CR3G2JQP.js";
12
12
  import "../chunk-XHGZ2E77.js";
13
13
  import "../chunk-W2245F6G.js";
14
- import "../chunk-2PU2TSUG.js";
14
+ import "../chunk-D5E5XFHK.js";
15
15
  import "../chunk-XV2K3AQT.js";
16
16
  import "../chunk-CL5Y6V6K.js";
17
17
  import "../chunk-2L7PYYJK.js";
18
- import "../chunk-AVTEPAWI.js";
18
+ import "../chunk-7RGDRDAR.js";
19
19
  import "../chunk-2ESYSVXG.js";
20
20
  export {
21
21
  ChatToPreviewLayout,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Marker
3
- } from "../../../chunk-D2EG7J7X.js";
3
+ } from "../../../chunk-DFB7UFZD.js";
4
4
  import "../../../chunk-2ESYSVXG.js";
5
5
  export {
6
6
  Marker
@@ -8,17 +8,17 @@ import {
8
8
  ProductLaunchTrailer,
9
9
  TerminalToBrowserDeploy,
10
10
  catalog
11
- } from "../chunk-4B4V3HYW.js";
11
+ } from "../chunk-C6ONUJIO.js";
12
12
  import "../chunk-S5H6GH6M.js";
13
13
  import "../chunk-QAGGJHAF.js";
14
14
  import "../chunk-MFHXCHRI.js";
15
15
  import "../chunk-QGAWDLYX.js";
16
16
  import "../chunk-W2245F6G.js";
17
- import "../chunk-2PU2TSUG.js";
17
+ import "../chunk-D5E5XFHK.js";
18
18
  import "../chunk-XV2K3AQT.js";
19
19
  import "../chunk-CL5Y6V6K.js";
20
20
  import "../chunk-2L7PYYJK.js";
21
- import "../chunk-AVTEPAWI.js";
21
+ import "../chunk-7RGDRDAR.js";
22
22
  import "../chunk-2ESYSVXG.js";
23
23
  export {
24
24
  ChangelogBite,