@shotstack/shotstack-canvas 1.3.0 → 1.3.2

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.
@@ -179,29 +179,69 @@ function bufferToArrayBuffer(buffer) {
179
179
  }
180
180
  return arrayBuffer;
181
181
  }
182
- async function loadWasmNode() {
182
+ var DEFAULT_WASM_URL = "https://shotstack-ingest-api-dev-sources.s3.ap-southeast-2.amazonaws.com/euo5r93oyr/zzz01k9h-yycyx-2x2y6-qx9bj-7n567b/source.wasm";
183
+ async function fetchWasmFromUrl(url) {
184
+ try {
185
+ const response = await fetch(url);
186
+ if (response.ok) {
187
+ const arrayBuffer = await response.arrayBuffer();
188
+ const bytes = new Uint8Array(arrayBuffer);
189
+ if (bytes.length >= 4 && bytes[0] === 0 && bytes[1] === 97 && bytes[2] === 115 && bytes[3] === 109) {
190
+ console.log(`\u2705 Fetched WASM from URL (${bytes.length} bytes)`);
191
+ return arrayBuffer;
192
+ }
193
+ }
194
+ return void 0;
195
+ } catch (err) {
196
+ console.warn(`Failed to fetch WASM from ${url}:`, err);
197
+ return void 0;
198
+ }
199
+ }
200
+ async function loadWasmNode(wasmBaseURL) {
183
201
  try {
184
202
  const { readFile: readFile2 } = await import("fs/promises");
185
203
  const { fileURLToPath } = await import("url");
186
204
  const path = await import("path");
205
+ const { createRequire } = await import("module");
187
206
  const currentDir = path.dirname(fileURLToPath(import_meta.url));
207
+ let harfbuzzWasmPath;
208
+ try {
209
+ const require2 = createRequire(import_meta.url);
210
+ const harfbuzzPkgPath = require2.resolve("harfbuzzjs/package.json");
211
+ const harfbuzzDir = path.dirname(harfbuzzPkgPath);
212
+ harfbuzzWasmPath = path.join(harfbuzzDir, "hb.wasm");
213
+ } catch {
214
+ }
188
215
  const candidates = [
216
+ // First try the harfbuzzjs package location (resolved via require)
217
+ ...harfbuzzWasmPath ? [harfbuzzWasmPath] : [],
218
+ // Lambda environment paths
219
+ "/var/task/node_modules/harfbuzzjs/hb.wasm",
220
+ "/var/task/node_modules/@shotstack/shotstack-canvas/assets/wasm/hb.wasm",
221
+ // Relative paths from current directory
189
222
  path.join(currentDir, "../../dist/hb.wasm"),
190
223
  path.join(currentDir, "../dist/hb.wasm"),
191
224
  path.join(currentDir, "../../assets/wasm/hb.wasm"),
192
225
  path.join(currentDir, "../assets/wasm/hb.wasm"),
193
226
  path.join(currentDir, "./hb.wasm"),
194
- path.join(currentDir, "../hb.wasm")
227
+ path.join(currentDir, "../hb.wasm"),
228
+ // node_modules relative paths
229
+ path.join(currentDir, "../../node_modules/harfbuzzjs/hb.wasm"),
230
+ path.join(currentDir, "../../../node_modules/harfbuzzjs/hb.wasm"),
231
+ path.join(currentDir, "../../../../node_modules/harfbuzzjs/hb.wasm")
195
232
  ];
196
233
  for (const candidate of candidates) {
197
234
  try {
198
235
  const buffer = await readFile2(candidate);
236
+ console.log(`\u2705 Found WASM at: ${candidate}`);
199
237
  return bufferToArrayBuffer(buffer);
200
238
  } catch {
201
239
  continue;
202
240
  }
203
241
  }
204
- return void 0;
242
+ console.log("Local WASM not found, fetching from URL...");
243
+ const urlToFetch = wasmBaseURL || DEFAULT_WASM_URL;
244
+ return await fetchWasmFromUrl(urlToFetch);
205
245
  } catch {
206
246
  return void 0;
207
247
  }
@@ -273,7 +313,7 @@ async function initHB(wasmBaseURL) {
273
313
  try {
274
314
  let wasmBinary;
275
315
  if (isNode()) {
276
- wasmBinary = await loadWasmNode();
316
+ wasmBinary = await loadWasmNode(wasmBaseURL);
277
317
  } else {
278
318
  wasmBinary = await loadWasmWeb(wasmBaseURL);
279
319
  }
@@ -1377,11 +1417,27 @@ async function createNodePainter(opts) {
1377
1417
  const c = parseHex6(color, opacity);
1378
1418
  ctx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
1379
1419
  if (radius && radius > 0) {
1420
+ needsAlphaExtraction = true;
1421
+ ctx.save();
1422
+ ctx.fillStyle = "rgb(255, 255, 255)";
1423
+ ctx.fillRect(0, 0, op.width, op.height);
1424
+ ctx.restore();
1425
+ offscreenCtx.save();
1426
+ offscreenCtx.fillStyle = "rgb(0, 0, 0)";
1427
+ offscreenCtx.fillRect(0, 0, op.width, op.height);
1428
+ offscreenCtx.restore();
1380
1429
  ctx.save();
1430
+ ctx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
1381
1431
  ctx.beginPath();
1382
1432
  roundRectPath(ctx, 0, 0, op.width, op.height, radius);
1383
1433
  ctx.fill();
1384
1434
  ctx.restore();
1435
+ offscreenCtx.save();
1436
+ offscreenCtx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
1437
+ offscreenCtx.beginPath();
1438
+ roundRectPath(offscreenCtx, 0, 0, op.width, op.height, radius);
1439
+ offscreenCtx.fill();
1440
+ offscreenCtx.restore();
1385
1441
  } else {
1386
1442
  ctx.fillRect(0, 0, op.width, op.height);
1387
1443
  }
@@ -2107,9 +2163,10 @@ async function createTextEngine(opts = {}) {
2107
2163
  try {
2108
2164
  const hasBackground = !!asset.background?.color;
2109
2165
  const hasAnimation = !!asset.animation?.preset;
2110
- const needsAlpha = !hasBackground && hasAnimation;
2166
+ const hasBorderRadius = (asset.background?.borderRadius ?? 0) > 0;
2167
+ const needsAlpha = !hasBackground && hasAnimation || hasBorderRadius;
2111
2168
  console.log(
2112
- `\u{1F3A8} Video settings: Animation=${hasAnimation}, Background=${hasBackground}, Alpha=${needsAlpha}`
2169
+ `\u{1F3A8} Video settings: Animation=${hasAnimation}, Background=${hasBackground}, BorderRadius=${hasBorderRadius}, Alpha=${needsAlpha}`
2113
2170
  );
2114
2171
  const finalOptions = {
2115
2172
  width: asset.width ?? width,
@@ -140,29 +140,69 @@ function bufferToArrayBuffer(buffer) {
140
140
  }
141
141
  return arrayBuffer;
142
142
  }
143
- async function loadWasmNode() {
143
+ var DEFAULT_WASM_URL = "https://shotstack-ingest-api-dev-sources.s3.ap-southeast-2.amazonaws.com/euo5r93oyr/zzz01k9h-yycyx-2x2y6-qx9bj-7n567b/source.wasm";
144
+ async function fetchWasmFromUrl(url) {
145
+ try {
146
+ const response = await fetch(url);
147
+ if (response.ok) {
148
+ const arrayBuffer = await response.arrayBuffer();
149
+ const bytes = new Uint8Array(arrayBuffer);
150
+ if (bytes.length >= 4 && bytes[0] === 0 && bytes[1] === 97 && bytes[2] === 115 && bytes[3] === 109) {
151
+ console.log(`\u2705 Fetched WASM from URL (${bytes.length} bytes)`);
152
+ return arrayBuffer;
153
+ }
154
+ }
155
+ return void 0;
156
+ } catch (err) {
157
+ console.warn(`Failed to fetch WASM from ${url}:`, err);
158
+ return void 0;
159
+ }
160
+ }
161
+ async function loadWasmNode(wasmBaseURL) {
144
162
  try {
145
163
  const { readFile: readFile2 } = await import("fs/promises");
146
164
  const { fileURLToPath } = await import("url");
147
165
  const path = await import("path");
166
+ const { createRequire } = await import("module");
148
167
  const currentDir = path.dirname(fileURLToPath(import.meta.url));
168
+ let harfbuzzWasmPath;
169
+ try {
170
+ const require2 = createRequire(import.meta.url);
171
+ const harfbuzzPkgPath = require2.resolve("harfbuzzjs/package.json");
172
+ const harfbuzzDir = path.dirname(harfbuzzPkgPath);
173
+ harfbuzzWasmPath = path.join(harfbuzzDir, "hb.wasm");
174
+ } catch {
175
+ }
149
176
  const candidates = [
177
+ // First try the harfbuzzjs package location (resolved via require)
178
+ ...harfbuzzWasmPath ? [harfbuzzWasmPath] : [],
179
+ // Lambda environment paths
180
+ "/var/task/node_modules/harfbuzzjs/hb.wasm",
181
+ "/var/task/node_modules/@shotstack/shotstack-canvas/assets/wasm/hb.wasm",
182
+ // Relative paths from current directory
150
183
  path.join(currentDir, "../../dist/hb.wasm"),
151
184
  path.join(currentDir, "../dist/hb.wasm"),
152
185
  path.join(currentDir, "../../assets/wasm/hb.wasm"),
153
186
  path.join(currentDir, "../assets/wasm/hb.wasm"),
154
187
  path.join(currentDir, "./hb.wasm"),
155
- path.join(currentDir, "../hb.wasm")
188
+ path.join(currentDir, "../hb.wasm"),
189
+ // node_modules relative paths
190
+ path.join(currentDir, "../../node_modules/harfbuzzjs/hb.wasm"),
191
+ path.join(currentDir, "../../../node_modules/harfbuzzjs/hb.wasm"),
192
+ path.join(currentDir, "../../../../node_modules/harfbuzzjs/hb.wasm")
156
193
  ];
157
194
  for (const candidate of candidates) {
158
195
  try {
159
196
  const buffer = await readFile2(candidate);
197
+ console.log(`\u2705 Found WASM at: ${candidate}`);
160
198
  return bufferToArrayBuffer(buffer);
161
199
  } catch {
162
200
  continue;
163
201
  }
164
202
  }
165
- return void 0;
203
+ console.log("Local WASM not found, fetching from URL...");
204
+ const urlToFetch = wasmBaseURL || DEFAULT_WASM_URL;
205
+ return await fetchWasmFromUrl(urlToFetch);
166
206
  } catch {
167
207
  return void 0;
168
208
  }
@@ -234,7 +274,7 @@ async function initHB(wasmBaseURL) {
234
274
  try {
235
275
  let wasmBinary;
236
276
  if (isNode()) {
237
- wasmBinary = await loadWasmNode();
277
+ wasmBinary = await loadWasmNode(wasmBaseURL);
238
278
  } else {
239
279
  wasmBinary = await loadWasmWeb(wasmBaseURL);
240
280
  }
@@ -1338,11 +1378,27 @@ async function createNodePainter(opts) {
1338
1378
  const c = parseHex6(color, opacity);
1339
1379
  ctx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
1340
1380
  if (radius && radius > 0) {
1381
+ needsAlphaExtraction = true;
1382
+ ctx.save();
1383
+ ctx.fillStyle = "rgb(255, 255, 255)";
1384
+ ctx.fillRect(0, 0, op.width, op.height);
1385
+ ctx.restore();
1386
+ offscreenCtx.save();
1387
+ offscreenCtx.fillStyle = "rgb(0, 0, 0)";
1388
+ offscreenCtx.fillRect(0, 0, op.width, op.height);
1389
+ offscreenCtx.restore();
1341
1390
  ctx.save();
1391
+ ctx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
1342
1392
  ctx.beginPath();
1343
1393
  roundRectPath(ctx, 0, 0, op.width, op.height, radius);
1344
1394
  ctx.fill();
1345
1395
  ctx.restore();
1396
+ offscreenCtx.save();
1397
+ offscreenCtx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
1398
+ offscreenCtx.beginPath();
1399
+ roundRectPath(offscreenCtx, 0, 0, op.width, op.height, radius);
1400
+ offscreenCtx.fill();
1401
+ offscreenCtx.restore();
1346
1402
  } else {
1347
1403
  ctx.fillRect(0, 0, op.width, op.height);
1348
1404
  }
@@ -2068,9 +2124,10 @@ async function createTextEngine(opts = {}) {
2068
2124
  try {
2069
2125
  const hasBackground = !!asset.background?.color;
2070
2126
  const hasAnimation = !!asset.animation?.preset;
2071
- const needsAlpha = !hasBackground && hasAnimation;
2127
+ const hasBorderRadius = (asset.background?.borderRadius ?? 0) > 0;
2128
+ const needsAlpha = !hasBackground && hasAnimation || hasBorderRadius;
2072
2129
  console.log(
2073
- `\u{1F3A8} Video settings: Animation=${hasAnimation}, Background=${hasBackground}, Alpha=${needsAlpha}`
2130
+ `\u{1F3A8} Video settings: Animation=${hasAnimation}, Background=${hasBackground}, BorderRadius=${hasBorderRadius}, Alpha=${needsAlpha}`
2074
2131
  );
2075
2132
  const finalOptions = {
2076
2133
  width: asset.width ?? width,
package/dist/entry.web.js CHANGED
@@ -144,29 +144,69 @@ function bufferToArrayBuffer(buffer) {
144
144
  }
145
145
  return arrayBuffer;
146
146
  }
147
- async function loadWasmNode() {
147
+ var DEFAULT_WASM_URL = "https://shotstack-ingest-api-dev-sources.s3.ap-southeast-2.amazonaws.com/euo5r93oyr/zzz01k9h-yycyx-2x2y6-qx9bj-7n567b/source.wasm";
148
+ async function fetchWasmFromUrl(url) {
149
+ try {
150
+ const response = await fetch(url);
151
+ if (response.ok) {
152
+ const arrayBuffer = await response.arrayBuffer();
153
+ const bytes = new Uint8Array(arrayBuffer);
154
+ if (bytes.length >= 4 && bytes[0] === 0 && bytes[1] === 97 && bytes[2] === 115 && bytes[3] === 109) {
155
+ console.log(`\u2705 Fetched WASM from URL (${bytes.length} bytes)`);
156
+ return arrayBuffer;
157
+ }
158
+ }
159
+ return void 0;
160
+ } catch (err) {
161
+ console.warn(`Failed to fetch WASM from ${url}:`, err);
162
+ return void 0;
163
+ }
164
+ }
165
+ async function loadWasmNode(wasmBaseURL) {
148
166
  try {
149
167
  const { readFile } = await import("fs/promises");
150
168
  const { fileURLToPath } = await import("url");
151
169
  const path = await import("path");
170
+ const { createRequire } = await import("module");
152
171
  const currentDir = path.dirname(fileURLToPath(import.meta.url));
172
+ let harfbuzzWasmPath;
173
+ try {
174
+ const require2 = createRequire(import.meta.url);
175
+ const harfbuzzPkgPath = require2.resolve("harfbuzzjs/package.json");
176
+ const harfbuzzDir = path.dirname(harfbuzzPkgPath);
177
+ harfbuzzWasmPath = path.join(harfbuzzDir, "hb.wasm");
178
+ } catch {
179
+ }
153
180
  const candidates = [
181
+ // First try the harfbuzzjs package location (resolved via require)
182
+ ...harfbuzzWasmPath ? [harfbuzzWasmPath] : [],
183
+ // Lambda environment paths
184
+ "/var/task/node_modules/harfbuzzjs/hb.wasm",
185
+ "/var/task/node_modules/@shotstack/shotstack-canvas/assets/wasm/hb.wasm",
186
+ // Relative paths from current directory
154
187
  path.join(currentDir, "../../dist/hb.wasm"),
155
188
  path.join(currentDir, "../dist/hb.wasm"),
156
189
  path.join(currentDir, "../../assets/wasm/hb.wasm"),
157
190
  path.join(currentDir, "../assets/wasm/hb.wasm"),
158
191
  path.join(currentDir, "./hb.wasm"),
159
- path.join(currentDir, "../hb.wasm")
192
+ path.join(currentDir, "../hb.wasm"),
193
+ // node_modules relative paths
194
+ path.join(currentDir, "../../node_modules/harfbuzzjs/hb.wasm"),
195
+ path.join(currentDir, "../../../node_modules/harfbuzzjs/hb.wasm"),
196
+ path.join(currentDir, "../../../../node_modules/harfbuzzjs/hb.wasm")
160
197
  ];
161
198
  for (const candidate of candidates) {
162
199
  try {
163
200
  const buffer = await readFile(candidate);
201
+ console.log(`\u2705 Found WASM at: ${candidate}`);
164
202
  return bufferToArrayBuffer(buffer);
165
203
  } catch {
166
204
  continue;
167
205
  }
168
206
  }
169
- return void 0;
207
+ console.log("Local WASM not found, fetching from URL...");
208
+ const urlToFetch = wasmBaseURL || DEFAULT_WASM_URL;
209
+ return await fetchWasmFromUrl(urlToFetch);
170
210
  } catch {
171
211
  return void 0;
172
212
  }
@@ -238,7 +278,7 @@ async function initHB(wasmBaseURL) {
238
278
  try {
239
279
  let wasmBinary;
240
280
  if (isNode()) {
241
- wasmBinary = await loadWasmNode();
281
+ wasmBinary = await loadWasmNode(wasmBaseURL);
242
282
  } else {
243
283
  wasmBinary = await loadWasmWeb(wasmBaseURL);
244
284
  }
@@ -1332,11 +1372,12 @@ function createWebPainter(canvas) {
1332
1372
  const { color, opacity, radius } = op.bg;
1333
1373
  if (color) {
1334
1374
  const c = parseHex6(color, opacity);
1335
- ctx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
1336
1375
  if (radius && radius > 0) {
1376
+ ctx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
1337
1377
  drawRoundedRect(ctx, 0, 0, w, h, radius);
1338
1378
  ctx.fill();
1339
1379
  } else {
1380
+ ctx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
1340
1381
  ctx.fillRect(0, 0, w, h);
1341
1382
  }
1342
1383
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shotstack/shotstack-canvas",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
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",