@shotstack/shotstack-canvas 1.2.9 → 1.3.1

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.
@@ -163,10 +163,62 @@ var RichTextAssetSchema = import_joi.default.object({
163
163
  }).unknown(false);
164
164
 
165
165
  // src/wasm/hb-loader.ts
166
+ var import_meta = {};
166
167
  var hbSingleton = null;
167
168
  function isNode() {
168
169
  return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
169
170
  }
171
+ function bufferToArrayBuffer(buffer) {
172
+ if (buffer.buffer instanceof ArrayBuffer) {
173
+ return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
174
+ }
175
+ const arrayBuffer = new ArrayBuffer(buffer.byteLength);
176
+ const view = new Uint8Array(arrayBuffer);
177
+ for (let i = 0; i < buffer.byteLength; i++) {
178
+ view[i] = buffer[i];
179
+ }
180
+ return arrayBuffer;
181
+ }
182
+ async function loadWasmNode() {
183
+ try {
184
+ const { readFile: readFile2 } = await import("fs/promises");
185
+ const { fileURLToPath } = await import("url");
186
+ const path = await import("path");
187
+ const { createRequire } = await import("module");
188
+ const currentDir = path.dirname(fileURLToPath(import_meta.url));
189
+ let harfbuzzWasmPath;
190
+ try {
191
+ const require2 = createRequire(import_meta.url);
192
+ const harfbuzzPkgPath = require2.resolve("harfbuzzjs/package.json");
193
+ const harfbuzzDir = path.dirname(harfbuzzPkgPath);
194
+ harfbuzzWasmPath = path.join(harfbuzzDir, "hb.wasm");
195
+ } catch {
196
+ }
197
+ const candidates = [
198
+ ...harfbuzzWasmPath ? [harfbuzzWasmPath] : [],
199
+ path.join(currentDir, "../../dist/hb.wasm"),
200
+ path.join(currentDir, "../dist/hb.wasm"),
201
+ path.join(currentDir, "../../assets/wasm/hb.wasm"),
202
+ path.join(currentDir, "../assets/wasm/hb.wasm"),
203
+ path.join(currentDir, "./hb.wasm"),
204
+ path.join(currentDir, "../hb.wasm"),
205
+ path.join(currentDir, "../../node_modules/harfbuzzjs/hb.wasm"),
206
+ path.join(currentDir, "../../../node_modules/harfbuzzjs/hb.wasm")
207
+ ];
208
+ for (const candidate of candidates) {
209
+ try {
210
+ const buffer = await readFile2(candidate);
211
+ console.log(`\u2705 Found WASM at: ${candidate}`);
212
+ return bufferToArrayBuffer(buffer);
213
+ } catch {
214
+ continue;
215
+ }
216
+ }
217
+ return void 0;
218
+ } catch {
219
+ return void 0;
220
+ }
221
+ }
170
222
  async function loadWasmWeb(wasmBaseURL) {
171
223
  try {
172
224
  if (wasmBaseURL) {
@@ -188,14 +240,14 @@ async function loadWasmWeb(wasmBaseURL) {
188
240
  return void 0;
189
241
  }
190
242
  }
191
- function setupWasmFetchInterceptor(wasmBinary) {
243
+ function setupWasmInterceptors(wasmBinary) {
192
244
  const originalFetch = window.fetch;
193
245
  window.fetch = function(input, init) {
194
246
  const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
195
- if (url.includes("hb.wasm") || url.endsWith(".wasm")) {
247
+ if (url.includes("hb.wasm") || url.endsWith(".wasm") && !url.includes("source.wasm")) {
196
248
  console.log(`\u{1F504} Intercepted fetch for: ${url}`);
197
249
  return Promise.resolve(
198
- new Response(wasmBinary, {
250
+ new Response(wasmBinary.slice(0), {
199
251
  status: 200,
200
252
  statusText: "OK",
201
253
  headers: {
@@ -207,28 +259,57 @@ function setupWasmFetchInterceptor(wasmBinary) {
207
259
  }
208
260
  return originalFetch.apply(this, [input, init]);
209
261
  };
262
+ const originalInstantiateStreaming = WebAssembly.instantiateStreaming;
263
+ if (originalInstantiateStreaming) {
264
+ WebAssembly.instantiateStreaming = async function(source, importObject) {
265
+ try {
266
+ const response = await source;
267
+ const url = response.url || "";
268
+ if (url.includes("hb.wasm") || url.endsWith(".wasm") && !url.includes("source.wasm")) {
269
+ console.log(`\u{1F504} Intercepted instantiateStreaming for: ${url}`);
270
+ const module2 = await WebAssembly.compile(wasmBinary);
271
+ const instance = await WebAssembly.instantiate(module2, importObject);
272
+ return { module: module2, instance };
273
+ }
274
+ return originalInstantiateStreaming.call(WebAssembly, response, importObject);
275
+ } catch (err) {
276
+ console.log("\u{1F504} instantiateStreaming failed, using pre-loaded binary");
277
+ const module2 = await WebAssembly.compile(wasmBinary);
278
+ const instance = await WebAssembly.instantiate(module2, importObject);
279
+ return { module: module2, instance };
280
+ }
281
+ };
282
+ }
210
283
  }
211
284
  async function initHB(wasmBaseURL) {
212
285
  if (hbSingleton) return hbSingleton;
213
286
  try {
214
287
  let wasmBinary;
215
- wasmBinary = await loadWasmWeb(wasmBaseURL);
288
+ if (isNode()) {
289
+ wasmBinary = await loadWasmNode();
290
+ } else {
291
+ wasmBinary = await loadWasmWeb(wasmBaseURL);
292
+ }
216
293
  if (!wasmBinary) {
217
294
  throw new Error("Failed to load WASM binary from any source");
218
295
  }
219
296
  console.log(`\u2705 WASM binary loaded successfully (${wasmBinary.byteLength} bytes)`);
220
297
  if (!isNode()) {
221
- setupWasmFetchInterceptor(wasmBinary);
298
+ setupWasmInterceptors(wasmBinary);
222
299
  }
223
300
  const mod = await import("harfbuzzjs");
224
- const candidate = mod.default;
225
301
  let hb;
302
+ const candidate = mod.default || mod;
226
303
  if (typeof candidate === "function") {
227
- hb = await candidate();
304
+ hb = await candidate({
305
+ wasmBinary
306
+ });
228
307
  } else if (candidate && typeof candidate.then === "function") {
229
308
  hb = await candidate;
230
- } else {
309
+ } else if (candidate && typeof candidate.createBuffer === "function") {
231
310
  hb = candidate;
311
+ } else {
312
+ throw new Error(`Unexpected harfbuzzjs export type: ${typeof candidate}`);
232
313
  }
233
314
  if (!hb || typeof hb.createBuffer !== "function" || typeof hb.createFont !== "function") {
234
315
  throw new Error("Failed to initialize HarfBuzz: unexpected export shape from 'harfbuzzjs'.");
@@ -1309,11 +1390,27 @@ async function createNodePainter(opts) {
1309
1390
  const c = parseHex6(color, opacity);
1310
1391
  ctx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
1311
1392
  if (radius && radius > 0) {
1393
+ needsAlphaExtraction = true;
1394
+ ctx.save();
1395
+ ctx.fillStyle = "rgb(255, 255, 255)";
1396
+ ctx.fillRect(0, 0, op.width, op.height);
1397
+ ctx.restore();
1398
+ offscreenCtx.save();
1399
+ offscreenCtx.fillStyle = "rgb(0, 0, 0)";
1400
+ offscreenCtx.fillRect(0, 0, op.width, op.height);
1401
+ offscreenCtx.restore();
1312
1402
  ctx.save();
1403
+ ctx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
1313
1404
  ctx.beginPath();
1314
1405
  roundRectPath(ctx, 0, 0, op.width, op.height, radius);
1315
1406
  ctx.fill();
1316
1407
  ctx.restore();
1408
+ offscreenCtx.save();
1409
+ offscreenCtx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
1410
+ offscreenCtx.beginPath();
1411
+ roundRectPath(offscreenCtx, 0, 0, op.width, op.height, radius);
1412
+ offscreenCtx.fill();
1413
+ offscreenCtx.restore();
1317
1414
  } else {
1318
1415
  ctx.fillRect(0, 0, op.width, op.height);
1319
1416
  }
@@ -1620,7 +1717,7 @@ function tokenizePath2(d) {
1620
1717
  var import_promises = require("fs/promises");
1621
1718
  var http = __toESM(require("http"), 1);
1622
1719
  var https = __toESM(require("https"), 1);
1623
- function bufferToArrayBuffer(buf) {
1720
+ function bufferToArrayBuffer2(buf) {
1624
1721
  const { buffer, byteOffset, byteLength } = buf;
1625
1722
  if (typeof SharedArrayBuffer !== "undefined" && buffer instanceof SharedArrayBuffer) {
1626
1723
  const ab2 = new ArrayBuffer(byteLength);
@@ -1669,10 +1766,10 @@ async function loadFileOrHttpToArrayBuffer(pathOrUrl) {
1669
1766
  reject(new Error(`Request timeout after 30s for ${pathOrUrl}`));
1670
1767
  });
1671
1768
  });
1672
- return bufferToArrayBuffer(buf2);
1769
+ return bufferToArrayBuffer2(buf2);
1673
1770
  }
1674
1771
  const buf = await (0, import_promises.readFile)(pathOrUrl);
1675
- return bufferToArrayBuffer(buf);
1772
+ return bufferToArrayBuffer2(buf);
1676
1773
  } catch (err) {
1677
1774
  if (err instanceof Error) {
1678
1775
  throw new Error(`Failed to load ${pathOrUrl}: ${err.message}`);
@@ -2039,9 +2136,10 @@ async function createTextEngine(opts = {}) {
2039
2136
  try {
2040
2137
  const hasBackground = !!asset.background?.color;
2041
2138
  const hasAnimation = !!asset.animation?.preset;
2042
- const needsAlpha = !hasBackground && hasAnimation;
2139
+ const hasBorderRadius = (asset.background?.borderRadius ?? 0) > 0;
2140
+ const needsAlpha = !hasBackground && hasAnimation || hasBorderRadius;
2043
2141
  console.log(
2044
- `\u{1F3A8} Video settings: Animation=${hasAnimation}, Background=${hasBackground}, Alpha=${needsAlpha}`
2142
+ `\u{1F3A8} Video settings: Animation=${hasAnimation}, Background=${hasBackground}, BorderRadius=${hasBorderRadius}, Alpha=${needsAlpha}`
2045
2143
  );
2046
2144
  const finalOptions = {
2047
2145
  width: asset.width ?? width,
@@ -129,6 +129,57 @@ var hbSingleton = null;
129
129
  function isNode() {
130
130
  return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
131
131
  }
132
+ function bufferToArrayBuffer(buffer) {
133
+ if (buffer.buffer instanceof ArrayBuffer) {
134
+ return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
135
+ }
136
+ const arrayBuffer = new ArrayBuffer(buffer.byteLength);
137
+ const view = new Uint8Array(arrayBuffer);
138
+ for (let i = 0; i < buffer.byteLength; i++) {
139
+ view[i] = buffer[i];
140
+ }
141
+ return arrayBuffer;
142
+ }
143
+ async function loadWasmNode() {
144
+ try {
145
+ const { readFile: readFile2 } = await import("fs/promises");
146
+ const { fileURLToPath } = await import("url");
147
+ const path = await import("path");
148
+ const { createRequire } = await import("module");
149
+ const currentDir = path.dirname(fileURLToPath(import.meta.url));
150
+ let harfbuzzWasmPath;
151
+ try {
152
+ const require2 = createRequire(import.meta.url);
153
+ const harfbuzzPkgPath = require2.resolve("harfbuzzjs/package.json");
154
+ const harfbuzzDir = path.dirname(harfbuzzPkgPath);
155
+ harfbuzzWasmPath = path.join(harfbuzzDir, "hb.wasm");
156
+ } catch {
157
+ }
158
+ const candidates = [
159
+ ...harfbuzzWasmPath ? [harfbuzzWasmPath] : [],
160
+ path.join(currentDir, "../../dist/hb.wasm"),
161
+ path.join(currentDir, "../dist/hb.wasm"),
162
+ path.join(currentDir, "../../assets/wasm/hb.wasm"),
163
+ path.join(currentDir, "../assets/wasm/hb.wasm"),
164
+ path.join(currentDir, "./hb.wasm"),
165
+ path.join(currentDir, "../hb.wasm"),
166
+ path.join(currentDir, "../../node_modules/harfbuzzjs/hb.wasm"),
167
+ path.join(currentDir, "../../../node_modules/harfbuzzjs/hb.wasm")
168
+ ];
169
+ for (const candidate of candidates) {
170
+ try {
171
+ const buffer = await readFile2(candidate);
172
+ console.log(`\u2705 Found WASM at: ${candidate}`);
173
+ return bufferToArrayBuffer(buffer);
174
+ } catch {
175
+ continue;
176
+ }
177
+ }
178
+ return void 0;
179
+ } catch {
180
+ return void 0;
181
+ }
182
+ }
132
183
  async function loadWasmWeb(wasmBaseURL) {
133
184
  try {
134
185
  if (wasmBaseURL) {
@@ -150,14 +201,14 @@ async function loadWasmWeb(wasmBaseURL) {
150
201
  return void 0;
151
202
  }
152
203
  }
153
- function setupWasmFetchInterceptor(wasmBinary) {
204
+ function setupWasmInterceptors(wasmBinary) {
154
205
  const originalFetch = window.fetch;
155
206
  window.fetch = function(input, init) {
156
207
  const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
157
- if (url.includes("hb.wasm") || url.endsWith(".wasm")) {
208
+ if (url.includes("hb.wasm") || url.endsWith(".wasm") && !url.includes("source.wasm")) {
158
209
  console.log(`\u{1F504} Intercepted fetch for: ${url}`);
159
210
  return Promise.resolve(
160
- new Response(wasmBinary, {
211
+ new Response(wasmBinary.slice(0), {
161
212
  status: 200,
162
213
  statusText: "OK",
163
214
  headers: {
@@ -169,28 +220,57 @@ function setupWasmFetchInterceptor(wasmBinary) {
169
220
  }
170
221
  return originalFetch.apply(this, [input, init]);
171
222
  };
223
+ const originalInstantiateStreaming = WebAssembly.instantiateStreaming;
224
+ if (originalInstantiateStreaming) {
225
+ WebAssembly.instantiateStreaming = async function(source, importObject) {
226
+ try {
227
+ const response = await source;
228
+ const url = response.url || "";
229
+ if (url.includes("hb.wasm") || url.endsWith(".wasm") && !url.includes("source.wasm")) {
230
+ console.log(`\u{1F504} Intercepted instantiateStreaming for: ${url}`);
231
+ const module = await WebAssembly.compile(wasmBinary);
232
+ const instance = await WebAssembly.instantiate(module, importObject);
233
+ return { module, instance };
234
+ }
235
+ return originalInstantiateStreaming.call(WebAssembly, response, importObject);
236
+ } catch (err) {
237
+ console.log("\u{1F504} instantiateStreaming failed, using pre-loaded binary");
238
+ const module = await WebAssembly.compile(wasmBinary);
239
+ const instance = await WebAssembly.instantiate(module, importObject);
240
+ return { module, instance };
241
+ }
242
+ };
243
+ }
172
244
  }
173
245
  async function initHB(wasmBaseURL) {
174
246
  if (hbSingleton) return hbSingleton;
175
247
  try {
176
248
  let wasmBinary;
177
- wasmBinary = await loadWasmWeb(wasmBaseURL);
249
+ if (isNode()) {
250
+ wasmBinary = await loadWasmNode();
251
+ } else {
252
+ wasmBinary = await loadWasmWeb(wasmBaseURL);
253
+ }
178
254
  if (!wasmBinary) {
179
255
  throw new Error("Failed to load WASM binary from any source");
180
256
  }
181
257
  console.log(`\u2705 WASM binary loaded successfully (${wasmBinary.byteLength} bytes)`);
182
258
  if (!isNode()) {
183
- setupWasmFetchInterceptor(wasmBinary);
259
+ setupWasmInterceptors(wasmBinary);
184
260
  }
185
261
  const mod = await import("harfbuzzjs");
186
- const candidate = mod.default;
187
262
  let hb;
263
+ const candidate = mod.default || mod;
188
264
  if (typeof candidate === "function") {
189
- hb = await candidate();
265
+ hb = await candidate({
266
+ wasmBinary
267
+ });
190
268
  } else if (candidate && typeof candidate.then === "function") {
191
269
  hb = await candidate;
192
- } else {
270
+ } else if (candidate && typeof candidate.createBuffer === "function") {
193
271
  hb = candidate;
272
+ } else {
273
+ throw new Error(`Unexpected harfbuzzjs export type: ${typeof candidate}`);
194
274
  }
195
275
  if (!hb || typeof hb.createBuffer !== "function" || typeof hb.createFont !== "function") {
196
276
  throw new Error("Failed to initialize HarfBuzz: unexpected export shape from 'harfbuzzjs'.");
@@ -1271,11 +1351,27 @@ async function createNodePainter(opts) {
1271
1351
  const c = parseHex6(color, opacity);
1272
1352
  ctx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
1273
1353
  if (radius && radius > 0) {
1354
+ needsAlphaExtraction = true;
1355
+ ctx.save();
1356
+ ctx.fillStyle = "rgb(255, 255, 255)";
1357
+ ctx.fillRect(0, 0, op.width, op.height);
1358
+ ctx.restore();
1359
+ offscreenCtx.save();
1360
+ offscreenCtx.fillStyle = "rgb(0, 0, 0)";
1361
+ offscreenCtx.fillRect(0, 0, op.width, op.height);
1362
+ offscreenCtx.restore();
1274
1363
  ctx.save();
1364
+ ctx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
1275
1365
  ctx.beginPath();
1276
1366
  roundRectPath(ctx, 0, 0, op.width, op.height, radius);
1277
1367
  ctx.fill();
1278
1368
  ctx.restore();
1369
+ offscreenCtx.save();
1370
+ offscreenCtx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
1371
+ offscreenCtx.beginPath();
1372
+ roundRectPath(offscreenCtx, 0, 0, op.width, op.height, radius);
1373
+ offscreenCtx.fill();
1374
+ offscreenCtx.restore();
1279
1375
  } else {
1280
1376
  ctx.fillRect(0, 0, op.width, op.height);
1281
1377
  }
@@ -1582,7 +1678,7 @@ function tokenizePath2(d) {
1582
1678
  import { readFile } from "fs/promises";
1583
1679
  import * as http from "http";
1584
1680
  import * as https from "https";
1585
- function bufferToArrayBuffer(buf) {
1681
+ function bufferToArrayBuffer2(buf) {
1586
1682
  const { buffer, byteOffset, byteLength } = buf;
1587
1683
  if (typeof SharedArrayBuffer !== "undefined" && buffer instanceof SharedArrayBuffer) {
1588
1684
  const ab2 = new ArrayBuffer(byteLength);
@@ -1631,10 +1727,10 @@ async function loadFileOrHttpToArrayBuffer(pathOrUrl) {
1631
1727
  reject(new Error(`Request timeout after 30s for ${pathOrUrl}`));
1632
1728
  });
1633
1729
  });
1634
- return bufferToArrayBuffer(buf2);
1730
+ return bufferToArrayBuffer2(buf2);
1635
1731
  }
1636
1732
  const buf = await readFile(pathOrUrl);
1637
- return bufferToArrayBuffer(buf);
1733
+ return bufferToArrayBuffer2(buf);
1638
1734
  } catch (err) {
1639
1735
  if (err instanceof Error) {
1640
1736
  throw new Error(`Failed to load ${pathOrUrl}: ${err.message}`);
@@ -2001,9 +2097,10 @@ async function createTextEngine(opts = {}) {
2001
2097
  try {
2002
2098
  const hasBackground = !!asset.background?.color;
2003
2099
  const hasAnimation = !!asset.animation?.preset;
2004
- const needsAlpha = !hasBackground && hasAnimation;
2100
+ const hasBorderRadius = (asset.background?.borderRadius ?? 0) > 0;
2101
+ const needsAlpha = !hasBackground && hasAnimation || hasBorderRadius;
2005
2102
  console.log(
2006
- `\u{1F3A8} Video settings: Animation=${hasAnimation}, Background=${hasBackground}, Alpha=${needsAlpha}`
2103
+ `\u{1F3A8} Video settings: Animation=${hasAnimation}, Background=${hasBackground}, BorderRadius=${hasBorderRadius}, Alpha=${needsAlpha}`
2007
2104
  );
2008
2105
  const finalOptions = {
2009
2106
  width: asset.width ?? width,
package/dist/entry.web.js CHANGED
@@ -133,6 +133,57 @@ var hbSingleton = null;
133
133
  function isNode() {
134
134
  return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
135
135
  }
136
+ function bufferToArrayBuffer(buffer) {
137
+ if (buffer.buffer instanceof ArrayBuffer) {
138
+ return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
139
+ }
140
+ const arrayBuffer = new ArrayBuffer(buffer.byteLength);
141
+ const view = new Uint8Array(arrayBuffer);
142
+ for (let i = 0; i < buffer.byteLength; i++) {
143
+ view[i] = buffer[i];
144
+ }
145
+ return arrayBuffer;
146
+ }
147
+ async function loadWasmNode() {
148
+ try {
149
+ const { readFile } = await import("fs/promises");
150
+ const { fileURLToPath } = await import("url");
151
+ const path = await import("path");
152
+ const { createRequire } = await import("module");
153
+ const currentDir = path.dirname(fileURLToPath(import.meta.url));
154
+ let harfbuzzWasmPath;
155
+ try {
156
+ const require2 = createRequire(import.meta.url);
157
+ const harfbuzzPkgPath = require2.resolve("harfbuzzjs/package.json");
158
+ const harfbuzzDir = path.dirname(harfbuzzPkgPath);
159
+ harfbuzzWasmPath = path.join(harfbuzzDir, "hb.wasm");
160
+ } catch {
161
+ }
162
+ const candidates = [
163
+ ...harfbuzzWasmPath ? [harfbuzzWasmPath] : [],
164
+ path.join(currentDir, "../../dist/hb.wasm"),
165
+ path.join(currentDir, "../dist/hb.wasm"),
166
+ path.join(currentDir, "../../assets/wasm/hb.wasm"),
167
+ path.join(currentDir, "../assets/wasm/hb.wasm"),
168
+ path.join(currentDir, "./hb.wasm"),
169
+ path.join(currentDir, "../hb.wasm"),
170
+ path.join(currentDir, "../../node_modules/harfbuzzjs/hb.wasm"),
171
+ path.join(currentDir, "../../../node_modules/harfbuzzjs/hb.wasm")
172
+ ];
173
+ for (const candidate of candidates) {
174
+ try {
175
+ const buffer = await readFile(candidate);
176
+ console.log(`\u2705 Found WASM at: ${candidate}`);
177
+ return bufferToArrayBuffer(buffer);
178
+ } catch {
179
+ continue;
180
+ }
181
+ }
182
+ return void 0;
183
+ } catch {
184
+ return void 0;
185
+ }
186
+ }
136
187
  async function loadWasmWeb(wasmBaseURL) {
137
188
  try {
138
189
  if (wasmBaseURL) {
@@ -154,14 +205,14 @@ async function loadWasmWeb(wasmBaseURL) {
154
205
  return void 0;
155
206
  }
156
207
  }
157
- function setupWasmFetchInterceptor(wasmBinary) {
208
+ function setupWasmInterceptors(wasmBinary) {
158
209
  const originalFetch = window.fetch;
159
210
  window.fetch = function(input, init) {
160
211
  const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
161
- if (url.includes("hb.wasm") || url.endsWith(".wasm")) {
212
+ if (url.includes("hb.wasm") || url.endsWith(".wasm") && !url.includes("source.wasm")) {
162
213
  console.log(`\u{1F504} Intercepted fetch for: ${url}`);
163
214
  return Promise.resolve(
164
- new Response(wasmBinary, {
215
+ new Response(wasmBinary.slice(0), {
165
216
  status: 200,
166
217
  statusText: "OK",
167
218
  headers: {
@@ -173,28 +224,57 @@ function setupWasmFetchInterceptor(wasmBinary) {
173
224
  }
174
225
  return originalFetch.apply(this, [input, init]);
175
226
  };
227
+ const originalInstantiateStreaming = WebAssembly.instantiateStreaming;
228
+ if (originalInstantiateStreaming) {
229
+ WebAssembly.instantiateStreaming = async function(source, importObject) {
230
+ try {
231
+ const response = await source;
232
+ const url = response.url || "";
233
+ if (url.includes("hb.wasm") || url.endsWith(".wasm") && !url.includes("source.wasm")) {
234
+ console.log(`\u{1F504} Intercepted instantiateStreaming for: ${url}`);
235
+ const module = await WebAssembly.compile(wasmBinary);
236
+ const instance = await WebAssembly.instantiate(module, importObject);
237
+ return { module, instance };
238
+ }
239
+ return originalInstantiateStreaming.call(WebAssembly, response, importObject);
240
+ } catch (err) {
241
+ console.log("\u{1F504} instantiateStreaming failed, using pre-loaded binary");
242
+ const module = await WebAssembly.compile(wasmBinary);
243
+ const instance = await WebAssembly.instantiate(module, importObject);
244
+ return { module, instance };
245
+ }
246
+ };
247
+ }
176
248
  }
177
249
  async function initHB(wasmBaseURL) {
178
250
  if (hbSingleton) return hbSingleton;
179
251
  try {
180
252
  let wasmBinary;
181
- wasmBinary = await loadWasmWeb(wasmBaseURL);
253
+ if (isNode()) {
254
+ wasmBinary = await loadWasmNode();
255
+ } else {
256
+ wasmBinary = await loadWasmWeb(wasmBaseURL);
257
+ }
182
258
  if (!wasmBinary) {
183
259
  throw new Error("Failed to load WASM binary from any source");
184
260
  }
185
261
  console.log(`\u2705 WASM binary loaded successfully (${wasmBinary.byteLength} bytes)`);
186
262
  if (!isNode()) {
187
- setupWasmFetchInterceptor(wasmBinary);
263
+ setupWasmInterceptors(wasmBinary);
188
264
  }
189
265
  const mod = await import("harfbuzzjs");
190
- const candidate = mod.default;
191
266
  let hb;
267
+ const candidate = mod.default || mod;
192
268
  if (typeof candidate === "function") {
193
- hb = await candidate();
269
+ hb = await candidate({
270
+ wasmBinary
271
+ });
194
272
  } else if (candidate && typeof candidate.then === "function") {
195
273
  hb = await candidate;
196
- } else {
274
+ } else if (candidate && typeof candidate.createBuffer === "function") {
197
275
  hb = candidate;
276
+ } else {
277
+ throw new Error(`Unexpected harfbuzzjs export type: ${typeof candidate}`);
198
278
  }
199
279
  if (!hb || typeof hb.createBuffer !== "function" || typeof hb.createFont !== "function") {
200
280
  throw new Error("Failed to initialize HarfBuzz: unexpected export shape from 'harfbuzzjs'.");
@@ -1265,11 +1345,12 @@ function createWebPainter(canvas) {
1265
1345
  const { color, opacity, radius } = op.bg;
1266
1346
  if (color) {
1267
1347
  const c = parseHex6(color, opacity);
1268
- ctx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
1269
1348
  if (radius && radius > 0) {
1349
+ ctx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
1270
1350
  drawRoundedRect(ctx, 0, 0, w, h, radius);
1271
1351
  ctx.fill();
1272
1352
  } else {
1353
+ ctx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
1273
1354
  ctx.fillRect(0, 0, w, h);
1274
1355
  }
1275
1356
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shotstack/shotstack-canvas",
3
- "version": "1.2.9",
3
+ "version": "1.3.1",
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",