@shotstack/shotstack-canvas 1.4.6 → 1.5.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.
@@ -0,0 +1,19 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
5
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
6
+ }) : x)(function(x) {
7
+ if (typeof require !== "undefined") return require.apply(this, arguments);
8
+ throw Error('Dynamic require of "' + x + '" is not supported');
9
+ });
10
+ var __commonJS = (cb, mod) => function __require2() {
11
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
12
+ };
13
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
14
+
15
+ export {
16
+ __require,
17
+ __commonJS,
18
+ __publicField
19
+ };
@@ -132,11 +132,26 @@ var animationSchema = import_joi.default.object({
132
132
  otherwise: import_joi.default.forbidden()
133
133
  })
134
134
  }).unknown(false);
135
+ var borderSchema = import_joi.default.object({
136
+ width: import_joi.default.number().min(0).default(0),
137
+ color: import_joi.default.string().pattern(HEX6).default("#000000"),
138
+ opacity: import_joi.default.number().min(0).max(1).default(1)
139
+ }).unknown(false);
135
140
  var backgroundSchema = import_joi.default.object({
136
141
  color: import_joi.default.string().pattern(HEX6).optional(),
137
142
  opacity: import_joi.default.number().min(0).max(1).default(1),
138
- borderRadius: import_joi.default.number().min(0).default(0)
143
+ borderRadius: import_joi.default.number().min(0).default(0),
144
+ border: borderSchema.optional()
139
145
  }).unknown(false);
