@shotstack/shotstack-canvas 1.1.0 → 1.1.1

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
@@ -132,19 +132,28 @@ var RichTextAssetSchema = Joi.object({
132
132
  var hbSingleton = null;
133
133
  async function initHB(wasmBaseURL) {
134
134
  if (hbSingleton) return hbSingleton;
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;
143
- }
144
- if (!hbSingleton || typeof hbSingleton.createBuffer !== "function") {
145
- throw new Error("Failed to initialize HarfBuzz: invalid API");
135
+ try {
136
+ const mod = await import("harfbuzzjs");
137
+ const candidate = mod.default;
138
+ let hb;
139
+ if (typeof candidate === "function") {
140
+ hb = await candidate();
141
+ } else if (candidate && typeof candidate.then === "function") {
142
+ hb = await candidate;
143
+ } else {
144
+ hb = candidate;
145
+ }
146
+ if (!hb || typeof hb.createBuffer !== "function" || typeof hb.createFont !== "function") {
147
+ throw new Error("Failed to initialize HarfBuzz: unexpected export shape from 'harfbuzzjs'.");
148
+ }
149
+ void wasmBaseURL;
150
+ hbSingleton = hb;
151
+ return hbSingleton;
152
+ } catch (err) {
153
+ throw new Error(
154
+ `Failed to initialize HarfBuzz: ${err instanceof Error ? err.message : String(err)}`
155
+ );
146
156
  }
147
- return hbSingleton;
148
157
  }
149
158
 
150
159
  // src/core/font-registry.ts
@@ -167,7 +176,13 @@ var FontRegistry = class {
167
176
  return;
168
177
  }
169
178
  this.initPromise = this._doInit();
170
- await this.initPromise;
179
+ try {
180
+ await this.initPromise;
181
+ } catch (err) {
182
+ throw new Error(
183
+ `Failed to initialize FontRegistry: ${err instanceof Error ? err.message : String(err)}`
184
+ );
185
+ }
171
186
  }
172
187
  async _doInit() {
173
188
  try {
@@ -179,7 +194,13 @@ var FontRegistry = class {
179
194
  }
180
195
  async getHB() {
181
196
  if (!this.hb) {
182
- await this.init();
197
+ try {
198
+ await this.init();
199
+ } catch (err) {
200
+ throw new Error(
201
+ `Failed to get HarfBuzz instance: ${err instanceof Error ? err.message : String(err)}`
202
+ );
203
+ }
183
204
  }
184
205
  return this.hb;
185
206
  }
@@ -187,48 +208,111 @@ var FontRegistry = class {
187
208
  return `${desc.family}__${desc.weight ?? "400"}__${desc.style ?? "normal"}`;
188
209
  }
189
210
  async registerFromBytes(bytes, desc) {
190
- if (!this.hb) await this.init();
191
- const k = this.key(desc);
192
- if (this.fonts.has(k)) return;
193
- const blob = this.hb.createBlob(bytes);
194
- const face = this.hb.createFace(blob, 0);
195
- const font = this.hb.createFont(face);
196
- const upem = face.upem || 1e3;
197
- font.setScale(upem, upem);
198
- this.blobs.set(k, blob);
199
- this.faces.set(k, face);
200
- this.fonts.set(k, font);
211
+ try {
212
+ if (!this.hb) await this.init();
213
+ const k = this.key(desc);
214
+ if (this.fonts.has(k)) return;
215
+ const blob = this.hb.createBlob(bytes);
216
+ const face = this.hb.createFace(blob, 0);
217
+ const font = this.hb.createFont(face);
218
+ const upem = face.upem || 1e3;
219
+ font.setScale(upem, upem);
220
+ this.blobs.set(k, blob);
221
+ this.faces.set(k, face);
222
+ this.fonts.set(k, font);
223
+ } catch (err) {
224
+ throw new Error(
225
+ `Failed to register font "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
226
+ );
227
+ }
201
228
  }
202
229
  async getFont(desc) {
203
- if (!this.hb) await this.init();
204
- const k = this.key(desc);
205
- const f = this.fonts.get(k);
206
- if (!f) throw new Error(`Font not registered for ${k}`);
207
- return f;
230
+ try {
231
+ if (!this.hb) await this.init();
232
+ const k = this.key(desc);
233
+ const f = this.fonts.get(k);
234
+ if (!f) throw new Error(`Font not registered for ${k}`);
235
+ return f;
236
+ } catch (err) {
237
+ if (err instanceof Error && err.message.includes("Font not registered")) {
238
+ throw err;
239
+ }
240
+ throw new Error(
241
+ `Failed to get font "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
242
+ );
243
+ }
208
244
  }
209
245
  async getFace(desc) {
210
- if (!this.hb) await this.init();
211
- const k = this.key(desc);
212
- return this.faces.get(k);
246
+ try {
247
+ if (!this.hb) await this.init();
248
+ const k = this.key(desc);
249
+ return this.faces.get(k);
250
+ } catch (err) {
251
+ throw new Error(
252
+ `Failed to get face for "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
253
+ );
254
+ }
213
255
  }
214
256
  async getUnitsPerEm(desc) {
215
- const face = await this.getFace(desc);
216
- return face?.upem || 1e3;
257
+ try {
258
+ const face = await this.getFace(desc);
259
+ return face?.upem || 1e3;
260
+ } catch (err) {
261
+ throw new Error(
262
+ `Failed to get units per em for "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
263
+ );
264
+ }
217
265
  }
218
266
  async glyphPath(desc, glyphId) {
219
- const font = await this.getFont(desc);
220
- const path = font.glyphToPath(glyphId);
221
- return path && path !== "" ? path : "M 0 0";
267
+ try {
268
+ const font = await this.getFont(desc);
269
+ const path = font.glyphToPath(glyphId);
270
+ return path && path !== "" ? path : "M 0 0";
271
+ } catch (err) {
272
+ throw new Error(
273
+ `Failed to get glyph path for glyph ${glyphId} in font "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
274
+ );
275
+ }
222
276
  }
223
277
  destroy() {
224
- for (const [, f] of this.fonts) f.destroy?.();
225
- for (const [, f] of this.faces) f.destroy?.();
226
- for (const [, b] of this.blobs) b.destroy?.();
227
- this.fonts.clear();
228
- this.faces.clear();
229
- this.blobs.clear();
230
- this.hb = void 0;
231
- this.initPromise = void 0;
278
+ try {
279
+ for (const [, f] of this.fonts) {
280
+ try {
281
+ f.destroy();
282
+ } catch (err) {
283
+ console.error(
284
+ `Error destroying font: ${err instanceof Error ? err.message : String(err)}`
285
+ );
286
+ }
287
+ }
288
+ for (const [, f] of this.faces) {
289
+ try {
290
+ f.destroy();
291
+ } catch (err) {
292
+ console.error(
293
+ `Error destroying face: ${err instanceof Error ? err.message : String(err)}`
294
+ );
295
+ }
296
+ }
297
+ for (const [, b] of this.blobs) {
298
+ try {
299
+ b.destroy();
300
+ } catch (err) {
301
+ console.error(
302
+ `Error destroying blob: ${err instanceof Error ? err.message : String(err)}`
303
+ );
304
+ }
305
+ }
306
+ this.fonts.clear();
307
+ this.faces.clear();
308
+ this.blobs.clear();
309
+ this.hb = void 0;
310
+ this.initPromise = void 0;
311
+ } catch (err) {
312
+ console.error(
313
+ `Error during FontRegistry cleanup: ${err instanceof Error ? err.message : String(err)}`
314
+ );
315
+ }
232
316
  }
233
317
  };
234
318
 
@@ -250,112 +334,145 @@ var LayoutEngine = class {
250
334
  }
251
335
  }
252
336
  async shapeFull(text, desc) {
253
- const hb = await this.fonts.getHB();
254
- const buffer = hb.createBuffer();
255
- buffer.addText(text);
256
- buffer.guessSegmentProperties();
257
- const font = await this.fonts.getFont(desc);
258
- const face = await this.fonts.getFace(desc);
259
- const upem = face?.upem || 1e3;
260
- font.setScale(upem, upem);
261
- hb.shape(font, buffer);
262
- const result = buffer.json();
263
- buffer.destroy();
264
- return result;
337
+ try {
338
+ const hb = await this.fonts.getHB();
339
+ const buffer = hb.createBuffer();
340
+ try {
341
+ buffer.addText(text);
342
+ buffer.guessSegmentProperties();
343
+ const font = await this.fonts.getFont(desc);
344
+ const face = await this.fonts.getFace(desc);
345
+ const upem = face?.upem || 1e3;
346
+ font.setScale(upem, upem);
347
+ hb.shape(font, buffer);
348
+ const result = buffer.json();
349
+ return result;
350
+ } finally {
351
+ try {
352
+ buffer.destroy();
353
+ } catch (err) {
354
+ console.error(
355
+ `Error destroying HarfBuzz buffer: ${err instanceof Error ? err.message : String(err)}`
356
+ );
357
+ }
358
+ }
359
+ } catch (err) {
360
+ throw new Error(
361
+ `Failed to shape text with font "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
362
+ );
363
+ }
265
364
  }
266
365
  async layout(params) {
267
- const { textTransform, desc, fontSize, letterSpacing, width } = params;
268
- const input = this.transformText(params.text, textTransform);
269
- if (!input || input.length === 0) {
270
- return [];
271
- }
272
- const shaped = await this.shapeFull(input, desc);
273
- const face = await this.fonts.getFace(desc);
274
- const upem = face?.upem || 1e3;
275
- const scale = fontSize / upem;
276
- const glyphs = shaped.map((g) => {
277
- const charIndex = g.cl;
278
- let char;
279
- if (charIndex >= 0 && charIndex < input.length) {
280
- char = input[charIndex];
366
+ try {
367
+ const { textTransform, desc, fontSize, letterSpacing, width } = params;
368
+ const input = this.transformText(params.text, textTransform);
369
+ if (!input || input.length === 0) {
370
+ return [];
281
371
  }
282
- return {
283
- id: g.g,
284
- xAdvance: g.ax * scale + letterSpacing,
285
- xOffset: g.dx * scale,
286
- yOffset: -g.dy * scale,
287
- cluster: g.cl,
288
- char
289
- // This now correctly maps to the original character
290
- };
291
- });
292
- const lines = [];
293
- let currentLine = [];
294
- let currentWidth = 0;
295
- const spaceIndices = /* @__PURE__ */ new Set();
296
- for (let i = 0; i < input.length; i++) {
297
- if (input[i] === " ") {
298
- spaceIndices.add(i);
372
+ let shaped;
373
+ try {
374
+ shaped = await this.shapeFull(input, desc);
375
+ } catch (err) {
376
+ throw new Error(`Text shaping failed: ${err instanceof Error ? err.message : String(err)}`);
299
377
  }
300
- }
301
- let lastBreakIndex = -1;
302
- for (let i = 0; i < glyphs.length; i++) {
303
- const glyph = glyphs[i];
304
- const glyphWidth = glyph.xAdvance;
305
- if (glyph.char === "\n") {
306
- if (currentLine.length > 0) {
307
- lines.push({
308
- glyphs: currentLine,
309
- width: currentWidth,
310
- y: 0
311
- });
378
+ let upem;
379
+ try {
380
+ const face = await this.fonts.getFace(desc);
381
+ upem = face?.upem || 1e3;
382
+ } catch (err) {
383
+ throw new Error(
384
+ `Failed to get font metrics: ${err instanceof Error ? err.message : String(err)}`
385
+ );
386
+ }
387
+ const scale = fontSize / upem;
388
+ const glyphs = shaped.map((g) => {
389
+ const charIndex = g.cl;
390
+ let char;
391
+ if (charIndex >= 0 && charIndex < input.length) {
392
+ char = input[charIndex];
393
+ }
394
+ return {
395
+ id: g.g,
396
+ xAdvance: g.ax * scale + letterSpacing,
397
+ xOffset: g.dx * scale,
398
+ yOffset: -g.dy * scale,
399
+ cluster: g.cl,
400
+ char
401
+ };
402
+ });
403
+ const lines = [];
404
+ let currentLine = [];
405
+ let currentWidth = 0;
406
+ const spaceIndices = /* @__PURE__ */ new Set();
407
+ for (let i = 0; i < input.length; i++) {
408
+ if (input[i] === " ") {
409
+ spaceIndices.add(i);
312
410
  }
313
- currentLine = [];
314
- currentWidth = 0;
315
- lastBreakIndex = i;
316
- continue;
317
411
  }
318
- if (currentWidth + glyphWidth > width && currentLine.length > 0) {
319
- if (lastBreakIndex > -1) {
320
- const breakPoint = lastBreakIndex - (i - currentLine.length) + 1;
321
- const nextLine = currentLine.splice(breakPoint);
322
- const lineWidth = currentLine.reduce((sum, g) => sum + g.xAdvance, 0);
323
- lines.push({
324
- glyphs: currentLine,
325
- width: lineWidth,
326
- y: 0
327
- });
328
- currentLine = nextLine;
329
- currentWidth = nextLine.reduce((sum, g) => sum + g.xAdvance, 0);
330
- } else {
331
- lines.push({
332
- glyphs: currentLine,
333
- width: currentWidth,
334
- y: 0
335
- });
412
+ let lastBreakIndex = -1;
413
+ for (let i = 0; i < glyphs.length; i++) {
414
+ const glyph = glyphs[i];
415
+ const glyphWidth = glyph.xAdvance;
416
+ if (glyph.char === "\n") {
417
+ if (currentLine.length > 0) {
418
+ lines.push({
419
+ glyphs: currentLine,
420
+ width: currentWidth,
421
+ y: 0
422
+ });
423
+ }
336
424
  currentLine = [];
337
425
  currentWidth = 0;
426
+ lastBreakIndex = i;
427
+ continue;
428
+ }
429
+ if (currentWidth + glyphWidth > width && currentLine.length > 0) {
430
+ if (lastBreakIndex > -1) {
431
+ const breakPoint = lastBreakIndex - (i - currentLine.length) + 1;
432
+ const nextLine = currentLine.splice(breakPoint);
433
+ const lineWidth = currentLine.reduce((sum, g) => sum + g.xAdvance, 0);
434
+ lines.push({
435
+ glyphs: currentLine,
436
+ width: lineWidth,
437
+ y: 0
438
+ });
439
+ currentLine = nextLine;
440
+ currentWidth = nextLine.reduce((sum, g) => sum + g.xAdvance, 0);
441
+ } else {
442
+ lines.push({
443
+ glyphs: currentLine,
444
+ width: currentWidth,
445
+ y: 0
446
+ });
447
+ currentLine = [];
448
+ currentWidth = 0;
449
+ }
450
+ lastBreakIndex = -1;
451
+ }
452
+ currentLine.push(glyph);
453
+ currentWidth += glyphWidth;
454
+ if (spaceIndices.has(glyph.cluster)) {
455
+ lastBreakIndex = i;
338
456
  }
339
- lastBreakIndex = -1;
340
457
  }
341
- currentLine.push(glyph);
342
- currentWidth += glyphWidth;
343
- if (spaceIndices.has(glyph.cluster)) {
344
- lastBreakIndex = i;
458
+ if (currentLine.length > 0) {
459
+ lines.push({
460
+ glyphs: currentLine,
461
+ width: currentWidth,
462
+ y: 0
463
+ });
345
464
  }
465
+ const lineHeight = params.lineHeight * fontSize;
466
+ for (let i = 0; i < lines.length; i++) {
467
+ lines[i].y = (i + 1) * lineHeight;
468
+ }
469
+ return lines;
470
+ } catch (err) {
471
+ if (err instanceof Error) {
472
+ throw err;
473
+ }
474
+ throw new Error(`Layout failed: ${String(err)}`);
346
475
  }
347
- if (currentLine.length > 0) {
348
- lines.push({
349
- glyphs: currentLine,
350
- width: currentWidth,
351
- y: 0
352
- });
353
- }
354
- const lineHeight = params.lineHeight * fontSize;
355
- for (let i = 0; i < lines.length; i++) {
356
- lines[i].y = (i + 1) * lineHeight;
357
- }
358
- return lines;
359
476
  }
360
477
  };
361
478
 
@@ -447,7 +564,6 @@ async function buildDrawOps(p) {
447
564
  path,
448
565
  x: glyphX + p.shadow.offsetX,
449
566
  y: glyphY + p.shadow.offsetY,
450
- // @ts-ignore scale propagated to painters
451
567
  scale,
452
568
  fill: { kind: "solid", color: p.shadow.color, opacity: p.shadow.opacity }
453
569
  });
@@ -458,7 +574,6 @@ async function buildDrawOps(p) {
458
574
  path,
459
575
  x: glyphX,
460
576
  y: glyphY,
461
- // @ts-ignore scale propagated to painters
462
577
  scale,
463
578
  width: p.stroke.width,
464
579
  color: p.stroke.color,
@@ -470,7 +585,6 @@ async function buildDrawOps(p) {
470
585
  path,
471
586
  x: glyphX,
472
587
  y: glyphY,
473
- // @ts-ignore scale propagated to painters
474
588
  scale,
475
589
  fill
476
590
  });
@@ -1013,7 +1127,8 @@ function createWebPainter(canvas) {
1013
1127
  for (const op of ops) {
1014
1128
  if (op.op === "BeginFrame") {
1015
1129
  const dpr = op.pixelRatio;
1016
- const w = op.width, h = op.height;
1130
+ const w = op.width;
1131
+ const h = op.height;
1017
1132
  if ("width" in canvas && "height" in canvas) {
1018
1133
  canvas.width = Math.floor(w * dpr);
1019
1134
  canvas.height = Math.floor(h * dpr);
@@ -1036,28 +1151,30 @@ function createWebPainter(canvas) {
1036
1151
  continue;
1037
1152
  }
1038
1153
  if (op.op === "FillPath") {
1039
- const p = new Path2D(op.path);
1154
+ const fillOp = op;
1155
+ const p = new Path2D(fillOp.path);
1040
1156
  ctx.save();
1041
- ctx.translate(op.x, op.y);
1042
- const s = op.scale ?? 1;
1157
+ ctx.translate(fillOp.x, fillOp.y);
1158
+ const s = fillOp.scale ?? 1;
1043
1159
  ctx.scale(s, -s);
1044
- const bbox = op.gradientBBox ?? globalBox;
1045
- const fill = makeGradientFromBBox(ctx, op.fill, bbox);
1160
+ const bbox = fillOp.gradientBBox ?? globalBox;
1161
+ const fill = makeGradientFromBBox(ctx, fillOp.fill, bbox);
1046
1162
  ctx.fillStyle = fill;
1047
1163
  ctx.fill(p);
1048
1164
  ctx.restore();
1049
1165
  continue;
1050
1166
  }
1051
1167
  if (op.op === "StrokePath") {
1052
- const p = new Path2D(op.path);
1168
+ const strokeOp = op;
1169
+ const p = new Path2D(strokeOp.path);
1053
1170
  ctx.save();
1054
- ctx.translate(op.x, op.y);
1055
- const s = op.scale ?? 1;
1171
+ ctx.translate(strokeOp.x, strokeOp.y);
1172
+ const s = strokeOp.scale ?? 1;
1056
1173
  ctx.scale(s, -s);
1057
1174
  const invAbs = 1 / Math.abs(s);
1058
- const c = parseHex6(op.color, op.opacity);
1175
+ const c = parseHex6(strokeOp.color, strokeOp.opacity);
1059
1176
  ctx.strokeStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
1060
- ctx.lineWidth = op.width * invAbs;
1177
+ ctx.lineWidth = strokeOp.width * invAbs;
1061
1178
  ctx.lineJoin = "round";
1062
1179
  ctx.lineCap = "round";
1063
1180
  ctx.stroke(p);
@@ -1097,17 +1214,20 @@ function makeGradientFromBBox(ctx, spec, box) {
1097
1214
  const c = parseHex6(spec.color, spec.opacity);
1098
1215
  return `rgba(${c.r},${c.g},${c.b},${c.a})`;
1099
1216
  }
1100
- const cx = box.x + box.w / 2, cy = box.y + box.h / 2, r = Math.max(box.w, box.h) / 2;
1217
+ const cx = box.x + box.w / 2;
1218
+ const cy = box.y + box.h / 2;
1219
+ const r = Math.max(box.w, box.h) / 2;
1101
1220
  const addStops = (g) => {
1102
- const op = spec.opacity ?? 1;
1103
- for (const s of spec.stops) {
1104
- const c = parseHex6(s.color, op);
1221
+ const opacity = spec.kind === "linear" || spec.kind === "radial" ? spec.opacity : 1;
1222
+ const stops = spec.kind === "linear" || spec.kind === "radial" ? spec.stops : [];
1223
+ for (const s of stops) {
1224
+ const c = parseHex6(s.color, opacity);
1105
1225
  g.addColorStop(s.offset, `rgba(${c.r},${c.g},${c.b},${c.a})`);
1106
1226
  }
1107
1227
  return g;
1108
1228
  };
1109
1229
  if (spec.kind === "linear") {
1110
- const rad = (spec.angle || 0) * Math.PI / 180;
1230
+ const rad = spec.angle * Math.PI / 180;
1111
1231
  const x1 = cx + Math.cos(rad + Math.PI) * r;
1112
1232
  const y1 = cy + Math.sin(rad + Math.PI) * r;
1113
1233
  const x2 = cx + Math.cos(rad) * r;
@@ -1118,27 +1238,42 @@ function makeGradientFromBBox(ctx, spec, box) {
1118
1238
  }
1119
1239
  }
1120
1240
  function computeGlobalTextBounds(ops) {
1121
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
1241
+ let minX = Infinity;
1242
+ let minY = Infinity;
1243
+ let maxX = -Infinity;
1244
+ let maxY = -Infinity;
1122
1245
  for (const op of ops) {
1123
- if (op.op !== "FillPath" || op.isShadow) continue;
1124
- const b = computePathBounds2(op.path);
1125
- const s = op.scale ?? 1;
1126
- const x1 = op.x + s * b.x;
1127
- const x2 = op.x + s * (b.x + b.w);
1128
- const y1 = op.y - s * (b.y + b.h);
1129
- const y2 = op.y - s * b.y;
1246
+ if (op.op !== "FillPath") continue;
1247
+ const fillOp = op;
1248
+ if (fillOp.isShadow) continue;
1249
+ const b = computePathBounds2(fillOp.path);
1250
+ const s = fillOp.scale ?? 1;
1251
+ const x1 = fillOp.x + s * b.x;
1252
+ const x2 = fillOp.x + s * (b.x + b.w);
1253
+ const y1 = fillOp.y - s * (b.y + b.h);
1254
+ const y2 = fillOp.y - s * b.y;
1130
1255
  if (x1 < minX) minX = x1;
1131
1256
  if (y1 < minY) minY = y1;
1132
1257
  if (x2 > maxX) maxX = x2;
1133
1258
  if (y2 > maxY) maxY = y2;
1134
1259
  }
1135
- if (minX === Infinity) return { x: 0, y: 0, w: 1, h: 1 };
1136
- return { x: minX, y: minY, w: Math.max(1, maxX - minX), h: Math.max(1, maxY - minY) };
1260
+ if (minX === Infinity) {
1261
+ return { x: 0, y: 0, w: 1, h: 1 };
1262
+ }
1263
+ return {
1264
+ x: minX,
1265
+ y: minY,
1266
+ w: Math.max(1, maxX - minX),
1267
+ h: Math.max(1, maxY - minY)
1268
+ };
1137
1269
  }
1138
1270
  function computePathBounds2(d) {
1139
1271
  const tokens = tokenizePath2(d);
1140
1272
  let i = 0;
1141
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
1273
+ let minX = Infinity;
1274
+ let minY = Infinity;
1275
+ let maxX = -Infinity;
1276
+ let maxY = -Infinity;
1142
1277
  const touch = (x, y) => {
1143
1278
  if (x < minX) minX = x;
1144
1279
  if (y < minY) minY = y;
@@ -1178,10 +1313,19 @@ function computePathBounds2(d) {
1178
1313
  }
1179
1314
  case "Z":
1180
1315
  break;
1316
+ default:
1317
+ break;
1181
1318
  }
1182
1319
  }
1183
- if (minX === Infinity) return { x: 0, y: 0, w: 0, h: 0 };
1184
- return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
1320
+ if (minX === Infinity) {
1321
+ return { x: 0, y: 0, w: 0, h: 0 };
1322
+ }
1323
+ return {
1324
+ x: minX,
1325
+ y: minY,
1326
+ w: maxX - minX,
1327
+ h: maxY - minY
1328
+ };
1185
1329
  }
1186
1330
  function tokenizePath2(d) {
1187
1331
  return d.match(/[MLCQZ]|-?\d*\.?\d+(?:e[-+]?\d+)?/gi) ?? [];
@@ -1189,11 +1333,37 @@ function tokenizePath2(d) {
1189
1333
 
1190
1334
  // src/io/web.ts
1191
1335
  async function fetchToArrayBuffer(url) {
1192
- const res = await fetch(url);
1193
- if (!res.ok) throw new Error(`Failed to fetch ${url}: ${res.status}`);
1194
- return await res.arrayBuffer();
1336
+ try {
1337
+ const res = await fetch(url);
1338
+ if (!res.ok) {
1339
+ throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
1340
+ }
1341
+ try {
1342
+ return await res.arrayBuffer();
1343
+ } catch (err) {
1344
+ throw new Error(
1345
+ `Failed to read response body as ArrayBuffer from ${url}: ${err instanceof Error ? err.message : String(err)}`
1346
+ );
1347
+ }
1348
+ } catch (err) {
1349
+ if (err instanceof Error) {
1350
+ if (err.message.includes("Failed to fetch") || err.message.includes("Failed to read")) {
1351
+ throw err;
1352
+ }
1353
+ throw new Error(`Failed to fetch ${url}: ${err.message}`);
1354
+ }
1355
+ throw new Error(`Failed to fetch ${url}: ${String(err)}`);
1356
+ }
1195
1357
  }
1196
1358
 
1359
+ // src/types.ts
1360
+ var isShadowFill2 = (op) => {
1361
+ return op.op === "FillPath" && op.isShadow === true;
1362
+ };
1363
+ var isGlyphFill2 = (op) => {
1364
+ return op.op === "FillPath" && op.isShadow !== true;
1365
+ };
1366
+
1197
1367
  // src/env/entry.web.ts
1198
1368
  async function createTextEngine(opts = {}) {
1199
1369
  const width = opts.width ?? CANVAS_CONFIG.DEFAULTS.width;
@@ -1202,109 +1372,206 @@ async function createTextEngine(opts = {}) {
1202
1372
  const wasmBaseURL = opts.wasmBaseURL;
1203
1373
  const fonts = new FontRegistry(wasmBaseURL);
1204
1374
  const layout = new LayoutEngine(fonts);
1205
- await fonts.init();
1375
+ try {
1376
+ await fonts.init();
1377
+ } catch (err) {
1378
+ throw new Error(
1379
+ `Failed to initialize font registry: ${err instanceof Error ? err.message : String(err)}`
1380
+ );
1381
+ }
1206
1382
  async function ensureFonts(asset) {
1207
- if (asset.customFonts) {
1208
- for (const cf of asset.customFonts) {
1209
- const bytes = await fetchToArrayBuffer(cf.src);
1210
- await fonts.registerFromBytes(bytes, {
1211
- family: cf.family,
1212
- weight: cf.weight ?? "400",
1213
- style: cf.style ?? "normal"
1214
- });
1383
+ try {
1384
+ if (asset.customFonts) {
1385
+ for (const cf of asset.customFonts) {
1386
+ try {
1387
+ const bytes = await fetchToArrayBuffer(cf.src);
1388
+ await fonts.registerFromBytes(bytes, {
1389
+ family: cf.family,
1390
+ weight: cf.weight ?? "400",
1391
+ style: cf.style ?? "normal"
1392
+ });
1393
+ } catch (err) {
1394
+ throw new Error(
1395
+ `Failed to load custom font "${cf.family}" from ${cf.src}: ${err instanceof Error ? err.message : String(err)}`
1396
+ );
1397
+ }
1398
+ }
1399
+ }
1400
+ const main = asset.font ?? {
1401
+ family: "Roboto",
1402
+ weight: "400",
1403
+ style: "normal",
1404
+ size: 48,
1405
+ color: "#000000",
1406
+ opacity: 1
1407
+ };
1408
+ return main;
1409
+ } catch (err) {
1410
+ if (err instanceof Error) {
1411
+ throw err;
1215
1412
  }
1413
+ throw new Error(`Failed to ensure fonts: ${String(err)}`);
1216
1414
  }
1217
- const main = asset.font ?? {
1218
- family: "Roboto",
1219
- weight: "400",
1220
- style: "normal",
1221
- size: 48,
1222
- color: "#000000",
1223
- opacity: 1
1224
- };
1225
- return main;
1226
1415
  }
1227
1416
  return {
1228
1417
  validate(input) {
1229
- const { value, error } = RichTextAssetSchema.validate(input, {
1230
- abortEarly: false,
1231
- convert: true
1232
- });
1233
- if (error) throw error;
1234
- return { value };
1418
+ try {
1419
+ const { value, error } = RichTextAssetSchema.validate(input, {
1420
+ abortEarly: false,
1421
+ convert: true
1422
+ });
1423
+ if (error) throw error;
1424
+ return { value };
1425
+ } catch (err) {
1426
+ if (err instanceof Error) {
1427
+ throw new Error(`Validation failed: ${err.message}`);
1428
+ }
1429
+ throw new Error(`Validation failed: ${String(err)}`);
1430
+ }
1235
1431
  },
1236
1432
  async registerFontFromUrl(url, desc) {
1237
- const bytes = await fetchToArrayBuffer(url);
1238
- await fonts.registerFromBytes(bytes, desc);
1433
+ try {
1434
+ const bytes = await fetchToArrayBuffer(url);
1435
+ await fonts.registerFromBytes(bytes, desc);
1436
+ } catch (err) {
1437
+ throw new Error(
1438
+ `Failed to register font "${desc.family}" from URL ${url}: ${err instanceof Error ? err.message : String(err)}`
1439
+ );
1440
+ }
1239
1441
  },
1240
1442
  async registerFontFromFile(source, desc) {
1241
- let bytes;
1242
- if (typeof source === "string") {
1243
- bytes = await fetchToArrayBuffer(source);
1244
- } else {
1245
- bytes = await source.arrayBuffer();
1443
+ try {
1444
+ let bytes;
1445
+ if (typeof source === "string") {
1446
+ try {
1447
+ bytes = await fetchToArrayBuffer(source);
1448
+ } catch (err) {
1449
+ throw new Error(
1450
+ `Failed to fetch font from ${source}: ${err instanceof Error ? err.message : String(err)}`
1451
+ );
1452
+ }
1453
+ } else {
1454
+ try {
1455
+ bytes = await source.arrayBuffer();
1456
+ } catch (err) {
1457
+ throw new Error(
1458
+ `Failed to read Blob as ArrayBuffer: ${err instanceof Error ? err.message : String(err)}`
1459
+ );
1460
+ }
1461
+ }
1462
+ await fonts.registerFromBytes(bytes, desc);
1463
+ } catch (err) {
1464
+ if (err instanceof Error) {
1465
+ throw err;
1466
+ }
1467
+ throw new Error(
1468
+ `Failed to register font "${desc.family}" from ${typeof source === "string" ? source : "Blob"}: ${String(err)}`
1469
+ );
1246
1470
  }
1247
- await fonts.registerFromBytes(bytes, desc);
1248
1471
  },
1249
1472
  async renderFrame(asset, tSeconds) {
1250
- const main = await ensureFonts(asset);
1251
- const desc = { family: main.family, weight: `${main.weight}`, style: main.style };
1252
- const lines = await layout.layout({
1253
- text: asset.text,
1254
- width: asset.width ?? width,
1255
- letterSpacing: asset.style?.letterSpacing ?? 0,
1256
- fontSize: main.size,
1257
- lineHeight: asset.style?.lineHeight ?? 1.2,
1258
- desc,
1259
- textTransform: asset.style?.textTransform ?? "none"
1260
- });
1261
- const textRect = { x: 0, y: 0, width: asset.width ?? width, height: asset.height ?? height };
1262
- const ops0 = await buildDrawOps({
1263
- canvas: { width, height, pixelRatio },
1264
- textRect,
1265
- lines,
1266
- font: {
1267
- family: main.family,
1268
- size: main.size,
1269
- weight: `${main.weight}`,
1270
- style: main.style,
1271
- color: main.color,
1272
- opacity: main.opacity
1273
- },
1274
- style: {
1275
- lineHeight: asset.style?.lineHeight ?? 1.2,
1276
- textDecoration: asset.style?.textDecoration ?? "none",
1277
- gradient: asset.style?.gradient
1278
- },
1279
- stroke: asset.stroke,
1280
- shadow: asset.shadow,
1281
- align: asset.align ?? { horizontal: "left", vertical: "middle" },
1282
- background: asset.background,
1283
- glyphPathProvider: (gid) => fonts.glyphPath(desc, gid),
1284
- getUnitsPerEm: () => fonts.getUnitsPerEm(desc)
1285
- });
1286
- const ops = applyAnimation(ops0, lines, {
1287
- t: tSeconds,
1288
- fontSize: main.size,
1289
- anim: asset.animation ? {
1290
- preset: asset.animation.preset,
1291
- speed: asset.animation.speed,
1292
- duration: asset.animation.duration,
1293
- style: asset.animation.style,
1294
- direction: asset.animation.direction
1295
- } : void 0
1296
- });
1297
- return ops;
1473
+ try {
1474
+ const main = await ensureFonts(asset);
1475
+ const desc = { family: main.family, weight: `${main.weight}`, style: main.style };
1476
+ let lines;
1477
+ try {
1478
+ lines = await layout.layout({
1479
+ text: asset.text,
1480
+ width: asset.width ?? width,
1481
+ letterSpacing: asset.style?.letterSpacing ?? 0,
1482
+ fontSize: main.size,
1483
+ lineHeight: asset.style?.lineHeight ?? 1.2,
1484
+ desc,
1485
+ textTransform: asset.style?.textTransform ?? "none"
1486
+ });
1487
+ } catch (err) {
1488
+ throw new Error(
1489
+ `Failed to layout text: ${err instanceof Error ? err.message : String(err)}`
1490
+ );
1491
+ }
1492
+ const textRect = {
1493
+ x: 0,
1494
+ y: 0,
1495
+ width: asset.width ?? width,
1496
+ height: asset.height ?? height
1497
+ };
1498
+ let ops0;
1499
+ try {
1500
+ ops0 = await buildDrawOps({
1501
+ canvas: { width, height, pixelRatio },
1502
+ textRect,
1503
+ lines,
1504
+ font: {
1505
+ family: main.family,
1506
+ size: main.size,
1507
+ weight: `${main.weight}`,
1508
+ style: main.style,
1509
+ color: main.color,
1510
+ opacity: main.opacity
1511
+ },
1512
+ style: {
1513
+ lineHeight: asset.style?.lineHeight ?? 1.2,
1514
+ textDecoration: asset.style?.textDecoration ?? "none",
1515
+ gradient: asset.style?.gradient
1516
+ },
1517
+ stroke: asset.stroke,
1518
+ shadow: asset.shadow,
1519
+ align: asset.align ?? { horizontal: "left", vertical: "middle" },
1520
+ background: asset.background,
1521
+ glyphPathProvider: (gid) => fonts.glyphPath(desc, gid),
1522
+ getUnitsPerEm: () => fonts.getUnitsPerEm(desc)
1523
+ });
1524
+ } catch (err) {
1525
+ throw new Error(
1526
+ `Failed to build draw operations: ${err instanceof Error ? err.message : String(err)}`
1527
+ );
1528
+ }
1529
+ try {
1530
+ const ops = applyAnimation(ops0, lines, {
1531
+ t: tSeconds,
1532
+ fontSize: main.size,
1533
+ anim: asset.animation ? {
1534
+ preset: asset.animation.preset,
1535
+ speed: asset.animation.speed,
1536
+ duration: asset.animation.duration,
1537
+ style: asset.animation.style,
1538
+ direction: asset.animation.direction
1539
+ } : void 0
1540
+ });
1541
+ return ops;
1542
+ } catch (err) {
1543
+ throw new Error(
1544
+ `Failed to apply animation: ${err instanceof Error ? err.message : String(err)}`
1545
+ );
1546
+ }
1547
+ } catch (err) {
1548
+ if (err instanceof Error) {
1549
+ throw err;
1550
+ }
1551
+ throw new Error(`Failed to render frame at time ${tSeconds}s: ${String(err)}`);
1552
+ }
1298
1553
  },
1299
1554
  createRenderer(canvas) {
1300
- return createWebPainter(canvas);
1555
+ try {
1556
+ return createWebPainter(canvas);
1557
+ } catch (err) {
1558
+ throw new Error(
1559
+ `Failed to create renderer: ${err instanceof Error ? err.message : String(err)}`
1560
+ );
1561
+ }
1301
1562
  },
1302
1563
  destroy() {
1303
- fonts.destroy();
1564
+ try {
1565
+ fonts.destroy();
1566
+ } catch (err) {
1567
+ console.error(`Error during cleanup: ${err instanceof Error ? err.message : String(err)}`);
1568
+ }
1304
1569
  }
1305
1570
  };
1306
1571
  }
1307
1572
  export {
1308
- createTextEngine
1573
+ createTextEngine,
1574
+ isGlyphFill2 as isGlyphFill,
1575
+ isShadowFill2 as isShadowFill
1309
1576
  };
1310
1577
  //# sourceMappingURL=entry.web.js.map