@shotstack/shotstack-canvas 2.1.5 → 2.1.7

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.
@@ -271,11 +271,6 @@ declare const richCaptionAssetSchema: z.ZodObject<{
271
271
  color: z.ZodDefault<z.ZodString>;
272
272
  opacity: z.ZodDefault<z.ZodNumber>;
273
273
  background: z.ZodOptional<z.ZodString>;
274
- textDecoration: z.ZodDefault<z.ZodEnum<{
275
- none: "none";
276
- underline: "underline";
277
- "line-through": "line-through";
278
- }>>;
279
274
  }, z.core.$strip>>;
280
275
  style: z.ZodOptional<z.ZodObject<{
281
276
  wordSpacing: z.ZodDefault<z.ZodOptional<z.ZodUnion<readonly [z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>, z.ZodString]>>>;
@@ -356,39 +351,38 @@ declare const richCaptionAssetSchema: z.ZodObject<{
356
351
  "line-through": "line-through";
357
352
  }>>;
358
353
  }, z.core.$strip>>;
359
- stroke: z.ZodOptional<z.ZodObject<{
354
+ stroke: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
360
355
  width: z.ZodOptional<z.ZodNumber>;
361
356
  color: z.ZodOptional<z.ZodString>;
362
357
  opacity: z.ZodOptional<z.ZodNumber>;
363
- }, z.core.$strip>>;
364
- shadow: z.ZodOptional<z.ZodObject<{
358
+ }, z.core.$strip>, z.ZodLiteral<"none">]>>;
359
+ shadow: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
365
360
  offsetX: z.ZodOptional<z.ZodNumber>;
366
361
  offsetY: z.ZodOptional<z.ZodNumber>;
367
362
  blur: z.ZodOptional<z.ZodNumber>;
368
363
  color: z.ZodOptional<z.ZodString>;
369
364
  opacity: z.ZodOptional<z.ZodNumber>;
370
- }, z.core.$strip>>;
365
+ }, z.core.$strip>, z.ZodLiteral<"none">]>>;
371
366
  scale: z.ZodDefault<z.ZodNumber>;
372
367
  }, z.core.$strict>>;
373
368
  wordAnimation: z.ZodOptional<z.ZodObject<{
374
369
  style: z.ZodDefault<z.ZodEnum<{
375
370
  typewriter: "typewriter";
376
371
  none: "none";
372
+ pop: "pop";
377
373
  karaoke: "karaoke";
378
374
  highlight: "highlight";
379
- pop: "pop";
380
375
  fade: "fade";
381
376
  slide: "slide";
382
377
  bounce: "bounce";
383
378
  }>>;
384
- speed: z.ZodDefault<z.ZodNumber>;
385
379
  direction: z.ZodDefault<z.ZodEnum<{
386
380
  right: "right";
387
381
  left: "left";
388
382
  up: "up";
389
383
  down: "down";
390
384
  }>>;
391
- }, z.core.$strict>>;
385
+ }, z.core.$strip>>;
392
386
  pauseThreshold: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
393
387
  customFonts: z.ZodOptional<z.ZodArray<z.ZodObject<{
394
388
  src: z.ZodString;
@@ -414,11 +408,6 @@ declare const CanvasRichCaptionAssetSchema: z.ZodObject<{
414
408
  color: z.ZodDefault<z.ZodString>;
415
409
  opacity: z.ZodDefault<z.ZodNumber>;
416
410
  background: z.ZodOptional<z.ZodString>;
417
- textDecoration: z.ZodDefault<z.ZodEnum<{
418
- none: "none";
419
- underline: "underline";
420
- "line-through": "line-through";
421
- }>>;
422
411
  }, z.core.$strip>>;
423
412
  style: z.ZodOptional<z.ZodObject<{
424
413
  wordSpacing: z.ZodDefault<z.ZodOptional<z.ZodUnion<readonly [z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>, z.ZodString]>>>;
@@ -499,39 +488,38 @@ declare const CanvasRichCaptionAssetSchema: z.ZodObject<{
499
488
  "line-through": "line-through";
500
489
  }>>;
501
490
  }, z.core.$strip>>;
502
- stroke: z.ZodOptional<z.ZodObject<{
491
+ stroke: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
503
492
  width: z.ZodOptional<z.ZodNumber>;
504
493
  color: z.ZodOptional<z.ZodString>;
505
494
  opacity: z.ZodOptional<z.ZodNumber>;
506
- }, z.core.$strip>>;
507
- shadow: z.ZodOptional<z.ZodObject<{
495
+ }, z.core.$strip>, z.ZodLiteral<"none">]>>;
496
+ shadow: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
508
497
  offsetX: z.ZodOptional<z.ZodNumber>;
