@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.web.js CHANGED
@@ -133,14 +133,26 @@ var hbSingleton = null;
133
133
  async function initHB(wasmBaseURL) {
134
134
  if (hbSingleton) return hbSingleton;
135
135
  const harfbuzzjs = await import("harfbuzzjs");
136
- const hbPromise = harfbuzzjs.default;
137
- if (typeof hbPromise === "function") {
138
- hbSingleton = await hbPromise();
139
- } else if (hbPromise && typeof hbPromise.then === "function") {
140
- hbSingleton = await hbPromise;
141
- } else {
142
- hbSingleton = hbPromise;
136
+ const factory = harfbuzzjs.default;
137
+ let hbUrlFromImport;
138
+ try {
139
+ hbUrlFromImport = (await import("harfbuzzjs/hb.wasm?url")).default;
140
+ } catch {
143
141
  }
142
+ const base = (() => {
143
+ if (wasmBaseURL) return wasmBaseURL.replace(/\/$/, "");
144
+ if (hbUrlFromImport) return hbUrlFromImport.replace(/hb\.wasm(?:\?.*)?$/, "");
145
+ return new URL(".", import.meta.url).toString().replace(/\/$/, "");
146
+ })();
147
+ const locateFile = (path, scriptDir) => {
148
+ if (path.endsWith(".wasm")) {
149
+ return `${base}/${path}`.replace(/([^:])\/{2,}/g, "$1/");
150
+ }
151
+ return scriptDir ? scriptDir + path : path;
152
+ };
153
+ const initArg = { locateFile };
154
+ const maybePromise = typeof factory === "function" ? factory(initArg) : factory;
155
+ hbSingleton = maybePromise && typeof maybePromise.then === "function" ? await maybePromise : maybePromise;
144
156
  if (!hbSingleton || typeof hbSingleton.createBuffer !== "function") {
145
157
  throw new Error("Failed to initialize HarfBuzz: invalid API");
146
158
  }
@@ -155,13 +167,32 @@ var FontRegistry = class {
155
167
  __publicField(this, "fonts", /* @__PURE__ */ new Map());
156
168
  __publicField(this, "blobs", /* @__PURE__ */ new Map());
157
169
  __publicField(this, "wasmBaseURL");
170
+ __publicField(this, "initPromise");
158
171
  this.wasmBaseURL = wasmBaseURL;
159
172
  }
160
173
  async init() {
161
- if (!this.hb) this.hb = await initHB(this.wasmBaseURL);
174
+ if (this.initPromise) {
175
+ await this.initPromise;
176
+ return;
177
+ }
178
+ if (this.hb) {
179
+ return;
180
+ }
181
+ this.initPromise = this._doInit();
182
+ await this.initPromise;
162
183
  }
163
- getHB() {
164
- if (!this.hb) throw new Error("FontRegistry not initialized - call init() first");
184
+ async _doInit() {
185
+ try {
186
+ this.hb = await initHB(this.wasmBaseURL);
187
+ } catch (error) {
188
+ this.initPromise = void 0;
189
+ throw error;
190
+ }
191
+ }
192
+ async getHB() {
193
+ if (!this.hb) {
194
+ await this.init();
195
+ }
165
196
  return this.hb;
166
197
  }
167
198
  key(desc) {
@@ -180,23 +211,24 @@ var FontRegistry = class {
180
211
  this.faces.set(k, face);
181
212
  this.fonts.set(k, font);
182
213
  }
183
- getFont(desc) {
214
+ async getFont(desc) {
215
+ if (!this.hb) await this.init();
184
216
  const k = this.key(desc);
185
217
  const f = this.fonts.get(k);
186
218
  if (!f) throw new Error(`Font not registered for ${k}`);
187
219
  return f;
188
220
  }
189
- getFace(desc) {
221
+ async getFace(desc) {
222
+ if (!this.hb) await this.init();
190
223
  const k = this.key(desc);
191
224
  return this.faces.get(k);
192
225
  }
193
- /** NEW: expose units-per-em for scaling glyph paths to px */
194
- getUnitsPerEm(desc) {
195
- const face = this.getFace(desc);
226
+ async getUnitsPerEm(desc) {
227
+ const face = await this.getFace(desc);
196
228
  return face?.upem || 1e3;
197
229
  }
198
- glyphPath(desc, glyphId) {
199
- const font = this.getFont(desc);
230
+ async glyphPath(desc, glyphId) {
231
+ const font = await this.getFont(desc);
200
232
  const path = font.glyphToPath(glyphId);
201
233
  return path && path !== "" ? path : "M 0 0";
202
234
  }
@@ -207,6 +239,8 @@ var FontRegistry = class {
207
239
  this.fonts.clear();
208
240
  this.faces.clear();
209
241
  this.blobs.clear();
242
+ this.hb = void 0;
243
+ this.initPromise = void 0;
210
244
  }
211
245
  };
212
246
 
@@ -227,13 +261,13 @@ var LayoutEngine = class {
227
261
  return t;
228
262
  }
229
263
  }
230
- shapeFull(text, desc) {
231
- const hb = this.fonts.getHB();
264
+ async shapeFull(text, desc) {
265
+ const hb = await this.fonts.getHB();
232
266
  const buffer = hb.createBuffer();
233
267
  buffer.addText(text);
234
268
  buffer.guessSegmentProperties();
235
- const font = this.fonts.getFont(desc);
236
- const face = this.fonts.getFace(desc);
269
+ const font = await this.fonts.getFont(desc);
270
+ const face = await this.fonts.getFace(desc);
237
271
  const upem = face?.upem || 1e3;
238
272
  font.setScale(upem, upem);
239
273
  hb.shape(font, buffer);
@@ -241,17 +275,17 @@ var LayoutEngine = class {
241
275
  buffer.destroy();
242
276
  return result;
243
277
  }
244
- layout(params) {
278
+ async layout(params) {
245
279
  const { textTransform, desc, fontSize, letterSpacing, width } = params;
246
280
  const input = this.transformText(params.text, textTransform);
247
281
  if (!input || input.length === 0) {
248
282
  return [];
249
283
  }
250
- const shaped = this.shapeFull(input, desc);
251
- const face = this.fonts.getFace(desc);
284
+ const shaped = await this.shapeFull(input, desc);
285
+ const face = await this.fonts.getFace(desc);
252
286
  const upem = face?.upem || 1e3;
253
287
  const scale = fontSize / upem;
254
- const glyphs = shaped.map((g, i) => {
288
+ const glyphs = shaped.map((g) => {
255
289
  const charIndex = g.cl;
256
290
  let char;
257
291
  if (charIndex >= 0 && charIndex < input.length) {
@@ -355,7 +389,7 @@ function decorationGeometry(kind, p) {
355
389
  }
356
390
 
357
391
  // src/core/drawops.ts
358
- function buildDrawOps(p) {
392
+ async function buildDrawOps(p) {
359
393
  const ops = [];
360
394
  ops.push({
361
395
  op: "BeginFrame",
@@ -366,7 +400,7 @@ function buildDrawOps(p) {
366
400
  bg: p.background ? { color: p.background.color, opacity: p.background.opacity, radius: p.background.borderRadius } : void 0
367
401
  });
368
402
  if (p.lines.length === 0) return ops;
369
- const upem = Math.max(1, p.getUnitsPerEm?.() ?? 1e3);
403
+ const upem = Math.max(1, await p.getUnitsPerEm());
370
404
  const scale = p.font.size / upem;
371
405
  const blockHeight = p.lines[p.lines.length - 1].y;
372
406
  let blockY;
@@ -402,7 +436,7 @@ function buildDrawOps(p) {
402
436
  let xCursor = lineX;
403
437
  const baselineY = blockY + line.y - p.font.size;
404
438
  for (const glyph of line.glyphs) {
405
- const path = p.glyphPathProvider(glyph.id);
439
+ const path = await p.glyphPathProvider(glyph.id);
406
440
  if (!path || path === "M 0 0") {
407
441
  xCursor += glyph.xAdvance;
408
442
  continue;
@@ -1180,6 +1214,7 @@ async function createTextEngine(opts = {}) {
1180
1214
  const wasmBaseURL = opts.wasmBaseURL;
1181
1215
  const fonts = new FontRegistry(wasmBaseURL);
1182
1216
  const layout = new LayoutEngine(fonts);
1217
+ await fonts.init();
1183
1218
  async function ensureFonts(asset) {
1184
1219
  if (asset.customFonts) {
1185
1220
  for (const cf of asset.customFonts) {
@@ -1226,7 +1261,7 @@ async function createTextEngine(opts = {}) {
1226
1261
  async renderFrame(asset, tSeconds) {
1227
1262
  const main = await ensureFonts(asset);
1228
1263
  const desc = { family: main.family, weight: `${main.weight}`, style: main.style };
1229
- const lines = layout.layout({
1264
+ const lines = await layout.layout({
1230
1265
  text: asset.text,
1231
1266
  width: asset.width ?? width,
1232
1267
  letterSpacing: asset.style?.letterSpacing ?? 0,
@@ -1236,7 +1271,7 @@ async function createTextEngine(opts = {}) {
1236
1271
  textTransform: asset.style?.textTransform ?? "none"
1237
1272
  });
1238
1273
  const textRect = { x: 0, y: 0, width: asset.width ?? width, height: asset.height ?? height };
1239
- const ops0 = buildDrawOps({
1274
+ const ops0 = await buildDrawOps({
1240
1275
  canvas: { width, height, pixelRatio },
1241
1276
  textRect,
1242
1277
  lines,
@@ -1258,7 +1293,6 @@ async function createTextEngine(opts = {}) {
1258
1293
  align: asset.align ?? { horizontal: "left", vertical: "middle" },
1259
1294
  background: asset.background,
1260
1295
  glyphPathProvider: (gid) => fonts.glyphPath(desc, gid),
1261
- /** NEW: provide UPEM so drawops can compute scale */
1262
1296
  getUnitsPerEm: () => fonts.getUnitsPerEm(desc)
1263
1297
  });
1264
1298
  const ops = applyAnimation(ops0, lines, {
@@ -1276,6 +1310,9 @@ async function createTextEngine(opts = {}) {
1276
1310
  },
1277
1311
  createRenderer(canvas) {
1278
1312
  return createWebPainter(canvas);
1313
+ },
1314
+ destroy() {
1315
+ fonts.destroy();
1279
1316
  }
1280
1317
  };
1281
1318
  }