@shotstack/shotstack-canvas 2.1.6 → 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.
@@ -590,8 +590,7 @@ var richCaptionFontSchema = import_zod.z.object({
590
590
  weight: import_zod.z.union([import_zod.z.string(), import_zod.z.number()]).default("400"),
591
591
  color: import_zod.z.string().regex(HEX6).default("#ffffff"),
592
592
  opacity: import_zod.z.number().min(0).max(1).default(1),
593
- background: import_zod.z.string().regex(HEX6).optional(),
594
- textDecoration: import_zod.z.enum(["none", "underline", "line-through"]).default("none")
593
+ background: import_zod.z.string().regex(HEX6).optional()
595
594
  });
596
595
  var richCaptionActiveSchema = import_zod2.richCaptionActiveSchema.extend({
597
596
  font: import_zod.z.object({
@@ -1539,7 +1538,7 @@ var LayoutEngine = class {
1539
1538
  }
1540
1539
  async layout(params) {
1541
1540
  try {
1542
- const { textTransform, desc, fontSize, letterSpacing, width, emojiFallback } = params;
1541
+ const { textTransform, desc, fontSize, letterSpacing, wordSpacing = 0, width, emojiFallback } = params;
1543
1542
  const input = this.transformText(params.text, textTransform);
1544
1543
  if (!input || input.length === 0) {
1545
1544
  return [];
@@ -1569,9 +1568,10 @@ var LayoutEngine = class {
1569
1568
  char = String.fromCodePoint(codePoint);
1570
1569
  }
1571
1570
  }
1571
+ const isSpace = char === " ";
1572
1572
  return {
1573
1573
  id: g.g,
1574
- xAdvance: g.ax * scale + letterSpacing,
1574
+ xAdvance: g.ax * scale + letterSpacing + (isSpace ? wordSpacing : 0),
1575
1575
  xOffset: g.dx * scale,
1576
1576
  yOffset: -g.dy * scale,
1577
1577
  cluster: g.cl,
@@ -2848,10 +2848,10 @@ var CaptionLayoutEngine = class {
2848
2848
  let spaceWidth;
2849
2849
  if (config.measureTextWidth) {
2850
2850
  const fontString = `${config.fontWeight} ${config.fontSize}px "${config.fontFamily}"`;
2851
- spaceWidth = config.measureTextWidth(" ", fontString) + config.wordSpacing;
2851
+ spaceWidth = config.measureTextWidth(" ", fontString);
2852
2852
  } else {
2853
2853
  const spaceWord = await this.measureWord(" ", measurementConfig);
2854
- spaceWidth = spaceWord.width + config.wordSpacing;
2854
+ spaceWidth = spaceWord.width;
2855
2855
  }
2856
2856
  const groups = wordGroups.flatMap((indices) => {
2857
2857
  const groupWidths = indices.map((i) => store.widths[i]);
@@ -3423,7 +3423,7 @@ function extractCaptionBorder(asset) {
3423
3423
  };
3424
3424
  }
3425
3425
  function extractTextDecoration(asset, isActive) {
3426
- const baseDecoration = asset.font?.textDecoration;
3426
+ const baseDecoration = asset.style?.textDecoration;
3427
3427
  const activeDecoration = asset.active?.font?.textDecoration;
3428
3428
  if (isActive && activeDecoration !== void 0) {
3429
3429
  return activeDecoration === "none" ? void 0 : activeDecoration;
@@ -5486,11 +5486,14 @@ function extractSvgDimensions(svgString) {
5486
5486
  }
5487
5487
 
5488
5488
  // src/core/canvas-text-measurer.ts
5489
- async function createCanvasTextMeasurer() {
5489
+ async function createCanvasTextMeasurer(letterSpacing) {
5490
5490
  const canvasMod = await import("canvas");
5491
5491
  const canvas = canvasMod.createCanvas(1, 1);
5492
5492
  const ctx = canvas.getContext("2d");
5493
5493
  ctx.textBaseline = "alphabetic";
5494
+ if (letterSpacing) {
5495
+ ctx.letterSpacing = `${letterSpacing}px`;
5496
+ }
5494
5497
  let lastFont = "";
5495
5498
  return (text, font) => {
5496
5499
  if (font !== lastFont) {
@@ -6269,7 +6272,7 @@ var RichCaptionRenderer = class {
6269
6272
  }
6270
6273
  const font = asset.font;
6271
6274
  const style = asset.style;
6272
- const measureTextWidth = await createCanvasTextMeasurer();
6275
+ const measureTextWidth = await createCanvasTextMeasurer(style?.letterSpacing ?? 0);
6273
6276
  const fontSize = font?.size ?? 24;
6274
6277
  const lineHeight = style?.lineHeight ?? 1.2;
6275
6278
  const padding = extractCaptionPadding(asset);
@@ -6290,7 +6293,6 @@ var RichCaptionRenderer = class {
6290
6293
  fontFamily: font?.family ?? "Roboto",
6291
6294
  fontWeight: String(font?.weight ?? "400"),
6292
6295
  letterSpacing: style?.letterSpacing ?? 0,
6293
- wordSpacing: typeof style?.wordSpacing === "number" ? style.wordSpacing : 0,
6294
6296
  lineHeight,
6295
6297
  textTransform: style?.textTransform ?? "none",
6296
6298
  pauseThreshold: this.currentAsset?.pauseThreshold ?? 500,
@@ -6844,6 +6846,7 @@ async function createTextEngine(opts = {}) {
6844
6846
  text: asset.text,
6845
6847
  width: (asset.width ?? width) - padding2.left - padding2.right,
6846
6848
  letterSpacing: asset.style?.letterSpacing ?? 0,
6849
+ wordSpacing: typeof asset.style?.wordSpacing === "number" ? asset.style.wordSpacing : 0,
6847
6850
  fontSize: main.size,
6848
6851
  lineHeight: asset.style?.lineHeight ?? 1.2,
6849
6852
  desc,
@@ -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]>>>;
@@ -413,11 +408,6 @@ declare const CanvasRichCaptionAssetSchema: z.ZodObject<{
413
408
  color: z.ZodDefault<z.ZodString>;
414
409
  opacity: z.ZodDefault<z.ZodNumber>;
415
410
  background: z.ZodOptional<z.ZodString>;
416
- textDecoration: z.ZodDefault<z.ZodEnum<{
417
- none: "none";
418
- underline: "underline";
419
- "line-through": "line-through";
420
- }>>;
421
411
  }, z.core.$strip>>;
422
412
  style: z.ZodOptional<z.ZodObject<{
423
413
  wordSpacing: z.ZodDefault<z.ZodOptional<z.ZodUnion<readonly [z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>, z.ZodString]>>>;
@@ -661,7 +651,6 @@ interface CaptionLayoutConfig {
661
651
  fontFamily: string;
662
652
  fontWeight: string | number;
663
653
  letterSpacing: number;
664
- wordSpacing: number;
665
654
  lineHeight: number;
666
655
  textTransform: "none" | "uppercase" | "lowercase" | "capitalize";
667
656
  pauseThreshold: number;
@@ -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]>>>;
@@ -413,11 +408,6 @@ declare const CanvasRichCaptionAssetSchema: z.ZodObject<{
413
408
  color: z.ZodDefault<z.ZodString>;
414
409
  opacity: z.ZodDefault<z.ZodNumber>;
415
410
  background: z.ZodOptional<z.ZodString>;
416
- textDecoration: z.ZodDefault<z.ZodEnum<{
417
- none: "none";
418
- underline: "underline";
419
- "line-through": "line-through";
420
- }>>;
421
411
  }, z.core.$strip>>;
422
412
  style: z.ZodOptional<z.ZodObject<{
423
413
  wordSpacing: z.ZodDefault<z.ZodOptional<z.ZodUnion<readonly [z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>, z.ZodString]>>>;
@@ -661,7 +651,6 @@ interface CaptionLayoutConfig {
661
651
  fontFamily: string;
662
652
  fontWeight: string | number;
663
653
  letterSpacing: number;
664
- wordSpacing: number;
665
654
  lineHeight: number;
666
655
  textTransform: "none" | "uppercase" | "lowercase" | "capitalize";
667
656
  pauseThreshold: number;
@@ -187,8 +187,7 @@ var richCaptionFontSchema = z.object({
187
187
  weight: z.union([z.string(), z.number()]).default("400"),
188
188
  color: z.string().regex(HEX6).default("#ffffff"),
189
189
  opacity: z.number().min(0).max(1).default(1),
190
- background: z.string().regex(HEX6).optional(),
191
- textDecoration: z.enum(["none", "underline", "line-through"]).default("none")
190
+ background: z.string().regex(HEX6).optional()
192
191
  });
193
192
  var richCaptionActiveSchema = baseCaptionActiveSchema.extend({
194
193
  font: z.object({
@@ -1135,7 +1134,7 @@ var LayoutEngine = class {
1135
1134
  }
1136
1135
  async layout(params) {
1137
1136
  try {
1138
- const { textTransform, desc, fontSize, letterSpacing, width, emojiFallback } = params;
1137
+ const { textTransform, desc, fontSize, letterSpacing, wordSpacing = 0, width, emojiFallback } = params;
1139
1138
  const input = this.transformText(params.text, textTransform);
1140
1139
  if (!input || input.length === 0) {
1141
1140
  return [];
@@ -1165,9 +1164,10 @@ var LayoutEngine = class {
1165
1164
  char = String.fromCodePoint(codePoint);
1166
1165
  }
1167
1166
  }
1167
+ const isSpace = char === " ";
1168
1168
  return {
1169
1169
  id: g.g,
1170
- xAdvance: g.ax * scale + letterSpacing,
1170
+ xAdvance: g.ax * scale + letterSpacing + (isSpace ? wordSpacing : 0),
1171
1171
  xOffset: g.dx * scale,
1172
1172
  yOffset: -g.dy * scale,
1173
1173
  cluster: g.cl,
@@ -2444,10 +2444,10 @@ var CaptionLayoutEngine = class {
2444
2444
  let spaceWidth;
2445
2445
  if (config.measureTextWidth) {
2446
2446
  const fontString = `${config.fontWeight} ${config.fontSize}px "${config.fontFamily}"`;
2447
- spaceWidth = config.measureTextWidth(" ", fontString) + config.wordSpacing;
2447
+ spaceWidth = config.measureTextWidth(" ", fontString);
2448
2448
  } else {
2449
2449
  const spaceWord = await this.measureWord(" ", measurementConfig);
2450
- spaceWidth = spaceWord.width + config.wordSpacing;
2450
+ spaceWidth = spaceWord.width;
2451
2451
  }
2452
2452
  const groups = wordGroups.flatMap((indices) => {
2453
2453
  const groupWidths = indices.map((i) => store.widths[i]);
@@ -3019,7 +3019,7 @@ function extractCaptionBorder(asset) {
3019
3019
  };
3020
3020
  }
3021
3021
  function extractTextDecoration(asset, isActive) {
3022
- const baseDecoration = asset.font?.textDecoration;
3022
+ const baseDecoration = asset.style?.textDecoration;
3023
3023
  const activeDecoration = asset.active?.font?.textDecoration;
3024
3024
  if (isActive && activeDecoration !== void 0) {
3025
3025
  return activeDecoration === "none" ? void 0 : activeDecoration;
@@ -5082,11 +5082,14 @@ function extractSvgDimensions(svgString) {
5082
5082
  }
5083
5083
 
5084
5084
  // src/core/canvas-text-measurer.ts
5085
- async function createCanvasTextMeasurer() {
5085
+ async function createCanvasTextMeasurer(letterSpacing) {
5086
5086
  const canvasMod = await import("canvas");
5087
5087
  const canvas = canvasMod.createCanvas(1, 1);
5088
5088
  const ctx = canvas.getContext("2d");
5089
5089
  ctx.textBaseline = "alphabetic";
5090
+ if (letterSpacing) {
5091
+ ctx.letterSpacing = `${letterSpacing}px`;
5092
+ }
5090
5093
  let lastFont = "";
5091
5094
  return (text, font) => {
5092
5095
  if (font !== lastFont) {
@@ -5865,7 +5868,7 @@ var RichCaptionRenderer = class {
5865
5868
  }
5866
5869
  const font = asset.font;
5867
5870
  const style = asset.style;
5868
- const measureTextWidth = await createCanvasTextMeasurer();
5871
+ const measureTextWidth = await createCanvasTextMeasurer(style?.letterSpacing ?? 0);
5869
5872
  const fontSize = font?.size ?? 24;
5870
5873
  const lineHeight = style?.lineHeight ?? 1.2;
5871
5874
  const padding = extractCaptionPadding(asset);
@@ -5886,7 +5889,6 @@ var RichCaptionRenderer = class {
5886
5889
  fontFamily: font?.family ?? "Roboto",
5887
5890
  fontWeight: String(font?.weight ?? "400"),
5888
5891
  letterSpacing: style?.letterSpacing ?? 0,
5889
- wordSpacing: typeof style?.wordSpacing === "number" ? style.wordSpacing : 0,
5890
5892
  lineHeight,
5891
5893
  textTransform: style?.textTransform ?? "none",
5892
5894
  pauseThreshold: this.currentAsset?.pauseThreshold ?? 500,
@@ -6440,6 +6442,7 @@ async function createTextEngine(opts = {}) {
6440
6442
  text: asset.text,
6441
6443
  width: (asset.width ?? width) - padding2.left - padding2.right,
6442
6444
  letterSpacing: asset.style?.letterSpacing ?? 0,
6445
+ wordSpacing: typeof asset.style?.wordSpacing === "number" ? asset.style.wordSpacing : 0,
6443
6446
  fontSize: main.size,
6444
6447
  lineHeight: asset.style?.lineHeight ?? 1.2,
6445
6448
  desc,
@@ -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]>>>;
@@ -413,11 +408,6 @@ declare const CanvasRichCaptionAssetSchema: z.ZodObject<{
413
408
  color: z.ZodDefault<z.ZodString>;
414
409
  opacity: z.ZodDefault<z.ZodNumber>;
415
410
  background: z.ZodOptional<z.ZodString>;
416
- textDecoration: z.ZodDefault<z.ZodEnum<{
417
- none: "none";
418
- underline: "underline";
419
- "line-through": "line-through";
420
- }>>;
421
411
  }, z.core.$strip>>;
422
412
  style: z.ZodOptional<z.ZodObject<{
423
413
  wordSpacing: z.ZodDefault<z.ZodOptional<z.ZodUnion<readonly [z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>, z.ZodString]>>>;
@@ -661,7 +651,6 @@ interface CaptionLayoutConfig {
661
651
  fontFamily: string;
662
652
  fontWeight: string | number;
663
653
  letterSpacing: number;
664
- wordSpacing: number;
665
654
  lineHeight: number;
666
655
  textTransform: "none" | "uppercase" | "lowercase" | "capitalize";
667
656
  pauseThreshold: number;
package/dist/entry.web.js CHANGED
@@ -18065,8 +18065,7 @@ var richCaptionFontSchema = external_exports.object({
18065
18065
  weight: external_exports.union([external_exports.string(), external_exports.number()]).default("400"),
18066
18066
  color: external_exports.string().regex(HEX6).default("#ffffff"),
18067
18067
  opacity: external_exports.number().min(0).max(1).default(1),
18068
- background: external_exports.string().regex(HEX6).optional(),
18069
- textDecoration: external_exports.enum(["none", "underline", "line-through"]).default("none")
18068
+ background: external_exports.string().regex(HEX6).optional()
18070
18069
  });
18071
18070
  var richCaptionActiveSchema = baseCaptionActiveSchema.extend({
18072
18071
  font: external_exports.object({
@@ -33170,7 +33169,7 @@ var LayoutEngine = class {
33170
33169
  }
33171
33170
  async layout(params) {
33172
33171
  try {
33173
- const { textTransform, desc, fontSize, letterSpacing, width, emojiFallback } = params;
33172
+ const { textTransform, desc, fontSize, letterSpacing, wordSpacing = 0, width, emojiFallback } = params;
33174
33173
  const input = this.transformText(params.text, textTransform);
33175
33174
  if (!input || input.length === 0) {
33176
33175
  return [];
@@ -33200,9 +33199,10 @@ var LayoutEngine = class {
33200
33199
  char = String.fromCodePoint(codePoint);
33201
33200
  }
33202
33201
  }
33202
+ const isSpace = char === " ";
33203
33203
  return {
33204
33204
  id: g.g,
33205
- xAdvance: g.ax * scale + letterSpacing,
33205
+ xAdvance: g.ax * scale + letterSpacing + (isSpace ? wordSpacing : 0),
33206
33206
  xOffset: g.dx * scale,
33207
33207
  yOffset: -g.dy * scale,
33208
33208
  cluster: g.cl,
@@ -35037,10 +35037,10 @@ var CaptionLayoutEngine = class {
35037
35037
  let spaceWidth;
35038
35038
  if (config2.measureTextWidth) {
35039
35039
  const fontString = `${config2.fontWeight} ${config2.fontSize}px "${config2.fontFamily}"`;
35040
- spaceWidth = config2.measureTextWidth(" ", fontString) + config2.wordSpacing;
35040
+ spaceWidth = config2.measureTextWidth(" ", fontString);
35041
35041
  } else {
35042
35042
  const spaceWord = await this.measureWord(" ", measurementConfig);
35043
- spaceWidth = spaceWord.width + config2.wordSpacing;
35043
+ spaceWidth = spaceWord.width;
35044
35044
  }
35045
35045
  const groups = wordGroups.flatMap((indices) => {
35046
35046
  const groupWidths = indices.map((i) => store.widths[i]);
@@ -35612,7 +35612,7 @@ function extractCaptionBorder(asset) {
35612
35612
  };
35613
35613
  }
35614
35614
  function extractTextDecoration(asset, isActive) {
35615
- const baseDecoration = asset.font?.textDecoration;
35615
+ const baseDecoration = asset.style?.textDecoration;
35616
35616
  const activeDecoration = asset.active?.font?.textDecoration;
35617
35617
  if (isActive && activeDecoration !== void 0) {
35618
35618
  return activeDecoration === "none" ? void 0 : activeDecoration;
@@ -38115,6 +38115,7 @@ async function createTextEngine(opts = {}) {
38115
38115
  text: asset.text,
38116
38116
  width: (asset.width ?? width) - padding2.left - padding2.right,
38117
38117
  letterSpacing: asset.style?.letterSpacing ?? 0,
38118
+ wordSpacing: typeof asset.style?.wordSpacing === "number" ? asset.style.wordSpacing : 0,
38118
38119
  fontSize: main.size,
38119
38120
  lineHeight: asset.style?.lineHeight ?? 1.2,
38120
38121
  desc,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shotstack/shotstack-canvas",
3
- "version": "2.1.6",
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.1",
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",