509
498
  offsetY: z.ZodOptional<z.ZodNumber>;
510
499
  blur: z.ZodOptional<z.ZodNumber>;
511
500
  color: z.ZodOptional<z.ZodString>;
512
501
  opacity: z.ZodOptional<z.ZodNumber>;
513
- }, z.core.$strip>>;
502
+ }, z.core.$strip>, z.ZodLiteral<"none">]>>;
514
503
  scale: z.ZodDefault<z.ZodNumber>;
515
504
  }, z.core.$strict>>;
516
505
  wordAnimation: z.ZodOptional<z.ZodObject<{
517
506
  style: z.ZodDefault<z.ZodEnum<{
518
507
  typewriter: "typewriter";
519
508
  none: "none";
509
+ pop: "pop";
520
510
  karaoke: "karaoke";
521
511
  highlight: "highlight";
522
- pop: "pop";
523
512
  fade: "fade";
524
513
  slide: "slide";
525
514
  bounce: "bounce";
526
515
  }>>;
527
- speed: z.ZodDefault<z.ZodNumber>;
528
516
  direction: z.ZodDefault<z.ZodEnum<{
529
517
  right: "right";
530
518
  left: "left";
531
519
  up: "up";
532
520
  down: "down";
533
521
  }>>;
534
- }, z.core.$strict>>;
522
+ }, z.core.$strip>>;
535
523
  pauseThreshold: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
