@shotstack/shotstack-canvas 1.1.1 → 1.1.3

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
@@ -157,7 +157,7 @@ async function initHB(wasmBaseURL) {
157
157
  }
158
158
 
159
159
  // src/core/font-registry.ts
160
- var FontRegistry = class {
160
+ var _FontRegistry = class _FontRegistry {
161
161
  constructor(wasmBaseURL) {
162
162
  __publicField(this, "hb");
163
163
  __publicField(this, "faces", /* @__PURE__ */ new Map());
@@ -167,14 +167,15 @@ var FontRegistry = class {
167
167
  __publicField(this, "initPromise");
168
168
  this.wasmBaseURL = wasmBaseURL;
169
169
  }
170
+ static setFallbackLoader(loader) {
171
+ _FontRegistry.fallbackLoader = loader;
172
+ }
170
173
  async init() {
171
174
  if (this.initPromise) {
172
175
  await this.initPromise;
173
176
  return;
174
177
  }
175
- if (this.hb) {
176
- return;
177
- }
178
+ if (this.hb) return;
178
179
  this.initPromise = this._doInit();
179
180
  try {
180
181
  await this.initPromise;
@@ -226,11 +227,35 @@ var FontRegistry = class {
226
227
  );
227
228
  }
228
229
  }
230
+ async tryFallbackInstall(desc) {
231
+ const loader = _FontRegistry.fallbackLoader;
232
+ if (!loader) return false;
233
+ try {
234
+ const bytes = await loader({
235
+ family: desc.family,
236
+ weight: desc.weight ?? "400",
237
+ style: desc.style ?? "normal"
238
+ });
239
+ if (!bytes) return false;
240
+ await this.registerFromBytes(bytes, {
241
+ family: desc.family,
242
+ weight: desc.weight ?? "400",
243
+ style: desc.style ?? "normal"
244
+ });
245
+ return true;
246
+ } catch {
247
+ return false;
248
+ }
249
+ }
229
250
  async getFont(desc) {
230
251
  try {
231
252
  if (!this.hb) await this.init();
232
253
  const k = this.key(desc);
233
- const f = this.fonts.get(k);
254
+ let f = this.fonts.get(k);
255
+ if (!f) {
256
+ const installed = await this.tryFallbackInstall(desc);
257
+ f = installed ? this.fonts.get(k) : void 0;
258
+ }
234
259
  if (!f) throw new Error(`Font not registered for ${k}`);
235
260
  return f;
236
261
  } catch (err) {
@@ -246,7 +271,12 @@ var FontRegistry = class {
246
271
  try {
247
272
  if (!this.hb) await this.init();
248
273
  const k = this.key(desc);
249
- return this.faces.get(k);
274
+ let face = this.faces.get(k);
275
+ if (!face) {
276
+ const installed = await this.tryFallbackInstall(desc);
277
+ face = installed ? this.faces.get(k) : void 0;
278
+ }
279
+ return face;
250
280
  } catch (err) {
251
281
  throw new Error(
252
282
  `Failed to get face for "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
@@ -315,6 +345,8 @@ var FontRegistry = class {
315
345
  }
316
346
  }
317
347
  };
348
+ __publicField(_FontRegistry, "fallbackLoader");
349
+ var FontRegistry = _FontRegistry;
318
350
 
319
351
  // src/core/layout.ts
320
352
  var LayoutEngine = class {
@@ -502,7 +534,7 @@ async function buildDrawOps(p) {
502
534
  height: p.canvas.height,
503
535
  pixelRatio: p.canvas.pixelRatio,
504
536
  clear: true,
505
- bg: p.background ? { color: p.background.color, opacity: p.background.opacity, radius: p.background.borderRadius } : void 0
537
+ bg: p.background && p.background.color ? { color: p.background.color, opacity: p.background.opacity, radius: p.background.borderRadius } : void 0
506
538
  });
507
539
  if (p.lines.length === 0) return ops;
508
540
  const upem = Math.max(1, await p.getUnitsPerEm());
@@ -1133,9 +1165,13 @@ function createWebPainter(canvas) {
1133
1165
  canvas.width = Math.floor(w * dpr);
1134
1166
  canvas.height = Math.floor(h * dpr);
1135
1167
  }
1168
+ if ("style" in canvas) {
1169
+ canvas.style.width = `${w}px`;
1170
+ canvas.style.height = `${h}px`;
1171
+ }
1136
1172
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
1137
1173
  if (op.clear) ctx.clearRect(0, 0, w, h);
1138
- if (op.bg) {
1174
+ if (op.bg && op.bg.color) {
1139
1175
  const { color, opacity, radius } = op.bg;
1140
1176
  if (color) {
1141
1177
  const c = parseHex6(color, opacity);
@@ -1365,6 +1401,7 @@ var isGlyphFill2 = (op) => {
1365
1401
  };
1366
1402
 
1367
1403
  // src/env/entry.web.ts
1404
+ var DEFAULT_ROBOTO_URL = "https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu4mxP.ttf";
1368
1405
  async function createTextEngine(opts = {}) {
1369
1406
  const width = opts.width ?? CANVAS_CONFIG.DEFAULTS.width;
1370
1407
  const height = opts.height ?? CANVAS_CONFIG.DEFAULTS.height;
@@ -1373,6 +1410,15 @@ async function createTextEngine(opts = {}) {
1373
1410
  const fonts = new FontRegistry(wasmBaseURL);
1374
1411
  const layout = new LayoutEngine(fonts);
1375
1412
  try {
1413
+ FontRegistry.setFallbackLoader(async (desc) => {
1414
+ const family = (desc.family ?? "Roboto").toLowerCase();
1415
+ const weight = `${desc.weight ?? "400"}`;
1416
+ const style = desc.style ?? "normal";
1417
+ if (family === "roboto" && weight === "400" && style === "normal") {
1418
+ return fetchToArrayBuffer(DEFAULT_ROBOTO_URL);
1419
+ }
1420
+ return void 0;
1421
+ });
1376
1422
  await fonts.init();
1377
1423
  } catch (err) {
1378
1424
  throw new Error(
@@ -1405,11 +1451,30 @@ async function createTextEngine(opts = {}) {
1405
1451
  color: "#000000",
1406
1452
  opacity: 1
1407
1453
  };
1454
+ const desc = { family: main.family, weight: `${main.weight}`, style: main.style };
1455
+ const ensureFace = async () => {
1456
+ try {
1457
+ await fonts.getFace(desc);
1458
+ } catch {
1459
+ const wantsDefaultRoboto = (main.family || "Roboto").toLowerCase() === "roboto" && `${main.weight}` === "400" && main.style === "normal";
1460
+ if (wantsDefaultRoboto) {
1461
+ const bytes = await fetchToArrayBuffer(DEFAULT_ROBOTO_URL);
1462
+ await fonts.registerFromBytes(bytes, {
1463
+ family: "Roboto",
1464
+ weight: "400",
1465
+ style: "normal"
1466
+ });
1467
+ } else {
1468
+ throw new Error(
1469
+ `Font not registered for ${desc.family}__${desc.weight}__${desc.style}`
1470
+ );
1471
+ }
1472
+ }
1473
+ };
1474
+ await ensureFace();
1408
1475
  return main;
1409
1476
  } catch (err) {
1410
- if (err instanceof Error) {
1411
- throw err;
1412
- }
1477
+ if (err instanceof Error) throw err;
1413
1478
  throw new Error(`Failed to ensure fonts: ${String(err)}`);
1414
1479
  }
1415
1480
  }
@@ -1495,10 +1560,13 @@ async function createTextEngine(opts = {}) {
1495
1560
  width: asset.width ?? width,
1496
1561
  height: asset.height ?? height
1497
1562
  };
1563
+ const canvasW = asset.width ?? width;
1564
+ const canvasH = asset.height ?? height;
1565
+ const canvasPR = asset.pixelRatio ?? pixelRatio;
1498
1566
  let ops0;
1499
1567
  try {
1500
1568
  ops0 = await buildDrawOps({
1501
- canvas: { width, height, pixelRatio },
1569
+ canvas: { width: canvasW, height: canvasH, pixelRatio: canvasPR },
1502
1570
  textRect,
1503
1571
  lines,
1504
1572
  font: {
@@ -1516,7 +1584,7 @@ async function createTextEngine(opts = {}) {
1516
1584
  },
1517
1585
  stroke: asset.stroke,
1518
1586
  shadow: asset.shadow,
1519
- align: asset.align ?? { horizontal: "left", vertical: "middle" },
1587
+ align: asset.align ?? { horizontal: "center", vertical: "middle" },
1520
1588
  background: asset.background,
1521
1589
  glyphPathProvider: (gid) => fonts.glyphPath(desc, gid),
1522
1590
  getUnitsPerEm: () => fonts.getUnitsPerEm(desc)