146
+ var paddingSchema = import_joi.default.alternatives().try(
147
+ import_joi.default.number().min(0).default(0),
148
+ import_joi.default.object({
149
+ top: import_joi.default.number().min(0).default(0),
150
+ right: import_joi.default.number().min(0).default(0),
151
+ bottom: import_joi.default.number().min(0).default(0),
152
+ left: import_joi.default.number().min(0).default(0)
153
+ }).unknown(false)
154
+ );
140
155
  var customFontSchema = import_joi.default.object({
141
156
  src: import_joi.default.string().uri().required(),
142
157
  family: import_joi.default.string().required(),
@@ -154,6 +169,7 @@ var RichTextAssetSchema = import_joi.default.object({
154
169
  stroke: strokeSchema.optional(),
155
170
  shadow: shadowSchema.optional(),
156
171
  background: backgroundSchema.optional(),
172
+ padding: paddingSchema.optional(),
157
173
  align: alignmentSchema.optional(),
158
174
  animation: animationSchema.optional(),
159
175
  customFonts: import_joi.default.array().items(customFontSchema).optional(),
@@ -293,6 +309,23 @@ function setupWasmInterceptors(wasmBinary) {
293
309
  }
294
310
  return originalFetch.apply(this, [input, init]);
295
311
  };
312
+ const originalInstantiate = WebAssembly.instantiate;
313
+ WebAssembly.instantiate = async function(bufferSourceOrModule, importObject) {
314
+ console.log(
315
+ `\u{1F504} WebAssembly.instantiate called, type: ${bufferSourceOrModule instanceof WebAssembly.Module ? "Module" : "BufferSource"}`
316
+ );
317
+ if (bufferSourceOrModule instanceof WebAssembly.Module) {
318
+ return originalInstantiate.call(WebAssembly, bufferSourceOrModule, importObject);
319
+ }
320
+ console.log(`\u{1F504} Intercepted WebAssembly.instantiate, using pre-loaded WASM binary`);
321
+ const module2 = await WebAssembly.compile(wasmBinary);
322
+ const instance = await originalInstantiate.call(
323
+ WebAssembly,
324
+ module2,
325
+ importObject
326
+ );
327
+ return { module: module2, instance };
328
+ };
296
329
  const originalInstantiateStreaming = WebAssembly.instantiateStreaming;
297
330
  if (originalInstantiateStreaming) {
298
331
  WebAssembly.instantiateStreaming = async function(source, importObject) {
@@ -330,21 +363,25 @@ async function initHB(wasmBaseURL) {
330
363
  console.log(`\u2705 WASM binary loaded successfully (${wasmBinary.byteLength} bytes)`);
331
364
  if (!isNode()) {
332
365
  setupWasmInterceptors(wasmBinary);
333
- }
334
- const mod = await import("harfbuzzjs");
335
- let hb;
336
- const candidate = mod.default || mod;
337
- if (typeof candidate === "function") {
338
- hb = await candidate({
339
- wasmBinary
340
- });
341
- } else if (candidate && typeof candidate.then === "function") {
342
- hb = await candidate;
343
- } else if (candidate && typeof candidate.createBuffer === "function") {
344
- hb = candidate;
345
- } else {
346
- throw new Error(`Unexpected harfbuzzjs export type: ${typeof candidate}`);
347
- }
366
+ window.Module = {
367
+ wasmBinary,
368
+ locateFile: (path) => {
369
+ console.log(`\u{1F50D} locateFile called for: ${path}`);
370
+ return path;
371
+ }
372
+ };
373
+ console.log(`\u{1F30D} Set global Module.wasmBinary (${wasmBinary.byteLength} bytes)`);
374
+ }
375
+ console.log("\u{1F504} Importing harfbuzzjs/hb.js (factory)");
376
+ const hbModule = await import("harfbuzzjs/hb.js");
377
+ const hbFactory = hbModule.default || hbModule;
378
+ console.log("\u{1F504} Calling hb factory with wasmBinary");
379
+ const hbInstance = await hbFactory({ wasmBinary });
380
+ console.log("\u{1F504} Importing harfbuzzjs/hbjs.js (wrapper)");
381
+ const hbjsModule = await import("harfbuzzjs/hbjs.js");
382
+ const hbjsWrapper = hbjsModule.default || hbjsModule;
383
+ console.log("\u{1F504} Wrapping hb instance");
384
+ const hb = hbjsWrapper(hbInstance);
348
385
  if (!hb || typeof hb.createBuffer !== "function" || typeof hb.createFont !== "function") {
349
386
  throw new Error("Failed to initialize HarfBuzz: unexpected export shape from 'harfbuzzjs'.");
350
387
  }
@@ -807,15 +844,27 @@ function decorationGeometry(kind, p) {
807
844
  }
808
845
 
809
846
  // src/core/drawops.ts
847
+ function normalizePadding(padding) {
848
+ if (padding === void 0 || padding === null) {
849
+ return { top: 0, right: 0, bottom: 0, left: 0 };
850
+ }
851
+ if (typeof padding === "number") {
852
+ return { top: padding, right: padding, bottom: padding, left: padding };
853
+ }
854
+ return padding;
855
+ }
810
856
  async function buildDrawOps(p) {
811
857
  const ops = [];
858
+ const padding = normalizePadding(p.padding);
859
+ const borderWidth = p.background?.border?.width ?? 0;
812
860
  ops.push({
813
861
  op: "BeginFrame",
814
862
  width: p.canvas.width,
815
863
  height: p.canvas.height,
816
864
  pixelRatio: p.canvas.pixelRatio,
817
865
  clear: true,
818
- bg: p.background && p.background.color ? { color: p.background.color, opacity: p.background.opacity, radius: p.background.borderRadius } : void 0
866
+ bg: void 0
867
+ // Background will be drawn as a separate layer with proper padding/border
819
868
  });
820
869
  if (p.lines.length === 0) return ops;
821
870
  const upem = Math.max(1, await p.getUnitsPerEm());
@@ -825,33 +874,34 @@ async function buildDrawOps(p) {
825
874
  let blockY;
826
875
  switch (p.align.vertical) {
827
876
  case "top":
828
- blockY = p.font.size;
877
+ blockY = p.font.size + padding.top;
829
878
  break;
830
879
  case "bottom":
831
- blockY = p.textRect.height - (numLines - 1) * lineHeightPx;
880
+ blockY = p.textRect.height - (numLines - 1) * lineHeightPx + padding.top;
832
881
  break;
833
882
  case "middle":
834
883
  default:
835
884
  const capHeightRatio = 0.35;
836
885
  const visualOffset = p.font.size * capHeightRatio;
837
- blockY = (p.textRect.height - (numLines - 1) * lineHeightPx) / 2 + visualOffset;
886
+ blockY = (p.textRect.height - (numLines - 1) * lineHeightPx) / 2 + visualOffset + padding.top;
838
887
  break;
839
888
  }
840
889
  const fill = p.style.gradient ? gradientSpecFrom(p.style.gradient, 1) : { kind: "solid", color: p.font.color, opacity: p.font.opacity };
841
890
  const decoColor = p.style.gradient ? p.style.gradient.stops[p.style.gradient.stops.length - 1]?.color ?? p.font.color : p.font.color;
891
+ const textOps = [];
842
892
  let gMinX = Infinity, gMinY = Infinity, gMaxX = -Infinity, gMaxY = -Infinity;
843
893
  for (const line of p.lines) {
844
894
  let lineX;
845
895
  switch (p.align.horizontal) {
846
896
  case "left":
847
- lineX = 0;
897
+ lineX = padding.left;
848
898
  break;
849
899
  case "right":
850
- lineX = p.textRect.width - line.width;
900
+ lineX = p.textRect.width - line.width + padding.left;
851
901
  break;
852
902
  case "center":
853
903
  default:
854
- lineX = (p.textRect.width - line.width) / 2;
904
+ lineX = (p.textRect.width - line.width) / 2 + padding.left;
855
905
  break;
856
906
  }
857
907
  let xCursor = lineX;
@@ -875,7 +925,7 @@ async function buildDrawOps(p) {
875
925
  if (x2 > gMaxX) gMaxX = x2;
876
926
  if (y2 > gMaxY) gMaxY = y2;
877
927
  if (p.shadow && p.shadow.blur > 0) {
878
- ops.push({
928
+ textOps.push({
879
929
  isShadow: true,
880
930
  op: "FillPath",
881
931
  path,
@@ -886,7 +936,7 @@ async function buildDrawOps(p) {
886
936
  });
887
937
  }
888
938
  if (p.stroke && p.stroke.width > 0) {
889
- ops.push({
939
+ textOps.push({
890
940
  op: "StrokePath",
891
941
  path,
892
942
  x: glyphX,
@@ -897,7 +947,7 @@ async function buildDrawOps(p) {
897
947
  opacity: p.stroke.opacity
898
948
  });
899
949
  }
900
- ops.push({
950
+ textOps.push({
901
951
  op: "FillPath",
902
952
  path,
903
953
  x: glyphX,
@@ -914,7 +964,7 @@ async function buildDrawOps(p) {
914
964
  lineWidth: line.width,
915
965
  xStart: lineX
916
966
  });
917
- ops.push({
967
+ textOps.push({
918
968
  op: "DecorationLine",
919
969
  from: { x: deco.x1, y: deco.y },
920
970
  to: { x: deco.x2, y: deco.y },
@@ -926,12 +976,46 @@ async function buildDrawOps(p) {
926
976
  }
927
977
  if (gMinX !== Infinity) {
928
978
  const gbox = { x: gMinX, y: gMinY, w: Math.max(1, gMaxX - gMinX), h: Math.max(1, gMaxY - gMinY) };
929
- for (const op of ops) {
979
+ for (const op of textOps) {
930
980
  if (op.op === "FillPath" && !op.isShadow) {
931
981
  op.gradientBBox = gbox;
932
982
  }
933
983
  }
934
984
  }
985
+ if (p.background && (p.background.color || p.background.border)) {
986
+ const bgX = 0;
987
+ const bgY = 0;
988
+ const bgWidth = p.canvas.width;
989
+ const bgHeight = p.canvas.height;
990
+ if (p.background.color) {
991
+ ops.push({
992
+ op: "Rectangle",
993
+ x: bgX,
994
+ y: bgY,
995
+ width: bgWidth,
996
+ height: bgHeight,
997
+ fill: { kind: "solid", color: p.background.color, opacity: p.background.opacity },
998
+ borderRadius: p.background.borderRadius
999
+ });
1000
+ }
1001
+ if (p.background.border && p.background.border.width > 0) {
1002
+ const halfBorder = p.background.border.width / 2;
1003
+ ops.push({
1004
+ op: "RectangleStroke",
1005
+ x: bgX + halfBorder,
1006
+ y: bgY + halfBorder,
1007
+ width: bgWidth - p.background.border.width,
1008
+ height: bgHeight - p.background.border.width,
1009
+ stroke: {
1010
+ width: p.background.border.width,
1011
+ color: p.background.border.color,
1012
+ opacity: p.background.border.opacity
1013
+ },
1014
+ borderRadius: Math.max(0, p.background.borderRadius - halfBorder)
1015
+ });
1016
+ }
1017
+ }
1018
+ ops.push(...textOps);
935
1019
  return ops;
936
1020
  }
937
1021
  function tokenizePath(d) {
@@ -1741,6 +1825,44 @@ async function createNodePainter(opts) {
1741
1825
  });
1742
1826
  continue;
1743
1827
  }
1828
+ if (op.op === "Rectangle") {
1829
+ renderToBoth((context) => {
1830
+ context.save();
1831
+ const fill = makeGradientFromBBox(context, op.fill, {
1832
+ x: op.x,
1833
+ y: op.y,
1834
+ w: op.width,
1835
+ h: op.height
1836
+ });
1837
+ context.fillStyle = fill;
1838
+ if (op.borderRadius && op.borderRadius > 0) {
1839
+ context.beginPath();
1840
+ roundRectPath(context, op.x, op.y, op.width, op.height, op.borderRadius);
1841
+ context.fill();
1842
+ } else {
1843
+ context.fillRect(op.x, op.y, op.width, op.height);
1844
+ }
1845
+ context.restore();
1846
+ });
1847
+ continue;
1848
+ }
1849
+ if (op.op === "RectangleStroke") {
1850
+ renderToBoth((context) => {
1851
+ context.save();
1852
+ const c = parseHex6(op.stroke.color, op.stroke.opacity);
1853
+ context.strokeStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
1854
+ context.lineWidth = op.stroke.width;
1855
+ if (op.borderRadius && op.borderRadius > 0) {
1856
+ context.beginPath();
1857
+ roundRectPath(context, op.x, op.y, op.width, op.height, op.borderRadius);
1858
+ context.stroke();
1859
+ } else {
1860
+ context.strokeRect(op.x, op.y, op.width, op.height);
1861
+ }
1862
+ context.restore();
1863
+ });
1864
+ continue;
1865
+ }
1744
1866
  }
1745
1867
  if (needsAlphaExtraction) {
1746
1868
  const whiteData = ctx.getImageData(0, 0, canvas.width, canvas.height);
@@ -2304,14 +2426,15 @@ async function createTextEngine(opts = {}) {
2304
2426
  `Failed to layout text: ${err instanceof Error ? err.message : String(err)}`
2305
2427
  );
2306
2428
  }
2429
+ const padding = asset.padding ? typeof asset.padding === "number" ? { top: asset.padding, right: asset.padding, bottom: asset.padding, left: asset.padding } : asset.padding : { top: 0, right: 0, bottom: 0, left: 0 };
2307
2430
  const textRect = {
2308
2431
  x: 0,
2309
2432
  y: 0,
2310
2433
  width: asset.width ?? width,
2311
2434
  height: asset.height ?? height
2312
2435
  };
2313
- const canvasW = asset.width ?? width;
2314
- const canvasH = asset.height ?? height;
2436
+ const canvasW = (asset.width ?? width) + padding.left + padding.right;
2437
+ const canvasH = (asset.height ?? height) + padding.top + padding.bottom;
2315
2438
  const canvasPR = asset.pixelRatio ?? pixelRatio;
2316
2439
  let ops0;
2317
2440
  try {
@@ -2338,6 +2461,7 @@ async function createTextEngine(opts = {}) {
2338
2461
  vertical: asset.align?.vertical ?? "middle"
2339
2462
  },
2340
2463
  background: asset.background,
2464
+ padding: asset.padding,
2341
2465
  glyphPathProvider: (gid, fontDesc) => fonts.glyphPath(fontDesc || desc, gid),
2342
2466
  getUnitsPerEm: (fontDesc) => fonts.getUnitsPerEm(fontDesc || desc)
2343
2467
  });
@@ -64,6 +64,17 @@ type RichTextValidated = Required<{
64
64
  color?: string;
65
65
  opacity: number;
66
66
  borderRadius: number;
67
+ border?: {
68
+ width: number;
69
+ color: string;
70
+ opacity: number;
71
+ };
72
+ };
73
+ padding?: number | {
74
+ top: number;
75
+ right: number;
76
+ bottom: number;
77
+ left: number;
67
78
  };
68
79
  align?: {
69
80
  horizontal: "left" | "center" | "right";
@@ -177,6 +188,26 @@ type DrawOp = {
177
188
  width: number;
178
189
  color: string;
179
190
  opacity: number;
191
+ } | {
192
+ op: "Rectangle";
193
+ x: number;
194
+ y: number;
195
+ width: number;
196
+ height: number;
197
+ fill: GradientSpec;
198
+ borderRadius?: number;
199
+ } | {
200
+ op: "RectangleStroke";
201
+ x: number;
202
+ y: number;
203
+ width: number;
204
+ height: number;
205
+ stroke: {
206
+ width: number;
207
+ color: string;
208
+ opacity: number;
209
+ };
210
+ borderRadius?: number;
180
211
  };
181
212
  type EngineInit = {
182
213
  width: number;
@@ -64,6 +64,17 @@ type RichTextValidated = Required<{
64
64
  color?: string;
65
65
  opacity: number;
66
66
  borderRadius: number;
67
+ border?: {
68
+ width: number;
69
+ color: string;
70
+ opacity: number;
71
+ };
72
+ };
73
+ padding?: number | {
74
+ top: number;
75
+ right: number;
76
+ bottom: number;
77
+ left: number;
67
78
  };
68
79
  align?: {
69
80
  horizontal: "left" | "center" | "right";
@@ -177,6 +188,26 @@ type DrawOp = {
177
188
  width: number;
178
189
  color: string;
179
190
  opacity: number;
191
+ } | {
192
+ op: "Rectangle";
193
+ x: number;
194
+ y: number;
195
+ width: number;
196
+ height: number;
197
+ fill: GradientSpec;
198
+ borderRadius?: number;
199
+ } | {
200
+ op: "RectangleStroke";
201
+ x: number;
202
+ y: number;
203
+ width: number;
204
+ height: number;
205
+ stroke: {
206
+ width: number;
207
+ color: string;
208
+ opacity: number;
209
+ };
210
+ borderRadius?: number;
180
211
  };
181
212
  type EngineInit = {
182
213
  width: number;