@shotstack/shotstack-canvas 1.0.7 → 1.0.9
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 +69 -31
- package/dist/entry.node.cjs.map +1 -1
- package/dist/entry.node.d.cts +1 -0
- package/dist/entry.node.d.ts +1 -0
- package/dist/entry.node.js +68 -31
- package/dist/entry.node.js.map +1 -1
- package/dist/entry.web.d.ts +1 -0
- package/dist/entry.web.js +68 -31
- package/dist/entry.web.js.map +1 -1
- package/package.json +1 -1
package/dist/entry.node.cjs
CHANGED
|
@@ -161,18 +161,31 @@ var RichTextAssetSchema = import_joi.default.object({
|
|
|
161
161
|
}).unknown(false);
|
|
162
162
|
|
|
163
163
|
// src/wasm/hb-loader.ts
|
|
164
|
+
var import_meta = {};
|
|
164
165
|
var hbSingleton = null;
|
|
165
166
|
async function initHB(wasmBaseURL) {
|
|
166
167
|
if (hbSingleton) return hbSingleton;
|
|
167
168
|
const harfbuzzjs = await import("harfbuzzjs");
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
} else {
|
|
174
|
-
hbSingleton = hbPromise;
|
|
169
|
+
const factory = harfbuzzjs.default;
|
|
170
|
+
let hbUrlFromImport;
|
|
171
|
+
try {
|
|
172
|
+
hbUrlFromImport = (await import("harfbuzzjs/hb.wasm?url")).default;
|
|
173
|
+
} catch {
|
|
175
174
|
}
|
|
175
|
+
const base = (() => {
|
|
176
|
+
if (wasmBaseURL) return wasmBaseURL.replace(/\/$/, "");
|
|
177
|
+
if (hbUrlFromImport) return hbUrlFromImport.replace(/hb\.wasm(?:\?.*)?$/, "");
|
|
178
|
+
return new URL(".", import_meta.url).toString().replace(/\/$/, "");
|
|
179
|
+
})();
|
|
180
|
+
const locateFile = (path, scriptDir) => {
|
|
181
|
+
if (path.endsWith(".wasm")) {
|
|
182
|
+
return `${base}/${path}`.replace(/([^:])\/{2,}/g, "$1/");
|
|
183
|
+
}
|
|
184
|
+
return scriptDir ? scriptDir + path : path;
|
|
185
|
+
};
|
|
186
|
+
const initArg = { locateFile };
|
|
187
|
+
const maybePromise = typeof factory === "function" ? factory(initArg) : factory;
|
|
188
|
+
hbSingleton = maybePromise && typeof maybePromise.then === "function" ? await maybePromise : maybePromise;
|
|
176
189
|
if (!hbSingleton || typeof hbSingleton.createBuffer !== "function") {
|
|
177
190
|
throw new Error("Failed to initialize HarfBuzz: invalid API");
|
|
178
191
|
}
|
|
@@ -186,14 +199,33 @@ var FontRegistry = class {
|
|
|
186
199
|
fonts = /* @__PURE__ */ new Map();
|
|
187
200
|
blobs = /* @__PURE__ */ new Map();
|
|
188
201
|
wasmBaseURL;
|
|
202
|
+
initPromise;
|
|
189
203
|
constructor(wasmBaseURL) {
|
|
190
204
|
this.wasmBaseURL = wasmBaseURL;
|
|
191
205
|
}
|
|
192
206
|
async init() {
|
|
193
|
-
if (
|
|
207
|
+
if (this.initPromise) {
|
|
208
|
+
await this.initPromise;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (this.hb) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
this.initPromise = this._doInit();
|
|
215
|
+
await this.initPromise;
|
|
216
|
+
}
|
|
217
|
+
async _doInit() {
|
|
218
|
+
try {
|
|
219
|
+
this.hb = await initHB(this.wasmBaseURL);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
this.initPromise = void 0;
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
194
224
|
}
|
|
195
|
-
getHB() {
|
|
196
|
-
if (!this.hb)
|
|
225
|
+
async getHB() {
|
|
226
|
+
if (!this.hb) {
|
|
227
|
+
await this.init();
|
|
228
|
+
}
|
|
197
229
|
return this.hb;
|
|
198
230
|
}
|
|
199
231
|
key(desc) {
|
|
@@ -212,23 +244,24 @@ var FontRegistry = class {
|
|
|
212
244
|
this.faces.set(k, face);
|
|
213
245
|
this.fonts.set(k, font);
|
|
214
246
|
}
|
|
215
|
-
getFont(desc) {
|
|
247
|
+
async getFont(desc) {
|
|
248
|
+
if (!this.hb) await this.init();
|
|
216
249
|
const k = this.key(desc);
|
|
217
250
|
const f = this.fonts.get(k);
|
|
218
251
|
if (!f) throw new Error(`Font not registered for ${k}`);
|
|
219
252
|
return f;
|
|
220
253
|
}
|
|
221
|
-
getFace(desc) {
|
|
254
|
+
async getFace(desc) {
|
|
255
|
+
if (!this.hb) await this.init();
|
|
222
256
|
const k = this.key(desc);
|
|
223
257
|
return this.faces.get(k);
|
|
224
258
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const face = this.getFace(desc);
|
|
259
|
+
async getUnitsPerEm(desc) {
|
|
260
|
+
const face = await this.getFace(desc);
|
|
228
261
|
return face?.upem || 1e3;
|
|
229
262
|
}
|
|
230
|
-
glyphPath(desc, glyphId) {
|
|
231
|
-
const font = this.getFont(desc);
|
|
263
|
+
async glyphPath(desc, glyphId) {
|
|
264
|
+
const font = await this.getFont(desc);
|
|
232
265
|
const path = font.glyphToPath(glyphId);
|
|
233
266
|
return path && path !== "" ? path : "M 0 0";
|
|
234
267
|
}
|
|
@@ -239,6 +272,8 @@ var FontRegistry = class {
|
|
|
239
272
|
this.fonts.clear();
|
|
240
273
|
this.faces.clear();
|
|
241
274
|
this.blobs.clear();
|
|
275
|
+
this.hb = void 0;
|
|
276
|
+
this.initPromise = void 0;
|
|
242
277
|
}
|
|
243
278
|
};
|
|
244
279
|
|
|
@@ -259,13 +294,13 @@ var LayoutEngine = class {
|
|
|
259
294
|
return t;
|
|
260
295
|
}
|
|
261
296
|
}
|
|
262
|
-
shapeFull(text, desc) {
|
|
263
|
-
const hb = this.fonts.getHB();
|
|
297
|
+
async shapeFull(text, desc) {
|
|
298
|
+
const hb = await this.fonts.getHB();
|
|
264
299
|
const buffer = hb.createBuffer();
|
|
265
300
|
buffer.addText(text);
|
|
266
301
|
buffer.guessSegmentProperties();
|
|
267
|
-
const font = this.fonts.getFont(desc);
|
|
268
|
-
const face = this.fonts.getFace(desc);
|
|
302
|
+
const font = await this.fonts.getFont(desc);
|
|
303
|
+
const face = await this.fonts.getFace(desc);
|
|
269
304
|
const upem = face?.upem || 1e3;
|
|
270
305
|
font.setScale(upem, upem);
|
|
271
306
|
hb.shape(font, buffer);
|
|
@@ -273,17 +308,17 @@ var LayoutEngine = class {
|
|
|
273
308
|
buffer.destroy();
|
|
274
309
|
return result;
|
|
275
310
|
}
|
|
276
|
-
layout(params) {
|
|
311
|
+
async layout(params) {
|
|
277
312
|
const { textTransform, desc, fontSize, letterSpacing, width } = params;
|
|
278
313
|
const input = this.transformText(params.text, textTransform);
|
|
279
314
|
if (!input || input.length === 0) {
|
|
280
315
|
return [];
|
|
281
316
|
}
|
|
282
|
-
const shaped = this.shapeFull(input, desc);
|
|
283
|
-
const face = this.fonts.getFace(desc);
|
|
317
|
+
const shaped = await this.shapeFull(input, desc);
|
|
318
|
+
const face = await this.fonts.getFace(desc);
|
|
284
319
|
const upem = face?.upem || 1e3;
|
|
285
320
|
const scale = fontSize / upem;
|
|
286
|
-
const glyphs = shaped.map((g
|
|
321
|
+
const glyphs = shaped.map((g) => {
|
|
287
322
|
const charIndex = g.cl;
|
|
288
323
|
let char;
|
|
289
324
|
if (charIndex >= 0 && charIndex < input.length) {
|
|
@@ -387,7 +422,7 @@ function decorationGeometry(kind, p) {
|
|
|
387
422
|
}
|
|
388
423
|
|
|
389
424
|
// src/core/drawops.ts
|
|
390
|
-
function buildDrawOps(p) {
|
|
425
|
+
async function buildDrawOps(p) {
|
|
391
426
|
const ops = [];
|
|
392
427
|
ops.push({
|
|
393
428
|
op: "BeginFrame",
|
|
@@ -398,7 +433,7 @@ function buildDrawOps(p) {
|
|
|
398
433
|
bg: p.background ? { color: p.background.color, opacity: p.background.opacity, radius: p.background.borderRadius } : void 0
|
|
399
434
|
});
|
|
400
435
|
if (p.lines.length === 0) return ops;
|
|
401
|
-
const upem = Math.max(1, p.getUnitsPerEm
|
|
436
|
+
const upem = Math.max(1, await p.getUnitsPerEm());
|
|
402
437
|
const scale = p.font.size / upem;
|
|
403
438
|
const blockHeight = p.lines[p.lines.length - 1].y;
|
|
404
439
|
let blockY;
|
|
@@ -434,7 +469,7 @@ function buildDrawOps(p) {
|
|
|
434
469
|
let xCursor = lineX;
|
|
435
470
|
const baselineY = blockY + line.y - p.font.size;
|
|
436
471
|
for (const glyph of line.glyphs) {
|
|
437
|
-
const path = p.glyphPathProvider(glyph.id);
|
|
472
|
+
const path = await p.glyphPathProvider(glyph.id);
|
|
438
473
|
if (!path || path === "M 0 0") {
|
|
439
474
|
xCursor += glyph.xAdvance;
|
|
440
475
|
continue;
|
|
@@ -1426,6 +1461,7 @@ async function createTextEngine(opts = {}) {
|
|
|
1426
1461
|
const fonts = new FontRegistry(wasmBaseURL);
|
|
1427
1462
|
const layout = new LayoutEngine(fonts);
|
|
1428
1463
|
const videoGenerator = new VideoGenerator();
|
|
1464
|
+
await fonts.init();
|
|
1429
1465
|
async function ensureFonts(asset) {
|
|
1430
1466
|
if (asset.customFonts) {
|
|
1431
1467
|
for (const cf of asset.customFonts) {
|
|
@@ -1467,7 +1503,7 @@ async function createTextEngine(opts = {}) {
|
|
|
1467
1503
|
async renderFrame(asset, tSeconds) {
|
|
1468
1504
|
const main = await ensureFonts(asset);
|
|
1469
1505
|
const desc = { family: main.family, weight: `${main.weight}`, style: main.style };
|
|
1470
|
-
const lines = layout.layout({
|
|
1506
|
+
const lines = await layout.layout({
|
|
1471
1507
|
text: asset.text,
|
|
1472
1508
|
width: asset.width ?? width,
|
|
1473
1509
|
letterSpacing: asset.style?.letterSpacing ?? 0,
|
|
@@ -1480,7 +1516,7 @@ async function createTextEngine(opts = {}) {
|
|
|
1480
1516
|
const canvasW = asset.width ?? width;
|
|
1481
1517
|
const canvasH = asset.height ?? height;
|
|
1482
1518
|
const canvasPR = asset.pixelRatio ?? pixelRatio;
|
|
1483
|
-
const ops0 = buildDrawOps({
|
|
1519
|
+
const ops0 = await buildDrawOps({
|
|
1484
1520
|
canvas: { width: canvasW, height: canvasH, pixelRatio: canvasPR },
|
|
1485
1521
|
textRect,
|
|
1486
1522
|
lines,
|
|
@@ -1502,7 +1538,6 @@ async function createTextEngine(opts = {}) {
|
|
|
1502
1538
|
align: asset.align ?? { horizontal: "left", vertical: "middle" },
|
|
1503
1539
|
background: asset.background,
|
|
1504
1540
|
glyphPathProvider: (gid) => fonts.glyphPath(desc, gid),
|
|
1505
|
-
/** NEW: provide UPEM so drawops can compute scale */
|
|
1506
1541
|
getUnitsPerEm: () => fonts.getUnitsPerEm(desc)
|
|
1507
1542
|
});
|
|
1508
1543
|
const ops = applyAnimation(ops0, lines, {
|
|
@@ -1538,6 +1573,9 @@ async function createTextEngine(opts = {}) {
|
|
|
1538
1573
|
return this.renderFrame(asset, time);
|
|
1539
1574
|
};
|
|
1540
1575
|
await videoGenerator.generateVideo(frameGenerator, finalOptions);
|
|
1576
|
+
},
|
|
1577
|
+
destroy() {
|
|
1578
|
+
fonts.destroy();
|
|
1541
1579
|
}
|
|
1542
1580
|
};
|
|
1543
1581
|
}
|