@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.js
CHANGED
|
@@ -129,14 +129,26 @@ var hbSingleton = null;
|
|
|
129
129
|
async function initHB(wasmBaseURL) {
|
|
130
130
|
if (hbSingleton) return hbSingleton;
|
|
131
131
|
const harfbuzzjs = await import("harfbuzzjs");
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
} else {
|
|
138
|
-
hbSingleton = hbPromise;
|
|
132
|
+
const factory = harfbuzzjs.default;
|
|
133
|
+
let hbUrlFromImport;
|
|
134
|
+
try {
|
|
135
|
+
hbUrlFromImport = (await import("harfbuzzjs/hb.wasm?url")).default;
|
|
136
|
+
} catch {
|
|
139
137
|
}
|
|
138
|
+
const base = (() => {
|
|
139
|
+
if (wasmBaseURL) return wasmBaseURL.replace(/\/$/, "");
|
|
140
|
+
if (hbUrlFromImport) return hbUrlFromImport.replace(/hb\.wasm(?:\?.*)?$/, "");
|
|
141
|
+
return new URL(".", import.meta.url).toString().replace(/\/$/, "");
|
|
142
|
+
})();
|
|
143
|
+
const locateFile = (path, scriptDir) => {
|
|
144
|
+
if (path.endsWith(".wasm")) {
|
|
145
|
+
return `${base}/${path}`.replace(/([^:])\/{2,}/g, "$1/");
|
|
146
|
+
}
|
|
147
|
+
return scriptDir ? scriptDir + path : path;
|
|
148
|
+
};
|
|
149
|
+
const initArg = { locateFile };
|
|
150
|
+
const maybePromise = typeof factory === "function" ? factory(initArg) : factory;
|
|
151
|
+
hbSingleton = maybePromise && typeof maybePromise.then === "function" ? await maybePromise : maybePromise;
|
|
140
152
|
if (!hbSingleton || typeof hbSingleton.createBuffer !== "function") {
|
|
141
153
|
throw new Error("Failed to initialize HarfBuzz: invalid API");
|
|
142
154
|
}
|
|
@@ -150,14 +162,33 @@ var FontRegistry = class {
|
|
|
150
162
|
fonts = /* @__PURE__ */ new Map();
|
|
151
163
|
blobs = /* @__PURE__ */ new Map();
|
|
152
164
|
wasmBaseURL;
|
|
165
|
+
initPromise;
|
|
153
166
|
constructor(wasmBaseURL) {
|
|
154
167
|
this.wasmBaseURL = wasmBaseURL;
|
|
155
168
|
}
|
|
156
169
|
async init() {
|
|
157
|
-
if (
|
|
170
|
+
if (this.initPromise) {
|
|
171
|
+
await this.initPromise;
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (this.hb) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
this.initPromise = this._doInit();
|
|
178
|
+
await this.initPromise;
|
|
179
|
+
}
|
|
180
|
+
async _doInit() {
|
|
181
|
+
try {
|
|
182
|
+
this.hb = await initHB(this.wasmBaseURL);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
this.initPromise = void 0;
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
158
187
|
}
|
|
159
|
-
getHB() {
|
|
160
|
-
if (!this.hb)
|
|
188
|
+
async getHB() {
|
|
189
|
+
if (!this.hb) {
|
|
190
|
+
await this.init();
|
|
191
|
+
}
|
|
161
192
|
return this.hb;
|
|
162
193
|
}
|
|
163
194
|
key(desc) {
|
|
@@ -176,23 +207,24 @@ var FontRegistry = class {
|
|
|
176
207
|
this.faces.set(k, face);
|
|
177
208
|
this.fonts.set(k, font);
|
|
178
209
|
}
|
|
179
|
-
getFont(desc) {
|
|
210
|
+
async getFont(desc) {
|
|
211
|
+
if (!this.hb) await this.init();
|
|
180
212
|
const k = this.key(desc);
|
|
181
213
|
const f = this.fonts.get(k);
|
|
182
214
|
if (!f) throw new Error(`Font not registered for ${k}`);
|
|
183
215
|
return f;
|
|
184
216
|
}
|
|
185
|
-
getFace(desc) {
|
|
217
|
+
async getFace(desc) {
|
|
218
|
+
if (!this.hb) await this.init();
|
|
186
219
|
const k = this.key(desc);
|
|
187
220
|
return this.faces.get(k);
|
|
188
221
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const face = this.getFace(desc);
|
|
222
|
+
async getUnitsPerEm(desc) {
|
|
223
|
+
const face = await this.getFace(desc);
|
|
192
224
|
return face?.upem || 1e3;
|
|
193
225
|
}
|
|
194
|
-
glyphPath(desc, glyphId) {
|
|
195
|
-
const font = this.getFont(desc);
|
|
226
|
+
async glyphPath(desc, glyphId) {
|
|
227
|
+
const font = await this.getFont(desc);
|
|
196
228
|
const path = font.glyphToPath(glyphId);
|
|
197
229
|
return path && path !== "" ? path : "M 0 0";
|
|
198
230
|
}
|
|
@@ -203,6 +235,8 @@ var FontRegistry = class {
|
|
|
203
235
|
this.fonts.clear();
|
|
204
236
|
this.faces.clear();
|
|
205
237
|
this.blobs.clear();
|
|
238
|
+
this.hb = void 0;
|
|
239
|
+
this.initPromise = void 0;
|
|
206
240
|
}
|
|
207
241
|
};
|
|
208
242
|
|
|
@@ -223,13 +257,13 @@ var LayoutEngine = class {
|
|
|
223
257
|
return t;
|
|
224
258
|
}
|
|
225
259
|
}
|
|
226
|
-
shapeFull(text, desc) {
|
|
227
|
-
const hb = this.fonts.getHB();
|
|
260
|
+
async shapeFull(text, desc) {
|
|
261
|
+
const hb = await this.fonts.getHB();
|
|
228
262
|
const buffer = hb.createBuffer();
|
|
229
263
|
buffer.addText(text);
|
|
230
264
|
buffer.guessSegmentProperties();
|
|
231
|
-
const font = this.fonts.getFont(desc);
|
|
232
|
-
const face = this.fonts.getFace(desc);
|
|
265
|
+
const font = await this.fonts.getFont(desc);
|
|
266
|
+
const face = await this.fonts.getFace(desc);
|
|
233
267
|
const upem = face?.upem || 1e3;
|
|
234
268
|
font.setScale(upem, upem);
|
|
235
269
|
hb.shape(font, buffer);
|
|
@@ -237,17 +271,17 @@ var LayoutEngine = class {
|
|
|
237
271
|
buffer.destroy();
|
|
238
272
|
return result;
|
|
239
273
|
}
|
|
240
|
-
layout(params) {
|
|
274
|
+
async layout(params) {
|
|
241
275
|
const { textTransform, desc, fontSize, letterSpacing, width } = params;
|
|
242
276
|
const input = this.transformText(params.text, textTransform);
|
|
243
277
|
if (!input || input.length === 0) {
|
|
244
278
|
return [];
|
|
245
279
|
}
|
|
246
|
-
const shaped = this.shapeFull(input, desc);
|
|
247
|
-
const face = this.fonts.getFace(desc);
|
|
280
|
+
const shaped = await this.shapeFull(input, desc);
|
|
281
|
+
const face = await this.fonts.getFace(desc);
|
|
248
282
|
const upem = face?.upem || 1e3;
|
|
249
283
|
const scale = fontSize / upem;
|
|
250
|
-
const glyphs = shaped.map((g
|
|
284
|
+
const glyphs = shaped.map((g) => {
|
|
251
285
|
const charIndex = g.cl;
|
|
252
286
|
let char;
|
|
253
287
|
if (charIndex >= 0 && charIndex < input.length) {
|
|
@@ -351,7 +385,7 @@ function decorationGeometry(kind, p) {
|
|
|
351
385
|
}
|
|
352
386
|
|
|
353
387
|
// src/core/drawops.ts
|
|
354
|
-
function buildDrawOps(p) {
|
|
388
|
+
async function buildDrawOps(p) {
|
|
355
389
|
const ops = [];
|
|
356
390
|
ops.push({
|
|
357
391
|
op: "BeginFrame",
|
|
@@ -362,7 +396,7 @@ function buildDrawOps(p) {
|
|
|
362
396
|
bg: p.background ? { color: p.background.color, opacity: p.background.opacity, radius: p.background.borderRadius } : void 0
|
|
363
397
|
});
|
|
364
398
|
if (p.lines.length === 0) return ops;
|
|
365
|
-
const upem = Math.max(1, p.getUnitsPerEm
|
|
399
|
+
const upem = Math.max(1, await p.getUnitsPerEm());
|
|
366
400
|
const scale = p.font.size / upem;
|
|
367
401
|
const blockHeight = p.lines[p.lines.length - 1].y;
|
|
368
402
|
let blockY;
|
|
@@ -398,7 +432,7 @@ function buildDrawOps(p) {
|
|
|
398
432
|
let xCursor = lineX;
|
|
399
433
|
const baselineY = blockY + line.y - p.font.size;
|
|
400
434
|
for (const glyph of line.glyphs) {
|
|
401
|
-
const path = p.glyphPathProvider(glyph.id);
|
|
435
|
+
const path = await p.glyphPathProvider(glyph.id);
|
|
402
436
|
if (!path || path === "M 0 0") {
|
|
403
437
|
xCursor += glyph.xAdvance;
|
|
404
438
|
continue;
|
|
@@ -1390,6 +1424,7 @@ async function createTextEngine(opts = {}) {
|
|
|
1390
1424
|
const fonts = new FontRegistry(wasmBaseURL);
|
|
1391
1425
|
const layout = new LayoutEngine(fonts);
|
|
1392
1426
|
const videoGenerator = new VideoGenerator();
|
|
1427
|
+
await fonts.init();
|
|
1393
1428
|
async function ensureFonts(asset) {
|
|
1394
1429
|
if (asset.customFonts) {
|
|
1395
1430
|
for (const cf of asset.customFonts) {
|
|
@@ -1431,7 +1466,7 @@ async function createTextEngine(opts = {}) {
|
|
|
1431
1466
|
async renderFrame(asset, tSeconds) {
|
|
1432
1467
|
const main = await ensureFonts(asset);
|
|
1433
1468
|
const desc = { family: main.family, weight: `${main.weight}`, style: main.style };
|
|
1434
|
-
const lines = layout.layout({
|
|
1469
|
+
const lines = await layout.layout({
|
|
1435
1470
|
text: asset.text,
|
|
1436
1471
|
width: asset.width ?? width,
|
|
1437
1472
|
letterSpacing: asset.style?.letterSpacing ?? 0,
|
|
@@ -1444,7 +1479,7 @@ async function createTextEngine(opts = {}) {
|
|
|
1444
1479
|
const canvasW = asset.width ?? width;
|
|
1445
1480
|
const canvasH = asset.height ?? height;
|
|
1446
1481
|
const canvasPR = asset.pixelRatio ?? pixelRatio;
|
|
1447
|
-
const ops0 = buildDrawOps({
|
|
1482
|
+
const ops0 = await buildDrawOps({
|
|
1448
1483
|
canvas: { width: canvasW, height: canvasH, pixelRatio: canvasPR },
|
|
1449
1484
|
textRect,
|
|
1450
1485
|
lines,
|
|
@@ -1466,7 +1501,6 @@ async function createTextEngine(opts = {}) {
|
|
|
1466
1501
|
align: asset.align ?? { horizontal: "left", vertical: "middle" },
|
|
1467
1502
|
background: asset.background,
|
|
1468
1503
|
glyphPathProvider: (gid) => fonts.glyphPath(desc, gid),
|
|
1469
|
-
/** NEW: provide UPEM so drawops can compute scale */
|
|
1470
1504
|
getUnitsPerEm: () => fonts.getUnitsPerEm(desc)
|
|
1471
1505
|
});
|
|
1472
1506
|
const ops = applyAnimation(ops0, lines, {
|
|
@@ -1502,6 +1536,9 @@ async function createTextEngine(opts = {}) {
|
|
|
1502
1536
|
return this.renderFrame(asset, time);
|
|
1503
1537
|
};
|
|
1504
1538
|
await videoGenerator.generateVideo(frameGenerator, finalOptions);
|
|
1539
|
+
},
|
|
1540
|
+
destroy() {
|
|
1541
|
+
fonts.destroy();
|
|
1505
1542
|
}
|
|
1506
1543
|
};
|
|
1507
1544
|
}
|