@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.
@@ -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 hbPromise = harfbuzzjs.default;
133
- if (typeof hbPromise === "function") {
134
- hbSingleton = await hbPromise();
135
- } else if (hbPromise && typeof hbPromise.then === "function") {
136
- hbSingleton = await hbPromise;
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 (!this.hb) this.hb = await initHB(this.wasmBaseURL);
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) throw new Error("FontRegistry not initialized - call init() first");
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
- /** NEW: expose units-per-em for scaling glyph paths to px */
190
- getUnitsPerEm(desc) {
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, i) => {
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?.() ?? 1e3);
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
  }