@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.
- package/dist/entry.node.cjs +111 -13
- package/dist/entry.node.js +110 -13
- package/dist/entry.web.js +90 -9
- package/package.json +1 -1
package/dist/entry.node.cjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
1769
|
+
return bufferToArrayBuffer2(buf2);
|
|
1673
1770
|
}
|
|
1674
1771
|
const buf = await (0, import_promises.readFile)(pathOrUrl);
|
|
1675
|
-
return
|
|
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
|
|
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,
|
package/dist/entry.node.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
1730
|
+
return bufferToArrayBuffer2(buf2);
|
|
1635
1731
|
}
|
|
1636
1732
|
const buf = await readFile(pathOrUrl);
|
|
1637
|
-
return
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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