@shotstack/shotstack-canvas 1.1.0 → 1.1.2
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 +595 -287
- package/dist/entry.node.cjs.map +1 -1
- package/dist/entry.node.d.cts +20 -1
- package/dist/entry.node.d.ts +20 -1
- package/dist/entry.node.js +592 -286
- package/dist/entry.node.js.map +1 -1
- package/dist/entry.web.d.ts +20 -1
- package/dist/entry.web.js +597 -262
- package/dist/entry.web.js.map +1 -1
- package/package.json +10 -4
package/dist/entry.web.js
CHANGED
|
@@ -132,23 +132,32 @@ var RichTextAssetSchema = Joi.object({
|
|
|
132
132
|
var hbSingleton = null;
|
|
133
133
|
async function initHB(wasmBaseURL) {
|
|
134
134
|
if (hbSingleton) return hbSingleton;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
|
151
|
-
var
|
|
160
|
+
var _FontRegistry = class _FontRegistry {
|
|
152
161
|
constructor(wasmBaseURL) {
|
|
153
162
|
__publicField(this, "hb");
|
|
154
163
|
__publicField(this, "faces", /* @__PURE__ */ new Map());
|
|
@@ -158,16 +167,23 @@ var FontRegistry = class {
|
|
|
158
167
|
__publicField(this, "initPromise");
|
|
159
168
|
this.wasmBaseURL = wasmBaseURL;
|
|
160
169
|
}
|
|
170
|
+
static setFallbackLoader(loader) {
|
|
171
|
+
_FontRegistry.fallbackLoader = loader;
|
|
172
|
+
}
|
|
161
173
|
async init() {
|
|
162
174
|
if (this.initPromise) {
|
|
163
175
|
await this.initPromise;
|
|
164
176
|
return;
|
|
165
177
|
}
|
|
166
|
-
if (this.hb)
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
178
|
+
if (this.hb) return;
|
|
169
179
|
this.initPromise = this._doInit();
|
|
170
|
-
|
|
180
|
+
try {
|
|
181
|
+
await this.initPromise;
|
|
182
|
+
} catch (err) {
|
|
183
|
+
throw new Error(
|
|
184
|
+
`Failed to initialize FontRegistry: ${err instanceof Error ? err.message : String(err)}`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
171
187
|
}
|
|
172
188
|
async _doInit() {
|
|
173
189
|
try {
|
|
@@ -179,7 +195,13 @@ var FontRegistry = class {
|
|
|
179
195
|
}
|
|
180
196
|
async getHB() {
|
|
181
197
|
if (!this.hb) {
|
|
182
|
-
|
|
198
|
+
try {
|
|
199
|
+
await this.init();
|
|
200
|
+
} catch (err) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
`Failed to get HarfBuzz instance: ${err instanceof Error ? err.message : String(err)}`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
183
205
|
}
|
|
184
206
|
return this.hb;
|
|
185
207
|
}
|
|
@@ -187,50 +209,144 @@ var FontRegistry = class {
|
|
|
187
209
|
return `${desc.family}__${desc.weight ?? "400"}__${desc.style ?? "normal"}`;
|
|
188
210
|
}
|
|
189
211
|
async registerFromBytes(bytes, desc) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
212
|
+
try {
|
|
213
|
+
if (!this.hb) await this.init();
|
|
214
|
+
const k = this.key(desc);
|
|
215
|
+
if (this.fonts.has(k)) return;
|
|
216
|
+
const blob = this.hb.createBlob(bytes);
|
|
217
|
+
const face = this.hb.createFace(blob, 0);
|
|
218
|
+
const font = this.hb.createFont(face);
|
|
219
|
+
const upem = face.upem || 1e3;
|
|
220
|
+
font.setScale(upem, upem);
|
|
221
|
+
this.blobs.set(k, blob);
|
|
222
|
+
this.faces.set(k, face);
|
|
223
|
+
this.fonts.set(k, font);
|
|
224
|
+
} catch (err) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
`Failed to register font "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
227
|
+
);
|
|
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
|
+
}
|
|
201
249
|
}
|
|
202
250
|
async getFont(desc) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
251
|
+
try {
|
|
252
|
+
if (!this.hb) await this.init();
|
|
253
|
+
const k = this.key(desc);
|
|
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
|
+
}
|
|
259
|
+
if (!f) throw new Error(`Font not registered for ${k}`);
|
|
260
|
+
return f;
|
|
261
|
+
} catch (err) {
|
|
262
|
+
if (err instanceof Error && err.message.includes("Font not registered")) {
|
|
263
|
+
throw err;
|
|
264
|
+
}
|
|
265
|
+
throw new Error(
|
|
266
|
+
`Failed to get font "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
267
|
+
);
|
|
268
|
+
}
|
|
208
269
|
}
|
|
209
270
|
async getFace(desc) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
271
|
+
try {
|
|
272
|
+
if (!this.hb) await this.init();
|
|
273
|
+
const k = this.key(desc);
|
|
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;
|
|
280
|
+
} catch (err) {
|
|
281
|
+
throw new Error(
|
|
282
|
+
`Failed to get face for "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
283
|
+
);
|
|
284
|
+
}
|
|
213
285
|
}
|
|
214
286
|
async getUnitsPerEm(desc) {
|
|
215
|
-
|
|
216
|
-
|
|
287
|
+
try {
|
|
288
|
+
const face = await this.getFace(desc);
|
|
289
|
+
return face?.upem || 1e3;
|
|
290
|
+
} catch (err) {
|
|
291
|
+
throw new Error(
|
|
292
|
+
`Failed to get units per em for "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
293
|
+
);
|
|
294
|
+
}
|
|
217
295
|
}
|
|
218
296
|
async glyphPath(desc, glyphId) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
297
|
+
try {
|
|
298
|
+
const font = await this.getFont(desc);
|
|
299
|
+
const path = font.glyphToPath(glyphId);
|
|
300
|
+
return path && path !== "" ? path : "M 0 0";
|
|
301
|
+
} catch (err) {
|
|
302
|
+
throw new Error(
|
|
303
|
+
`Failed to get glyph path for glyph ${glyphId} in font "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
304
|
+
);
|
|
305
|
+
}
|
|
222
306
|
}
|
|
223
307
|
destroy() {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
308
|
+
try {
|
|
309
|
+
for (const [, f] of this.fonts) {
|
|
310
|
+
try {
|
|
311
|
+
f.destroy();
|
|
312
|
+
} catch (err) {
|
|
313
|
+
console.error(
|
|
314
|
+
`Error destroying font: ${err instanceof Error ? err.message : String(err)}`
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
for (const [, f] of this.faces) {
|
|
319
|
+
try {
|
|
320
|
+
f.destroy();
|
|
321
|
+
} catch (err) {
|
|
322
|
+
console.error(
|
|
323
|
+
`Error destroying face: ${err instanceof Error ? err.message : String(err)}`
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
for (const [, b] of this.blobs) {
|
|
328
|
+
try {
|
|
329
|
+
b.destroy();
|
|
330
|
+
} catch (err) {
|
|
331
|
+
console.error(
|
|
332
|
+
`Error destroying blob: ${err instanceof Error ? err.message : String(err)}`
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
this.fonts.clear();
|
|
337
|
+
this.faces.clear();
|
|
338
|
+
this.blobs.clear();
|
|
339
|
+
this.hb = void 0;
|
|
340
|
+
this.initPromise = void 0;
|
|
341
|
+
} catch (err) {
|
|
342
|
+
console.error(
|
|
343
|
+
`Error during FontRegistry cleanup: ${err instanceof Error ? err.message : String(err)}`
|
|
344
|
+
);
|
|
345
|
+
}
|
|
232
346
|
}
|
|
233
347
|
};
|
|
348
|
+
__publicField(_FontRegistry, "fallbackLoader");
|
|
349
|
+
var FontRegistry = _FontRegistry;
|
|
234
350
|
|
|
235
351
|
// src/core/layout.ts
|
|
236
352
|
var LayoutEngine = class {
|
|
@@ -250,112 +366,145 @@ var LayoutEngine = class {
|
|
|
250
366
|
}
|
|
251
367
|
}
|
|
252
368
|
async shapeFull(text, desc) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
369
|
+
try {
|
|
370
|
+
const hb = await this.fonts.getHB();
|
|
371
|
+
const buffer = hb.createBuffer();
|
|
372
|
+
try {
|
|
373
|
+
buffer.addText(text);
|
|
374
|
+
buffer.guessSegmentProperties();
|
|
375
|
+
const font = await this.fonts.getFont(desc);
|
|
376
|
+
const face = await this.fonts.getFace(desc);
|
|
377
|
+
const upem = face?.upem || 1e3;
|
|
378
|
+
font.setScale(upem, upem);
|
|
379
|
+
hb.shape(font, buffer);
|
|
380
|
+
const result = buffer.json();
|
|
381
|
+
return result;
|
|
382
|
+
} finally {
|
|
383
|
+
try {
|
|
384
|
+
buffer.destroy();
|
|
385
|
+
} catch (err) {
|
|
386
|
+
console.error(
|
|
387
|
+
`Error destroying HarfBuzz buffer: ${err instanceof Error ? err.message : String(err)}`
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
} catch (err) {
|
|
392
|
+
throw new Error(
|
|
393
|
+
`Failed to shape text with font "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
394
|
+
);
|
|
395
|
+
}
|
|
265
396
|
}
|
|
266
397
|
async layout(params) {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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];
|
|
398
|
+
try {
|
|
399
|
+
const { textTransform, desc, fontSize, letterSpacing, width } = params;
|
|
400
|
+
const input = this.transformText(params.text, textTransform);
|
|
401
|
+
if (!input || input.length === 0) {
|
|
402
|
+
return [];
|
|
281
403
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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);
|
|
404
|
+
let shaped;
|
|
405
|
+
try {
|
|
406
|
+
shaped = await this.shapeFull(input, desc);
|
|
407
|
+
} catch (err) {
|
|
408
|
+
throw new Error(`Text shaping failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
299
409
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
410
|
+
let upem;
|
|
411
|
+
try {
|
|
412
|
+
const face = await this.fonts.getFace(desc);
|
|
413
|
+
upem = face?.upem || 1e3;
|
|
414
|
+
} catch (err) {
|
|
415
|
+
throw new Error(
|
|
416
|
+
`Failed to get font metrics: ${err instanceof Error ? err.message : String(err)}`
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
const scale = fontSize / upem;
|
|
420
|
+
const glyphs = shaped.map((g) => {
|
|
421
|
+
const charIndex = g.cl;
|
|
422
|
+
let char;
|
|
423
|
+
if (charIndex >= 0 && charIndex < input.length) {
|
|
424
|
+
char = input[charIndex];
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
id: g.g,
|
|
428
|
+
xAdvance: g.ax * scale + letterSpacing,
|
|
429
|
+
xOffset: g.dx * scale,
|
|
430
|
+
yOffset: -g.dy * scale,
|
|
431
|
+
cluster: g.cl,
|
|
432
|
+
char
|
|
433
|
+
};
|
|
434
|
+
});
|
|
435
|
+
const lines = [];
|
|
436
|
+
let currentLine = [];
|
|
437
|
+
let currentWidth = 0;
|
|
438
|
+
const spaceIndices = /* @__PURE__ */ new Set();
|
|
439
|
+
for (let i = 0; i < input.length; i++) {
|
|
440
|
+
if (input[i] === " ") {
|
|
441
|
+
spaceIndices.add(i);
|
|
312
442
|
}
|
|
313
|
-
currentLine = [];
|
|
314
|
-
currentWidth = 0;
|
|
315
|
-
lastBreakIndex = i;
|
|
316
|
-
continue;
|
|
317
443
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
} else {
|
|
331
|
-
lines.push({
|
|
332
|
-
glyphs: currentLine,
|
|
333
|
-
width: currentWidth,
|
|
334
|
-
y: 0
|
|
335
|
-
});
|
|
444
|
+
let lastBreakIndex = -1;
|
|
445
|
+
for (let i = 0; i < glyphs.length; i++) {
|
|
446
|
+
const glyph = glyphs[i];
|
|
447
|
+
const glyphWidth = glyph.xAdvance;
|
|
448
|
+
if (glyph.char === "\n") {
|
|
449
|
+
if (currentLine.length > 0) {
|
|
450
|
+
lines.push({
|
|
451
|
+
glyphs: currentLine,
|
|
452
|
+
width: currentWidth,
|
|
453
|
+
y: 0
|
|
454
|
+
});
|
|
455
|
+
}
|
|
336
456
|
currentLine = [];
|
|
337
457
|
currentWidth = 0;
|
|
458
|
+
lastBreakIndex = i;
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (currentWidth + glyphWidth > width && currentLine.length > 0) {
|
|
462
|
+
if (lastBreakIndex > -1) {
|
|
463
|
+
const breakPoint = lastBreakIndex - (i - currentLine.length) + 1;
|
|
464
|
+
const nextLine = currentLine.splice(breakPoint);
|
|
465
|
+
const lineWidth = currentLine.reduce((sum, g) => sum + g.xAdvance, 0);
|
|
466
|
+
lines.push({
|
|
467
|
+
glyphs: currentLine,
|
|
468
|
+
width: lineWidth,
|
|
469
|
+
y: 0
|
|
470
|
+
});
|
|
471
|
+
currentLine = nextLine;
|
|
472
|
+
currentWidth = nextLine.reduce((sum, g) => sum + g.xAdvance, 0);
|
|
473
|
+
} else {
|
|
474
|
+
lines.push({
|
|
475
|
+
glyphs: currentLine,
|
|
476
|
+
width: currentWidth,
|
|
477
|
+
y: 0
|
|
478
|
+
});
|
|
479
|
+
currentLine = [];
|
|
480
|
+
currentWidth = 0;
|
|
481
|
+
}
|
|
482
|
+
lastBreakIndex = -1;
|
|
483
|
+
}
|
|
484
|
+
currentLine.push(glyph);
|
|
485
|
+
currentWidth += glyphWidth;
|
|
486
|
+
if (spaceIndices.has(glyph.cluster)) {
|
|
487
|
+
lastBreakIndex = i;
|
|
338
488
|
}
|
|
339
|
-
lastBreakIndex = -1;
|
|
340
489
|
}
|
|
341
|
-
currentLine.
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
490
|
+
if (currentLine.length > 0) {
|
|
491
|
+
lines.push({
|
|
492
|
+
glyphs: currentLine,
|
|
493
|
+
width: currentWidth,
|
|
494
|
+
y: 0
|
|
495
|
+
});
|
|
345
496
|
}
|
|
497
|
+
const lineHeight = params.lineHeight * fontSize;
|
|
498
|
+
for (let i = 0; i < lines.length; i++) {
|
|
499
|
+
lines[i].y = (i + 1) * lineHeight;
|
|
500
|
+
}
|
|
501
|
+
return lines;
|
|
502
|
+
} catch (err) {
|
|
503
|
+
if (err instanceof Error) {
|
|
504
|
+
throw err;
|
|
505
|
+
}
|
|
506
|
+
throw new Error(`Layout failed: ${String(err)}`);
|
|
346
507
|
}
|
|
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
508
|
}
|
|
360
509
|
};
|
|
361
510
|
|
|
@@ -447,7 +596,6 @@ async function buildDrawOps(p) {
|
|
|
447
596
|
path,
|
|
448
597
|
x: glyphX + p.shadow.offsetX,
|
|
449
598
|
y: glyphY + p.shadow.offsetY,
|
|
450
|
-
// @ts-ignore scale propagated to painters
|
|
451
599
|
scale,
|
|
452
600
|
fill: { kind: "solid", color: p.shadow.color, opacity: p.shadow.opacity }
|
|
453
601
|
});
|
|
@@ -458,7 +606,6 @@ async function buildDrawOps(p) {
|
|
|
458
606
|
path,
|
|
459
607
|
x: glyphX,
|
|
460
608
|
y: glyphY,
|
|
461
|
-
// @ts-ignore scale propagated to painters
|
|
462
609
|
scale,
|
|
463
610
|
width: p.stroke.width,
|
|
464
611
|
color: p.stroke.color,
|
|
@@ -470,7 +617,6 @@ async function buildDrawOps(p) {
|
|
|
470
617
|
path,
|
|
471
618
|
x: glyphX,
|
|
472
619
|
y: glyphY,
|
|
473
|
-
// @ts-ignore scale propagated to painters
|
|
474
620
|
scale,
|
|
475
621
|
fill
|
|
476
622
|
});
|
|
@@ -1013,11 +1159,16 @@ function createWebPainter(canvas) {
|
|
|
1013
1159
|
for (const op of ops) {
|
|
1014
1160
|
if (op.op === "BeginFrame") {
|
|
1015
1161
|
const dpr = op.pixelRatio;
|
|
1016
|
-
const w = op.width
|
|
1162
|
+
const w = op.width;
|
|
1163
|
+
const h = op.height;
|
|
1017
1164
|
if ("width" in canvas && "height" in canvas) {
|
|
1018
1165
|
canvas.width = Math.floor(w * dpr);
|
|
1019
1166
|
canvas.height = Math.floor(h * dpr);
|
|
1020
1167
|
}
|
|
1168
|
+
if ("style" in canvas) {
|
|
1169
|
+
canvas.style.width = `${w}px`;
|
|
1170
|
+
canvas.style.height = `${h}px`;
|
|
1171
|
+
}
|
|
1021
1172
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
1022
1173
|
if (op.clear) ctx.clearRect(0, 0, w, h);
|
|
1023
1174
|
if (op.bg) {
|
|
@@ -1036,28 +1187,30 @@ function createWebPainter(canvas) {
|
|
|
1036
1187
|
continue;
|
|
1037
1188
|
}
|
|
1038
1189
|
if (op.op === "FillPath") {
|
|
1039
|
-
const
|
|
1190
|
+
const fillOp = op;
|
|
1191
|
+
const p = new Path2D(fillOp.path);
|
|
1040
1192
|
ctx.save();
|
|
1041
|
-
ctx.translate(
|
|
1042
|
-
const s =
|
|
1193
|
+
ctx.translate(fillOp.x, fillOp.y);
|
|
1194
|
+
const s = fillOp.scale ?? 1;
|
|
1043
1195
|
ctx.scale(s, -s);
|
|
1044
|
-
const bbox =
|
|
1045
|
-
const fill = makeGradientFromBBox(ctx,
|
|
1196
|
+
const bbox = fillOp.gradientBBox ?? globalBox;
|
|
1197
|
+
const fill = makeGradientFromBBox(ctx, fillOp.fill, bbox);
|
|
1046
1198
|
ctx.fillStyle = fill;
|
|
1047
1199
|
ctx.fill(p);
|
|
1048
1200
|
ctx.restore();
|
|
1049
1201
|
continue;
|
|
1050
1202
|
}
|
|
1051
1203
|
if (op.op === "StrokePath") {
|
|
1052
|
-
const
|
|
1204
|
+
const strokeOp = op;
|
|
1205
|
+
const p = new Path2D(strokeOp.path);
|
|
1053
1206
|
ctx.save();
|
|
1054
|
-
ctx.translate(
|
|
1055
|
-
const s =
|
|
1207
|
+
ctx.translate(strokeOp.x, strokeOp.y);
|
|
1208
|
+
const s = strokeOp.scale ?? 1;
|
|
1056
1209
|
ctx.scale(s, -s);
|
|
1057
1210
|
const invAbs = 1 / Math.abs(s);
|
|
1058
|
-
const c = parseHex6(
|
|
1211
|
+
const c = parseHex6(strokeOp.color, strokeOp.opacity);
|
|
1059
1212
|
ctx.strokeStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
1060
|
-
ctx.lineWidth =
|
|
1213
|
+
ctx.lineWidth = strokeOp.width * invAbs;
|
|
1061
1214
|
ctx.lineJoin = "round";
|
|
1062
1215
|
ctx.lineCap = "round";
|
|
1063
1216
|
ctx.stroke(p);
|
|
@@ -1097,17 +1250,20 @@ function makeGradientFromBBox(ctx, spec, box) {
|
|
|
1097
1250
|
const c = parseHex6(spec.color, spec.opacity);
|
|
1098
1251
|
return `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
1099
1252
|
}
|
|
1100
|
-
const cx = box.x + box.w / 2
|
|
1253
|
+
const cx = box.x + box.w / 2;
|
|
1254
|
+
const cy = box.y + box.h / 2;
|
|
1255
|
+
const r = Math.max(box.w, box.h) / 2;
|
|
1101
1256
|
const addStops = (g) => {
|
|
1102
|
-
const
|
|
1103
|
-
|
|
1104
|
-
|
|
1257
|
+
const opacity = spec.kind === "linear" || spec.kind === "radial" ? spec.opacity : 1;
|
|
1258
|
+
const stops = spec.kind === "linear" || spec.kind === "radial" ? spec.stops : [];
|
|
1259
|
+
for (const s of stops) {
|
|
1260
|
+
const c = parseHex6(s.color, opacity);
|
|
1105
1261
|
g.addColorStop(s.offset, `rgba(${c.r},${c.g},${c.b},${c.a})`);
|
|
1106
1262
|
}
|
|
1107
1263
|
return g;
|
|
1108
1264
|
};
|
|
1109
1265
|
if (spec.kind === "linear") {
|
|
1110
|
-
const rad =
|
|
1266
|
+
const rad = spec.angle * Math.PI / 180;
|
|
1111
1267
|
const x1 = cx + Math.cos(rad + Math.PI) * r;
|
|
1112
1268
|
const y1 = cy + Math.sin(rad + Math.PI) * r;
|
|
1113
1269
|
const x2 = cx + Math.cos(rad) * r;
|
|
@@ -1118,27 +1274,42 @@ function makeGradientFromBBox(ctx, spec, box) {
|
|
|
1118
1274
|
}
|
|
1119
1275
|
}
|
|
1120
1276
|
function computeGlobalTextBounds(ops) {
|
|
1121
|
-
let minX = Infinity
|
|
1277
|
+
let minX = Infinity;
|
|
1278
|
+
let minY = Infinity;
|
|
1279
|
+
let maxX = -Infinity;
|
|
1280
|
+
let maxY = -Infinity;
|
|
1122
1281
|
for (const op of ops) {
|
|
1123
|
-
if (op.op !== "FillPath"
|
|
1124
|
-
const
|
|
1125
|
-
|
|
1126
|
-
const
|
|
1127
|
-
const
|
|
1128
|
-
const
|
|
1129
|
-
const
|
|
1282
|
+
if (op.op !== "FillPath") continue;
|
|
1283
|
+
const fillOp = op;
|
|
1284
|
+
if (fillOp.isShadow) continue;
|
|
1285
|
+
const b = computePathBounds2(fillOp.path);
|
|
1286
|
+
const s = fillOp.scale ?? 1;
|
|
1287
|
+
const x1 = fillOp.x + s * b.x;
|
|
1288
|
+
const x2 = fillOp.x + s * (b.x + b.w);
|
|
1289
|
+
const y1 = fillOp.y - s * (b.y + b.h);
|
|
1290
|
+
const y2 = fillOp.y - s * b.y;
|
|
1130
1291
|
if (x1 < minX) minX = x1;
|
|
1131
1292
|
if (y1 < minY) minY = y1;
|
|
1132
1293
|
if (x2 > maxX) maxX = x2;
|
|
1133
1294
|
if (y2 > maxY) maxY = y2;
|
|
1134
1295
|
}
|
|
1135
|
-
if (minX === Infinity)
|
|
1136
|
-
|
|
1296
|
+
if (minX === Infinity) {
|
|
1297
|
+
return { x: 0, y: 0, w: 1, h: 1 };
|
|
1298
|
+
}
|
|
1299
|
+
return {
|
|
1300
|
+
x: minX,
|
|
1301
|
+
y: minY,
|
|
1302
|
+
w: Math.max(1, maxX - minX),
|
|
1303
|
+
h: Math.max(1, maxY - minY)
|
|
1304
|
+
};
|
|
1137
1305
|
}
|
|
1138
1306
|
function computePathBounds2(d) {
|
|
1139
1307
|
const tokens = tokenizePath2(d);
|
|
1140
1308
|
let i = 0;
|
|
1141
|
-
let minX = Infinity
|
|
1309
|
+
let minX = Infinity;
|
|
1310
|
+
let minY = Infinity;
|
|
1311
|
+
let maxX = -Infinity;
|
|
1312
|
+
let maxY = -Infinity;
|
|
1142
1313
|
const touch = (x, y) => {
|
|
1143
1314
|
if (x < minX) minX = x;
|
|
1144
1315
|
if (y < minY) minY = y;
|
|
@@ -1178,10 +1349,19 @@ function computePathBounds2(d) {
|
|
|
1178
1349
|
}
|
|
1179
1350
|
case "Z":
|
|
1180
1351
|
break;
|
|
1352
|
+
default:
|
|
1353
|
+
break;
|
|
1181
1354
|
}
|
|
1182
1355
|
}
|
|
1183
|
-
if (minX === Infinity)
|
|
1184
|
-
|
|
1356
|
+
if (minX === Infinity) {
|
|
1357
|
+
return { x: 0, y: 0, w: 0, h: 0 };
|
|
1358
|
+
}
|
|
1359
|
+
return {
|
|
1360
|
+
x: minX,
|
|
1361
|
+
y: minY,
|
|
1362
|
+
w: maxX - minX,
|
|
1363
|
+
h: maxY - minY
|
|
1364
|
+
};
|
|
1185
1365
|
}
|
|
1186
1366
|
function tokenizePath2(d) {
|
|
1187
1367
|
return d.match(/[MLCQZ]|-?\d*\.?\d+(?:e[-+]?\d+)?/gi) ?? [];
|
|
@@ -1189,12 +1369,39 @@ function tokenizePath2(d) {
|
|
|
1189
1369
|
|
|
1190
1370
|
// src/io/web.ts
|
|
1191
1371
|
async function fetchToArrayBuffer(url) {
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1372
|
+
try {
|
|
1373
|
+
const res = await fetch(url);
|
|
1374
|
+
if (!res.ok) {
|
|
1375
|
+
throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
|
|
1376
|
+
}
|
|
1377
|
+
try {
|
|
1378
|
+
return await res.arrayBuffer();
|
|
1379
|
+
} catch (err) {
|
|
1380
|
+
throw new Error(
|
|
1381
|
+
`Failed to read response body as ArrayBuffer from ${url}: ${err instanceof Error ? err.message : String(err)}`
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
} catch (err) {
|
|
1385
|
+
if (err instanceof Error) {
|
|
1386
|
+
if (err.message.includes("Failed to fetch") || err.message.includes("Failed to read")) {
|
|
1387
|
+
throw err;
|
|
1388
|
+
}
|
|
1389
|
+
throw new Error(`Failed to fetch ${url}: ${err.message}`);
|
|
1390
|
+
}
|
|
1391
|
+
throw new Error(`Failed to fetch ${url}: ${String(err)}`);
|
|
1392
|
+
}
|
|
1195
1393
|
}
|
|
1196
1394
|
|
|
1395
|
+
// src/types.ts
|
|
1396
|
+
var isShadowFill2 = (op) => {
|
|
1397
|
+
return op.op === "FillPath" && op.isShadow === true;
|
|
1398
|
+
};
|
|
1399
|
+
var isGlyphFill2 = (op) => {
|
|
1400
|
+
return op.op === "FillPath" && op.isShadow !== true;
|
|
1401
|
+
};
|
|
1402
|
+
|
|
1197
1403
|
// src/env/entry.web.ts
|
|
1404
|
+
var DEFAULT_ROBOTO_URL = "https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu4mxP.ttf";
|
|
1198
1405
|
async function createTextEngine(opts = {}) {
|
|
1199
1406
|
const width = opts.width ?? CANVAS_CONFIG.DEFAULTS.width;
|
|
1200
1407
|
const height = opts.height ?? CANVAS_CONFIG.DEFAULTS.height;
|
|
@@ -1202,109 +1409,237 @@ async function createTextEngine(opts = {}) {
|
|
|
1202
1409
|
const wasmBaseURL = opts.wasmBaseURL;
|
|
1203
1410
|
const fonts = new FontRegistry(wasmBaseURL);
|
|
1204
1411
|
const layout = new LayoutEngine(fonts);
|
|
1205
|
-
|
|
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
|
+
});
|
|
1422
|
+
await fonts.init();
|
|
1423
|
+
} catch (err) {
|
|
1424
|
+
throw new Error(
|
|
1425
|
+
`Failed to initialize font registry: ${err instanceof Error ? err.message : String(err)}`
|
|
1426
|
+
);
|
|
1427
|
+
}
|
|
1206
1428
|
async function ensureFonts(asset) {
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
const
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1429
|
+
try {
|
|
1430
|
+
if (asset.customFonts) {
|
|
1431
|
+
for (const cf of asset.customFonts) {
|
|
1432
|
+
try {
|
|
1433
|
+
const bytes = await fetchToArrayBuffer(cf.src);
|
|
1434
|
+
await fonts.registerFromBytes(bytes, {
|
|
1435
|
+
family: cf.family,
|
|
1436
|
+
weight: cf.weight ?? "400",
|
|
1437
|
+
style: cf.style ?? "normal"
|
|
1438
|
+
});
|
|
1439
|
+
} catch (err) {
|
|
1440
|
+
throw new Error(
|
|
1441
|
+
`Failed to load custom font "${cf.family}" from ${cf.src}: ${err instanceof Error ? err.message : String(err)}`
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1215
1445
|
}
|
|
1446
|
+
const main = asset.font ?? {
|
|
1447
|
+
family: "Roboto",
|
|
1448
|
+
weight: "400",
|
|
1449
|
+
style: "normal",
|
|
1450
|
+
size: 48,
|
|
1451
|
+
color: "#000000",
|
|
1452
|
+
opacity: 1
|
|
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();
|
|
1475
|
+
return main;
|
|
1476
|
+
} catch (err) {
|
|
1477
|
+
if (err instanceof Error) throw err;
|
|
1478
|
+
throw new Error(`Failed to ensure fonts: ${String(err)}`);
|
|
1216
1479
|
}
|
|
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
1480
|
}
|
|
1227
1481
|
return {
|
|
1228
1482
|
validate(input) {
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1483
|
+
try {
|
|
1484
|
+
const { value, error } = RichTextAssetSchema.validate(input, {
|
|
1485
|
+
abortEarly: false,
|
|
1486
|
+
convert: true
|
|
1487
|
+
});
|
|
1488
|
+
if (error) throw error;
|
|
1489
|
+
return { value };
|
|
1490
|
+
} catch (err) {
|
|
1491
|
+
if (err instanceof Error) {
|
|
1492
|
+
throw new Error(`Validation failed: ${err.message}`);
|
|
1493
|
+
}
|
|
1494
|
+
throw new Error(`Validation failed: ${String(err)}`);
|
|
1495
|
+
}
|
|
1235
1496
|
},
|
|
1236
1497
|
async registerFontFromUrl(url, desc) {
|
|
1237
|
-
|
|
1238
|
-
|
|
1498
|
+
try {
|
|
1499
|
+
const bytes = await fetchToArrayBuffer(url);
|
|
1500
|
+
await fonts.registerFromBytes(bytes, desc);
|
|
1501
|
+
} catch (err) {
|
|
1502
|
+
throw new Error(
|
|
1503
|
+
`Failed to register font "${desc.family}" from URL ${url}: ${err instanceof Error ? err.message : String(err)}`
|
|
1504
|
+
);
|
|
1505
|
+
}
|
|
1239
1506
|
},
|
|
1240
1507
|
async registerFontFromFile(source, desc) {
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1508
|
+
try {
|
|
1509
|
+
let bytes;
|
|
1510
|
+
if (typeof source === "string") {
|
|
1511
|
+
try {
|
|
1512
|
+
bytes = await fetchToArrayBuffer(source);
|
|
1513
|
+
} catch (err) {
|
|
1514
|
+
throw new Error(
|
|
1515
|
+
`Failed to fetch font from ${source}: ${err instanceof Error ? err.message : String(err)}`
|
|
1516
|
+
);
|
|
1517
|
+
}
|
|
1518
|
+
} else {
|
|
1519
|
+
try {
|
|
1520
|
+
bytes = await source.arrayBuffer();
|
|
1521
|
+
} catch (err) {
|
|
1522
|
+
throw new Error(
|
|
1523
|
+
`Failed to read Blob as ArrayBuffer: ${err instanceof Error ? err.message : String(err)}`
|
|
1524
|
+
);
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
await fonts.registerFromBytes(bytes, desc);
|
|
1528
|
+
} catch (err) {
|
|
1529
|
+
if (err instanceof Error) {
|
|
1530
|
+
throw err;
|
|
1531
|
+
}
|
|
1532
|
+
throw new Error(
|
|
1533
|
+
`Failed to register font "${desc.family}" from ${typeof source === "string" ? source : "Blob"}: ${String(err)}`
|
|
1534
|
+
);
|
|
1246
1535
|
}
|
|
1247
|
-
await fonts.registerFromBytes(bytes, desc);
|
|
1248
1536
|
},
|
|
1249
1537
|
async renderFrame(asset, tSeconds) {
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1538
|
+
try {
|
|
1539
|
+
const main = await ensureFonts(asset);
|
|
1540
|
+
const desc = { family: main.family, weight: `${main.weight}`, style: main.style };
|
|
1541
|
+
let lines;
|
|
1542
|
+
try {
|
|
1543
|
+
lines = await layout.layout({
|
|
1544
|
+
text: asset.text,
|
|
1545
|
+
width: asset.width ?? width,
|
|
1546
|
+
letterSpacing: asset.style?.letterSpacing ?? 0,
|
|
1547
|
+
fontSize: main.size,
|
|
1548
|
+
lineHeight: asset.style?.lineHeight ?? 1.2,
|
|
1549
|
+
desc,
|
|
1550
|
+
textTransform: asset.style?.textTransform ?? "none"
|
|
1551
|
+
});
|
|
1552
|
+
} catch (err) {
|
|
1553
|
+
throw new Error(
|
|
1554
|
+
`Failed to layout text: ${err instanceof Error ? err.message : String(err)}`
|
|
1555
|
+
);
|
|
1556
|
+
}
|
|
1557
|
+
const textRect = {
|
|
1558
|
+
x: 0,
|
|
1559
|
+
y: 0,
|
|
1560
|
+
width: asset.width ?? width,
|
|
1561
|
+
height: asset.height ?? height
|
|
1562
|
+
};
|
|
1563
|
+
const canvasW = asset.width ?? width;
|
|
1564
|
+
const canvasH = asset.height ?? height;
|
|
1565
|
+
const canvasPR = asset.pixelRatio ?? pixelRatio;
|
|
1566
|
+
let ops0;
|
|
1567
|
+
try {
|
|
1568
|
+
ops0 = await buildDrawOps({
|
|
1569
|
+
canvas: { width: canvasW, height: canvasH, pixelRatio: canvasPR },
|
|
1570
|
+
textRect,
|
|
1571
|
+
lines,
|
|
1572
|
+
font: {
|
|
1573
|
+
family: main.family,
|
|
1574
|
+
size: main.size,
|
|
1575
|
+
weight: `${main.weight}`,
|
|
1576
|
+
style: main.style,
|
|
1577
|
+
color: main.color,
|
|
1578
|
+
opacity: main.opacity
|
|
1579
|
+
},
|
|
1580
|
+
style: {
|
|
1581
|
+
lineHeight: asset.style?.lineHeight ?? 1.2,
|
|
1582
|
+
textDecoration: asset.style?.textDecoration ?? "none",
|
|
1583
|
+
gradient: asset.style?.gradient
|
|
1584
|
+
},
|
|
1585
|
+
stroke: asset.stroke,
|
|
1586
|
+
shadow: asset.shadow,
|
|
1587
|
+
align: asset.align ?? { horizontal: "center", vertical: "middle" },
|
|
1588
|
+
background: asset.background,
|
|
1589
|
+
glyphPathProvider: (gid) => fonts.glyphPath(desc, gid),
|
|
1590
|
+
getUnitsPerEm: () => fonts.getUnitsPerEm(desc)
|
|
1591
|
+
});
|
|
1592
|
+
} catch (err) {
|
|
1593
|
+
throw new Error(
|
|
1594
|
+
`Failed to build draw operations: ${err instanceof Error ? err.message : String(err)}`
|
|
1595
|
+
);
|
|
1596
|
+
}
|
|
1597
|
+
try {
|
|
1598
|
+
const ops = applyAnimation(ops0, lines, {
|
|
1599
|
+
t: tSeconds,
|
|
1600
|
+
fontSize: main.size,
|
|
1601
|
+
anim: asset.animation ? {
|
|
1602
|
+
preset: asset.animation.preset,
|
|
1603
|
+
speed: asset.animation.speed,
|
|
1604
|
+
duration: asset.animation.duration,
|
|
1605
|
+
style: asset.animation.style,
|
|
1606
|
+
direction: asset.animation.direction
|
|
1607
|
+
} : void 0
|
|
1608
|
+
});
|
|
1609
|
+
return ops;
|
|
1610
|
+
} catch (err) {
|
|
1611
|
+
throw new Error(
|
|
1612
|
+
`Failed to apply animation: ${err instanceof Error ? err.message : String(err)}`
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1615
|
+
} catch (err) {
|
|
1616
|
+
if (err instanceof Error) {
|
|
1617
|
+
throw err;
|
|
1618
|
+
}
|
|
1619
|
+
throw new Error(`Failed to render frame at time ${tSeconds}s: ${String(err)}`);
|
|
1620
|
+
}
|
|
1298
1621
|
},
|
|
1299
1622
|
createRenderer(canvas) {
|
|
1300
|
-
|
|
1623
|
+
try {
|
|
1624
|
+
return createWebPainter(canvas);
|
|
1625
|
+
} catch (err) {
|
|
1626
|
+
throw new Error(
|
|
1627
|
+
`Failed to create renderer: ${err instanceof Error ? err.message : String(err)}`
|
|
1628
|
+
);
|
|
1629
|
+
}
|
|
1301
1630
|
},
|
|
1302
1631
|
destroy() {
|
|
1303
|
-
|
|
1632
|
+
try {
|
|
1633
|
+
fonts.destroy();
|
|
1634
|
+
} catch (err) {
|
|
1635
|
+
console.error(`Error during cleanup: ${err instanceof Error ? err.message : String(err)}`);
|
|
1636
|
+
}
|
|
1304
1637
|
}
|
|
1305
1638
|
};
|
|
1306
1639
|
}
|
|
1307
1640
|
export {
|
|
1308
|
-
createTextEngine
|
|
1641
|
+
createTextEngine,
|
|
1642
|
+
isGlyphFill2 as isGlyphFill,
|
|
1643
|
+
isShadowFill2 as isShadowFill
|
|
1309
1644
|
};
|
|
1310
1645
|
//# sourceMappingURL=entry.web.js.map
|