536
524
  customFonts: z.ZodOptional<z.ZodArray<z.ZodObject<{
537
525
  src: z.ZodString;
@@ -663,7 +651,6 @@ interface CaptionLayoutConfig {
663
651
  fontFamily: string;
664
652
  fontWeight: string | number;
665
653
  letterSpacing: number;
666
- wordSpacing: number;
667
654
  lineHeight: number;
668
655
  textTransform: "none" | "uppercase" | "lowercase" | "capitalize";
669
656
  pauseThreshold: number;
@@ -745,7 +732,6 @@ type AnimationStyle = "karaoke" | "highlight" | "pop" | "fade" | "slide" | "boun
745
732
  type AnimationDirection = "left" | "right" | "up" | "down";
746
733
  interface WordAnimationConfig {
747
734
  style: AnimationStyle;
748
- speed: number;
749
735
  direction: AnimationDirection;
750
736
  }
751
737
  interface WordAnimationState {
@@ -1163,7 +1149,7 @@ interface FrameSchedule {
1163
1149
  uniqueFrameCount: number;
1164
1150
  skipRatio: number;
1165
1151
  }
1166
- declare function createFrameSchedule(layout: CaptionLayout, durationMs: number, fps: number, animationStyle?: AnimationStyle, speed?: number): FrameSchedule;
1152
+ declare function createFrameSchedule(layout: CaptionLayout, durationMs: number, fps: number, animationStyle?: AnimationStyle): FrameSchedule;
1167
1153
 
1168
1154
  interface RichCaptionRendererOptions {
1169
1155
  width: number;
package/dist/entry.web.js CHANGED
@@ -17896,8 +17896,7 @@ import {
17896
17896
  svgShadowSchema,
17897
17897
  svgTransformSchema,
17898
17898
  svgGradientStopSchema,
17899
- richCaptionActiveSchema as baseCaptionActiveSchema,
17900
- richCaptionWordAnimationSchema as baseCaptionWordAnimationSchema
17899
+ richCaptionActiveSchema as baseCaptionActiveSchema
17901
17900
  } from "@shotstack/schemas/zod";
17902
17901
 
17903
17902
  // src/config/canvas-constants.ts
@@ -18066,8 +18065,7 @@ var richCaptionFontSchema = external_exports.object({
18066
18065
  weight: external_exports.union([external_exports.string(), external_exports.number()]).default("400"),
18067
18066
  color: external_exports.string().regex(HEX6).default("#ffffff"),
18068
18067
  opacity: external_exports.number().min(0).max(1).default(1),
18069
- background: external_exports.string().regex(HEX6).optional(),
18070
- textDecoration: external_exports.enum(["none", "underline", "line-through"]).default("none")
18068
+ background: external_exports.string().regex(HEX6).optional()
18071
18069
  });
18072
18070
  var richCaptionActiveSchema = baseCaptionActiveSchema.extend({
18073
18071
  font: external_exports.object({
@@ -18076,23 +18074,28 @@ var richCaptionActiveSchema = baseCaptionActiveSchema.extend({
18076
18074
  opacity: external_exports.number().min(0).max(1).optional(),
18077
18075
  textDecoration: external_exports.enum(["none", "underline", "line-through"]).optional()
18078
18076
  }).optional(),
18079
- stroke: external_exports.object({
18080
- width: external_exports.number().min(0).optional(),
18081
- color: external_exports.string().regex(HEX6).optional(),
18082
- opacity: external_exports.number().min(0).max(1).optional()
18083
- }).optional(),
18084
- shadow: external_exports.object({
18085
- offsetX: external_exports.number().optional(),
18086
- offsetY: external_exports.number().optional(),
18087
- blur: external_exports.number().min(0).optional(),
18088
- color: external_exports.string().regex(HEX6).optional(),
18089
- opacity: external_exports.number().min(0).max(1).optional()
18090
- }).optional(),
18077
+ stroke: external_exports.union([
18078
+ external_exports.object({
18079
+ width: external_exports.number().min(0).optional(),
18080
+ color: external_exports.string().regex(HEX6).optional(),
18081
+ opacity: external_exports.number().min(0).max(1).optional()
18082
+ }),
18083
+ external_exports.literal("none")
18084
+ ]).optional(),
18085
+ shadow: external_exports.union([
18086
+ external_exports.object({
18087
+ offsetX: external_exports.number().optional(),
18088
+ offsetY: external_exports.number().optional(),
18089
+ blur: external_exports.number().min(0).optional(),
18090
+ color: external_exports.string().regex(HEX6).optional(),
18091
+ opacity: external_exports.number().min(0).max(1).optional()
18092
+ }),
18093
+ external_exports.literal("none")
18094
+ ]).optional(),
18091
18095
  scale: external_exports.number().min(0.5).max(2).default(1)
18092
18096
  });
18093
- var richCaptionWordAnimationSchema = baseCaptionWordAnimationSchema.extend({
18097
+ var richCaptionWordAnimationSchema = external_exports.object({
18094
18098
  style: external_exports.enum(["karaoke", "highlight", "pop", "fade", "slide", "bounce", "typewriter", "none"]).default("highlight"),
18095
- speed: external_exports.number().min(0.5).max(2).default(1),
18096
18099
  direction: external_exports.enum(["left", "right", "up", "down"]).default("up")
18097
18100
  });
18098
18101
  var richCaptionAssetSchema = external_exports.object({
@@ -33166,7 +33169,7 @@ var LayoutEngine = class {
33166
33169
  }
33167
33170
  async layout(params) {
33168
33171
  try {
33169
- const { textTransform, desc, fontSize, letterSpacing, width, emojiFallback } = params;
33172
+ const { textTransform, desc, fontSize, letterSpacing, wordSpacing = 0, width, emojiFallback } = params;
33170
33173
  const input = this.transformText(params.text, textTransform);
33171
33174
  if (!input || input.length === 0) {
33172
33175
  return [];
@@ -33196,9 +33199,10 @@ var LayoutEngine = class {
33196
33199
  char = String.fromCodePoint(codePoint);
33197
33200
  }
33198
33201
  }
33202
+ const isSpace = char === " ";
33199
33203
  return {
33200
33204
  id: g.g,
33201
- xAdvance: g.ax * scale + letterSpacing,
33205
+ xAdvance: g.ax * scale + letterSpacing + (isSpace ? wordSpacing : 0),
33202
33206
  xOffset: g.dx * scale,
33203
33207
  yOffset: -g.dy * scale,
33204
33208
  cluster: g.cl,
@@ -35033,10 +35037,10 @@ var CaptionLayoutEngine = class {
35033
35037
  let spaceWidth;
35034
35038
  if (config2.measureTextWidth) {
35035
35039
  const fontString = `${config2.fontWeight} ${config2.fontSize}px "${config2.fontFamily}"`;
35036
- spaceWidth = config2.measureTextWidth(" ", fontString) + config2.wordSpacing;
35040
+ spaceWidth = config2.measureTextWidth(" ", fontString);
35037
35041
  } else {
35038
35042
  const spaceWord = await this.measureWord(" ", measurementConfig);
35039
- spaceWidth = spaceWord.width + config2.wordSpacing;
35043
+ spaceWidth = spaceWord.width;
35040
35044
  }
35041
35045
  const groups = wordGroups.flatMap((indices) => {
35042
35046
  const groupWidths = indices.map((i) => store.widths[i]);
@@ -35235,12 +35239,8 @@ function calculateWordProgress(ctx) {
35235
35239
  function isWordActive(ctx) {
35236
35240
  return ctx.currentTime >= ctx.wordStart && ctx.currentTime < ctx.wordEnd;
35237
35241
  }
35238
- function calculateKaraokeState(ctx, speed) {
35242
+ function calculateKaraokeState(ctx) {
35239
35243
  const isActive = isWordActive(ctx);
35240
- const wordDuration = ctx.wordEnd - ctx.wordStart;
35241
- const adjustedDuration = wordDuration / speed;
35242
- const adjustedEnd = ctx.wordStart + adjustedDuration;
35243
- const adjustedCtx = { ...ctx, wordEnd: adjustedEnd };
35244
35244
  if (ctx.currentTime < ctx.wordStart) {
35245
35245
  return {
35246
35246
  fillProgress: 0,
@@ -35248,7 +35248,7 @@ function calculateKaraokeState(ctx, speed) {
35248
35248
  opacity: 1
35249
35249
  };
35250
35250
  }
35251
- if (ctx.currentTime >= adjustedEnd) {
35251
+ if (ctx.currentTime >= ctx.wordEnd) {
35252
35252
  return {
35253
35253
  fillProgress: 1,
35254
35254
  isActive: false,
@@ -35256,7 +35256,7 @@ function calculateKaraokeState(ctx, speed) {
35256
35256
  };
35257
35257
  }
35258
35258
  return {
35259
- fillProgress: calculateWordProgress(adjustedCtx),
35259
+ fillProgress: calculateWordProgress(ctx),
35260
35260
  isActive,
35261
35261
  opacity: 1
35262
35262
  };
@@ -35269,7 +35269,7 @@ function calculateHighlightState(ctx) {
35269
35269
  opacity: 1
35270
35270
  };
35271
35271
  }
35272
- function calculatePopState(ctx, activeScale, speed) {
35272
+ function calculatePopState(ctx, activeScale) {
35273
35273
  if (ctx.currentTime < ctx.wordStart) {
35274
35274
  return {
35275
35275
  scale: 0.5,
@@ -35278,9 +35278,7 @@ function calculatePopState(ctx, activeScale, speed) {
35278
35278
  fillProgress: 0
35279
35279
  };
35280
35280
  }
35281
- const adjustedDuration = ctx.animationDuration / speed;
35282
- const adjustedCtx = { ...ctx, animationDuration: adjustedDuration };
35283
- const progress = calculateAnimationProgress(adjustedCtx);
35281
+ const progress = calculateAnimationProgress(ctx);
35284
35282
  const easedProgress = easeOutBack(progress);
35285
35283
  const startScale = 0.5;
35286
35284
  const isActive = isWordActive(ctx);
@@ -35293,7 +35291,7 @@ function calculatePopState(ctx, activeScale, speed) {
35293
35291
  fillProgress: isActive ? 1 : 0
35294
35292
  };
35295
35293
  }
35296
- function calculateFadeState(ctx, speed) {
35294
+ function calculateFadeState(ctx) {
35297
35295
  if (ctx.currentTime < ctx.wordStart) {
35298
35296
  return {
35299
35297
  opacity: 0,
@@ -35301,9 +35299,7 @@ function calculateFadeState(ctx, speed) {
35301
35299
  fillProgress: 0
35302
35300
  };
35303
35301
  }
35304
- const adjustedDuration = ctx.animationDuration / speed;
35305
- const adjustedCtx = { ...ctx, animationDuration: adjustedDuration };
35306
- const progress = calculateAnimationProgress(adjustedCtx);
35302
+ const progress = calculateAnimationProgress(ctx);
35307
35303
  const easedProgress = easeInOutQuad(progress);
35308
35304
  const isActive = isWordActive(ctx);
35309
35305
  return {
@@ -35312,7 +35308,7 @@ function calculateFadeState(ctx, speed) {
35312
35308
  fillProgress: isActive ? 1 : 0
35313
35309
  };
35314
35310
  }
35315
- function calculateSlideState(ctx, direction, speed, fontSize) {
35311
+ function calculateSlideState(ctx, direction, fontSize) {
35316
35312
  const slideDistance = fontSize * 1.5;
35317
35313
  if (ctx.currentTime < ctx.wordStart) {
35318
35314
  const offset2 = getDirectionOffset(direction, slideDistance);
@@ -35324,9 +35320,7 @@ function calculateSlideState(ctx, direction, speed, fontSize) {
35324
35320
  fillProgress: 0
35325
35321
  };
35326
35322
  }
35327
- const adjustedDuration = ctx.animationDuration / speed;
35328
- const adjustedCtx = { ...ctx, animationDuration: adjustedDuration };
35329
- const progress = calculateAnimationProgress(adjustedCtx);
35323
+ const progress = calculateAnimationProgress(ctx);
35330
35324
  const easedProgress = easeOutCirc(progress);
35331
35325
  const offset = getDirectionOffset(direction, slideDistance);
35332
35326
  const translateX = offset.x * (1 - easedProgress);
@@ -35352,7 +35346,7 @@ function getDirectionOffset(direction, distance) {
35352
35346
  return { x: 0, y: -distance };
35353
35347
  }
35354
35348
  }
35355
- function calculateBounceState(ctx, speed, fontSize) {
35349
+ function calculateBounceState(ctx, fontSize) {
35356
35350
  const bounceDistance = fontSize * 0.8;
35357
35351
  if (ctx.currentTime < ctx.wordStart) {
35358
35352
  return {
@@ -35362,9 +35356,7 @@ function calculateBounceState(ctx, speed, fontSize) {
35362
35356
  fillProgress: 0
35363
35357
  };
35364
35358
  }
35365
- const adjustedDuration = ctx.animationDuration / speed;
35366
- const adjustedCtx = { ...ctx, animationDuration: adjustedDuration };
35367
- const progress = calculateAnimationProgress(adjustedCtx);
35359
+ const progress = calculateAnimationProgress(ctx);
35368
35360
  const easedProgress = easeOutBounce(progress);
35369
35361
  const isActive = isWordActive(ctx);
35370
35362
  return {
@@ -35374,11 +35366,7 @@ function calculateBounceState(ctx, speed, fontSize) {
35374
35366
  fillProgress: isActive ? 1 : 0
35375
35367
  };
35376
35368
  }
35377
- function calculateTypewriterState(ctx, charCount, speed) {
35378
- const wordDuration = ctx.wordEnd - ctx.wordStart;
35379
- const adjustedDuration = wordDuration / speed;
35380
- const adjustedEnd = ctx.wordStart + adjustedDuration;
35381
- const adjustedCtx = { ...ctx, wordEnd: adjustedEnd };
35369
+ function calculateTypewriterState(ctx, charCount) {
35382
35370
  if (ctx.currentTime < ctx.wordStart) {
35383
35371
  return {
35384
35372
  visibleCharacters: 0,
@@ -35387,7 +35375,7 @@ function calculateTypewriterState(ctx, charCount, speed) {
35387
35375
  fillProgress: 0
35388
35376
  };
35389
35377
  }
35390
- if (ctx.currentTime >= adjustedEnd) {
35378
+ if (ctx.currentTime >= ctx.wordEnd) {
35391
35379
  return {
35392
35380
  visibleCharacters: charCount,
35393
35381
  opacity: 1,
@@ -35395,7 +35383,7 @@ function calculateTypewriterState(ctx, charCount, speed) {
35395
35383
  fillProgress: 0
35396
35384
  };
35397
35385
  }
35398
- const progress = calculateWordProgress(adjustedCtx);
35386
+ const progress = calculateWordProgress(ctx);
35399
35387
  const visibleCharacters = Math.ceil(progress * charCount);
35400
35388
  const isActive = isWordActive(ctx);
35401
35389
  return {
@@ -35413,7 +35401,6 @@ function calculateNoneState(_ctx) {
35413
35401
  };
35414
35402
  }
35415
35403
  function calculateWordAnimationState(wordStart, wordEnd, currentTime, config2, activeScale = 1, charCount = 0, fontSize = 48, isRTL = false) {
35416
- const safeSpeed = config2.speed > 0 ? config2.speed : 1;
35417
35404
  const ctx = {
35418
35405
  wordStart,
35419
35406
  wordEnd,
@@ -35424,27 +35411,27 @@ function calculateWordAnimationState(wordStart, wordEnd, currentTime, config2, a
35424
35411
  let partialState;
35425
35412
  switch (config2.style) {
35426
35413
  case "karaoke":
35427
- partialState = calculateKaraokeState(ctx, safeSpeed);
35414
+ partialState = calculateKaraokeState(ctx);
35428
35415
  break;
35429
35416
  case "highlight":
35430
35417
  partialState = calculateHighlightState(ctx);
35431
35418
  break;
35432
35419
  case "pop":
35433
- partialState = calculatePopState(ctx, activeScale, safeSpeed);
35420
+ partialState = calculatePopState(ctx, activeScale);
35434
35421
  break;
35435
35422
  case "fade":
35436
- partialState = calculateFadeState(ctx, safeSpeed);
35423
+ partialState = calculateFadeState(ctx);
35437
35424
  break;
35438
35425
  case "slide": {
35439
35426
  const slideDir = mirrorAnimationDirection(config2.direction, isRTL);
35440
- partialState = calculateSlideState(ctx, slideDir, config2.speed, fontSize);
35427
+ partialState = calculateSlideState(ctx, slideDir, fontSize);
35441
35428
  break;
35442
35429
  }
35443
35430
  case "bounce":
35444
- partialState = calculateBounceState(ctx, safeSpeed, fontSize);
35431
+ partialState = calculateBounceState(ctx, fontSize);
35445
35432
  break;
35446
35433
  case "typewriter":
35447
- partialState = calculateTypewriterState(ctx, charCount, safeSpeed);
35434
+ partialState = calculateTypewriterState(ctx, charCount);
35448
35435
  break;
35449
35436
  case "none":
35450
35437
  default:
@@ -35477,7 +35464,6 @@ function calculateAnimationStatesForGroup(words, currentTime, config2, activeSca
35477
35464
  function getDefaultAnimationConfig() {
35478
35465
  return {
35479
35466
  style: "highlight",
35480
- speed: 1,
35481
35467
  direction: "up"
35482
35468
  };
35483
35469
  }
@@ -35518,18 +35504,17 @@ function extractFontConfig(asset) {
35518
35504
  function extractStrokeConfig(asset, isActive) {
35519
35505
  const baseStroke = asset.stroke;
35520
35506
  const activeStroke = asset.active?.stroke;
35521
- if (!baseStroke && !activeStroke) {
35522
- return void 0;
35523
- }
35524
35507
  if (isActive) {
35525
- if (!activeStroke) {
35508
+ if (activeStroke === "none") {
35526
35509
  return void 0;
35527
35510
  }
35528
- return {
35529
- width: activeStroke.width ?? baseStroke?.width ?? 0,
35530
- color: activeStroke.color ?? baseStroke?.color ?? "#000000",
35531
- opacity: activeStroke.opacity ?? baseStroke?.opacity ?? 1
35532
- };
35511
+ if (activeStroke && typeof activeStroke === "object") {
35512
+ return {
35513
+ width: activeStroke.width ?? baseStroke?.width ?? 0,
35514
+ color: activeStroke.color ?? baseStroke?.color ?? "#000000",
35515
+ opacity: activeStroke.opacity ?? baseStroke?.opacity ?? 1
35516
+ };
35517
+ }
35533
35518
  }
35534
35519
  if (baseStroke) {
35535
35520
  return {
@@ -35543,20 +35528,19 @@ function extractStrokeConfig(asset, isActive) {
35543
35528
  function extractShadowConfig(asset, isActive) {
35544
35529
  const baseShadow = asset.shadow;
35545
35530
  const activeShadow = asset.active?.shadow;
35546
- if (!baseShadow && !activeShadow) {
35547
- return void 0;
35548
- }
35549
35531
  if (isActive) {
35550
- if (!activeShadow) {
35532
+ if (activeShadow === "none") {
35551
35533
  return void 0;
35552
35534
  }
35553
- return {
35554
- offsetX: activeShadow.offsetX ?? baseShadow?.offsetX ?? 0,
35555
- offsetY: activeShadow.offsetY ?? baseShadow?.offsetY ?? 0,
35556
- blur: activeShadow.blur ?? baseShadow?.blur ?? 0,
35557
- color: activeShadow.color ?? baseShadow?.color ?? "#000000",
35558
- opacity: activeShadow.opacity ?? baseShadow?.opacity ?? 0.5
35559
- };
35535
+ if (activeShadow && typeof activeShadow === "object") {
35536
+ return {
35537
+ offsetX: activeShadow.offsetX ?? baseShadow?.offsetX ?? 0,
35538
+ offsetY: activeShadow.offsetY ?? baseShadow?.offsetY ?? 0,
35539
+ blur: activeShadow.blur ?? baseShadow?.blur ?? 0,
35540
+ color: activeShadow.color ?? baseShadow?.color ?? "#000000",
35541
+ opacity: activeShadow.opacity ?? baseShadow?.opacity ?? 0.5
35542
+ };
35543
+ }
35560
35544
  }
35561
35545
  if (baseShadow) {
35562
35546
  return {
@@ -35572,7 +35556,12 @@ function extractShadowConfig(asset, isActive) {
35572
35556
  function extractBackgroundConfig(asset, isActive, fontSize) {
35573
35557
  const fontBackground = asset.font?.background;
35574
35558
  const activeBackground = asset.active?.font?.background;
35575
- const bgColor = isActive && activeBackground ? activeBackground : fontBackground;
35559
+ let bgColor;
35560
+ if (isActive) {
35561
+ bgColor = activeBackground ?? fontBackground;
35562
+ } else {
35563
+ bgColor = fontBackground;
35564
+ }
35576
35565
  if (!bgColor) {
35577
35566
  return void 0;
35578
35567
  }
@@ -35623,7 +35612,7 @@ function extractCaptionBorder(asset) {
35623
35612
  };
35624
35613
  }
35625
35614
  function extractTextDecoration(asset, isActive) {
35626
- const baseDecoration = asset.font?.textDecoration;
35615
+ const baseDecoration = asset.style?.textDecoration;
35627
35616
  const activeDecoration = asset.active?.font?.textDecoration;
35628
35617
  if (isActive && activeDecoration !== void 0) {
35629
35618
  return activeDecoration === "none" ? void 0 : activeDecoration;
@@ -35640,7 +35629,6 @@ function extractAnimationConfig(asset) {
35640
35629
  }
35641
35630
  return {
35642
35631
  style: wordAnim.style ?? "highlight",
35643
- speed: wordAnim.speed ?? 1,
35644
35632
  direction: wordAnim.direction ?? "up"
35645
35633
  };
35646
35634
  }
@@ -37849,7 +37837,7 @@ function findActiveWordIndex(store, groupWordIndices, timeMs) {
37849
37837
  }
37850
37838
  return -1;
37851
37839
  }
37852
- function getAnimationPhase(store, groupWordIndices, timeMs, animationStyle, speed) {
37840
+ function getAnimationPhase(store, groupWordIndices, timeMs, animationStyle) {
37853
37841
  if (groupWordIndices.length === 0) {
37854
37842
  return "idle";
37855
37843
  }
@@ -37866,7 +37854,7 @@ function getAnimationPhase(store, groupWordIndices, timeMs, animationStyle, spee
37866
37854
  return "after";
37867
37855
  }
37868
37856
  if (TRANSITION_ANIMATION_STYLES.has(animationStyle)) {
37869
- const transitionDurationMs = (ANIMATION_DURATION_MS[animationStyle] ?? 200) / speed;
37857
+ const transitionDurationMs = ANIMATION_DURATION_MS[animationStyle] ?? 200;
37870
37858
  for (const idx of groupWordIndices) {
37871
37859
  const wordStart = store.startTimes[idx];
37872
37860
  if (timeMs >= wordStart && timeMs < wordStart + transitionDurationMs) {
@@ -37888,7 +37876,7 @@ function getAnimationPhase(store, groupWordIndices, timeMs, animationStyle, spee
37888
37876
  }
37889
37877
  return "before";
37890
37878
  }
37891
- function computeStateSignature(layout, timeMs, animationStyle, speed) {
37879
+ function computeStateSignature(layout, timeMs, animationStyle) {
37892
37880
  const groupIndex = findGroupIndexAtTime(layout.groups, timeMs);
37893
37881
  if (groupIndex === -1) {
37894
37882
  return { groupIndex: -1, activeWordIndex: -1, animationPhase: "idle" };
@@ -37899,21 +37887,20 @@ function computeStateSignature(layout, timeMs, animationStyle, speed) {
37899
37887
  layout.store,
37900
37888
  group.wordIndices,
37901
37889
  timeMs,
37902
- animationStyle,
37903
- speed
37890
+ animationStyle
37904
37891
  );
37905
37892
  return { groupIndex, activeWordIndex, animationPhase };
37906
37893
  }
37907
37894
  function signaturesMatch(a, b) {
37908
37895
  return a.groupIndex === b.groupIndex && a.activeWordIndex === b.activeWordIndex && a.animationPhase === b.animationPhase;
37909
37896
  }
37910
- function createFrameSchedule(layout, durationMs, fps, animationStyle = "highlight", speed = 1) {
37897
+ function createFrameSchedule(layout, durationMs, fps, animationStyle = "highlight") {
37911
37898
  const totalFrames = Math.max(2, Math.round(durationMs / 1e3 * fps) + 1);
37912
37899
  const renderFrames = [];
37913
37900
  let previousSignature = null;
37914
37901
  for (let frame = 0; frame < totalFrames; frame++) {
37915
37902
  const timeMs = frame / (totalFrames - 1) * durationMs;
37916
- const signature = computeStateSignature(layout, timeMs, animationStyle, speed);
37903
+ const signature = computeStateSignature(layout, timeMs, animationStyle);
37917
37904
  const isAnimating = signature.animationPhase === "animating";
37918
37905
  if (isAnimating || previousSignature === null || !signaturesMatch(signature, previousSignature)) {
37919
37906
  renderFrames.push({
@@ -38128,6 +38115,7 @@ async function createTextEngine(opts = {}) {
38128
38115
  text: asset.text,
38129
38116
  width: (asset.width ?? width) - padding2.left - padding2.right,
38130
38117
  letterSpacing: asset.style?.letterSpacing ?? 0,
38118
+ wordSpacing: typeof asset.style?.wordSpacing === "number" ? asset.style.wordSpacing : 0,
38131
38119
  fontSize: main.size,
38132
38120
  lineHeight: asset.style?.lineHeight ?? 1.2,
38133
38121
  desc,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shotstack/shotstack-canvas",
3
- "version": "2.1.5",
3
+ "version": "2.1.7",
4
4
  "description": "Text layout & animation engine (HarfBuzz) for Node & Web - fully self-contained.",
5
5
  "type": "module",
6
6
  "main": "./dist/entry.node.cjs",
@@ -51,7 +51,7 @@
51
51
  "dependencies": {
52
52
  "@resvg/resvg-js": "^2.6.2",
53
53
  "@resvg/resvg-wasm": "^2.6.2",
54
- "@shotstack/schemas": "1.9.0",
54
+ "@shotstack/schemas": "1.9.2",
55
55
  "bidi-js": "^1.0.3",
56
56
  "canvas": "npm:@napi-rs/canvas@^0.1.54",
57
57
  "ffmpeg-static": "^5.2.0",